Workload · Syncing your Joplin notes

Your own Joplin Server: self hosted sync for every device you write on

You can run Joplin Server yourself by starting its official Docker image with a PostgreSQL database on a machine you control, then pointing the Joplin apps on your laptop, phone and desktop at it. Your notes travel between your devices through a box only you administer. This guide walks you through it in order, including the two steps that need care: the admin account that ships with a well-known password, and a proxy setting without which nothing loads.

The short version
  1. Notes sync through a server you administer, not a shared service
  2. Swap the shipped admin password before the world can reach it
  3. Turn on Joplin's end-to-end encryption so even your server sees ciphertext
  4. Free for personal, non-commercial use under its own licence

What you are building

Three pieces sit on one machine: the Joplin Server container, a PostgreSQL database beside it, and a web server in front that adds HTTPS. The apps talk only to the HTTPS address. Why Joplin Server rather than any folder that syncs? In Joplin's own code it is the target that adds better performance and Joplin's sharing features on top of plain sync.

Already running a file-sync server? Joplin can use that instead. Its documentation names Nextcloud as a sync target, and Seafile appears on its list of WebDAV services that work with it, so a Nextcloud server or a Seafile server can carry your notes too, minus the sharing features.

How big a server does it need?

Not a big one. Joplin publishes requirements only for its business edition, aimed at moderate load with several people syncing at once, and even those are modest:

  • Processor: 2 cores, 4 threads.
  • Memory: 4 GB at minimum; 8 GB is the recommendation.
  • Disk: at least 50 GB of SSD, plus room for attachments if you store them outside the database.
  • Database: PostgreSQL 16.8 or newer for real use; SQLite only for trying it out.
  • Network: a reliable 1 Gbps connection.

For one person or a household, one of our small cartridge servers fits the job: compact machines meant for running one thing well, with the whole box still yours. If you want room for other jobs alongside it, an everyday single-processor server is where most people start. Both are on the dedicated server page. Nobody meters the port, so syncing a big attachment library to a new phone never shows up on your invoice.

Rack servers, blades, and two cartridge models (m510, m710x) have a remote console that works out of band: the screen and power button even when the network is down. The remaining cartridge models offer power control and nothing more, so take extra care with firewall changes on them.

Before you start

  • A domain name you can point at the server, for example notes.example.com.
  • Docker Engine 28 or newer, installed from Docker's own guide. On older engines a port bound to 127.0.0.1 could still be reached from other machines on that network segment (moby#45610).
  • Your own SSH login on the server; the admin tunnel later goes through it.
  • IPv6 if you want it: it comes at no charge, and the firewall steps below cover it.
  • Pick the data center closest to where you write; a ready server is handed over within 30 minutes of payment approval.

Getting it running, step by step

  1. Run sudo -i by itself so you have a root shell.
  2. Paste the first block. It tells ufw to keep IPv6 rules before adding any, lets SSH in, switches the firewall on without the yes/no prompt, and writes a .env file only root can read, holding a database password generated on the spot, which never lands in your shell history.
  3. Save the second block as /opt/joplin/docker-compose.yml. It is the official docker-compose.server.yml with the optional Transcribe services removed, a fixed image version, and the app reachable from the server itself only.
  4. Launch the two containers: docker compose up -d, run inside /opt/joplin. Then read docker compose logs app to confirm it started cleanly.
sed -i 's/^IPV6=.*/IPV6=yes/' /etc/default/ufw
ufw allow OpenSSH
ufw --force enable
mkdir -p /opt/joplin && cd /opt/joplin
umask 077
cat > .env <<EOF
APP_BASE_URL=http://localhost:22300
APP_PORT=22300
DB_CLIENT=pg
POSTGRES_USER=joplin
POSTGRES_PASSWORD=$(openssl rand -hex 24)
POSTGRES_DATABASE=joplin
POSTGRES_PORT=5432
EOF
chmod 600 .env
services:
  db:
    image: postgres:16
    restart: unless-stopped
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
    environment:
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=${POSTGRES_DATABASE}
  app:
    image: joplin/server:3.7.3
    restart: unless-stopped
    depends_on:
      - db
    ports:
      - "127.0.0.1:22300:22300"
    environment:
      - APP_PORT=22300
      - APP_BASE_URL=${APP_BASE_URL}
      - DB_CLIENT=pg
      - POSTGRES_HOST=db
      - POSTGRES_PORT=${POSTGRES_PORT}
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DATABASE=${POSTGRES_DATABASE}

Why 127.0.0.1 in the ports line? Joplin's sample writes the port with no address, which makes Docker listen on every address the server has, IPv4 and IPv6. Traffic to a published port goes around ufw for IPv4, and for IPv6 as well when IPv6 networking is enabled in Docker. Binding to 127.0.0.1 keeps the app private until the proxy is ready.

Changing the admin password before anyone else can see it

Every new Joplin Server starts with the same admin account, as its README states: login admin@localhost, password admin. Fix that while the app is still private:

  1. On your own computer, run ssh -N -L 22300:127.0.0.1:22300 yourname@your-server-ip, using your normal login.
  2. Open http://localhost:22300 in your browser and sign in with the default details.
  3. Click Profile at the top right and choose a strong new password.
  4. Go to Users and add an ordinary account for syncing. Joplin recommends a separate non-admin account for this.

That is why .env starts with APP_BASE_URL at http://localhost:22300: the server checks that each request arrives on the same host as APP_BASE_URL, and answers Invalid origin when it does not. Nobody can sign themselves up meanwhile, because public signup is switched off by default in the server's settings.

Opening it to your devices with HTTPS

  1. Point notes.example.com at the server and edit .env to read APP_BASE_URL=https://notes.example.com. Re-run docker compose up -d; Compose notices the changed value and recreates the container.
  2. Set up nginx (Joplin's README also links Apache's proxy module) with proxy_pass http://127.0.0.1:22300; and, just as important, proxy_set_header Host $host;. Leave that second line out and the server sees the wrong host and refuses everything with Invalid origin.
  3. Lift nginx's default 1 MB request limit with client_max_body_size, or larger attachments fail to upload.
  4. Open the web ports first: ufw allow 80,443/tcp. Because you set IPV6=yes first, the rule covers IPv6 visitors too. Then get a certificate for the name.
  5. On every device, go to Joplin's synchronisation settings, select Joplin Server as the target, then type https://notes.example.com plus your sync account's email and password.

From now on you reach the admin pages through notes.example.com; the tunnel address stops matching.

Locking your notes with end-to-end encryption

Turn on Joplin's end-to-end encryption and your server only ever stores scrambled notes, notebooks, tags and files. Joplin's guide is careful about the order. Start on your desktop or terminal app, enable encryption and choose a master password you will not lose, because there is no way to recover it. Sync and wait, even overnight, while everything is re-sent encrypted. Then sync your other devices one at a time; each one asks for that password. Switching it on in two places at once leaves you with more than one key.

Keeping copies, and updating safely

Your notes live in PostgreSQL, so a nightly pg_dump is your backup. Make a private folder with install -d -m 0700 -o root /var/backups/joplin, then create /etc/cron.d/joplin-backup containing these two lines:

15 2 * * * root umask 077; cd /opt/joplin && docker compose exec -T db pg_dump -U joplin -Fc joplin > /var/backups/joplin/joplin-$(date +\%F).dump
45 2 * * * root find /var/backups/joplin -name 'joplin-*.dump' -mtime +14 -delete

You manage the server; we keep the hardware and the network running. So copy those dumps, and your .env, somewhere else on a schedule of your own. If you ever need one back, run these inside /opt/joplin: docker compose stop app, then docker compose exec -T db pg_restore --clean --if-exists -U joplin -d joplin < /var/backups/joplin/joplin-YYYY-MM-DD.dump, then docker compose start app.

Updating is short: read the release notes in the server changelog, make a new dump, raise the version in the image line, fetch it with docker compose pull, then recreate the container with docker compose up -d. The server updates its database structure on start by default, so going back means restoring the dump, not just an older image. Leave the database image on postgres:16; moving PostgreSQL to a new major version is a separate job.

Is the licence right for you?

Joplin's apps are open source under AGPL-3.0-or-later, but the server is not: it has a licence of its own, Joplin Server Personal Use License, version 1, dated 21 December 2021. That licence allows personal and professional note-taking, sharing and syncing for non-commercial purposes. It does not let a business give people access, even for free, or let anyone charge for it. For a company, Joplin sells a commercially licensed edition, Joplin Server Business.

Questions people ask

What do I sign in with the first time?
The email admin@localhost and the password admin. Set a new one on the Profile page straight away, ideally through an SSH tunnel, while the app is bound to 127.0.0.1. Then create a separate account for everyday syncing.
My browser shows Invalid origin. What did I miss?
The address you used does not match APP_BASE_URL. Behind nginx, add proxy_set_header Host $host to the site. If you changed APP_BASE_URL, recreate the container with docker compose up -d so it reads the new value.
Can I skip PostgreSQL and keep it simple?
For a quick test, yes: the server falls back to SQLite when no database is set. Joplin's own sample configuration says that mode is mostly for testing and evaluation, and recommends PostgreSQL for production. With PostgreSQL your backup is also one command.
Can I host it for my small business?
Not under the free licence: it covers non-commercial use only, and a business may not give others access to the server. Joplin Server Business is the commercially licensed edition for that case.
Does my server see my notes?
Only if you leave encryption off. With Joplin's end-to-end encryption enabled on your devices, your server, and every backup of it, holds your notes, notebooks, tags and attachments only in encrypted form; account emails and sync metadata stay readable. The content cannot be opened without your master password. Keep that password safe: Joplin cannot recover it.

Give your notes a home of their own

Choose a small dedicated server for your Joplin sync; ready ones are handed over within 30 minutes of approval, month to month.

Get a server for your notes

Read next