Workload · Syncing your Trilium notes

Run your own Trilium server and keep every device in sync

To run your own Trilium server, start the official triliumnext/trilium Docker image on a machine you control, keep its port 8080 private to that machine, set your password through a private SSH tunnel, and open it to the internet behind nginx with HTTPS only after that. Your Trilium desktop apps then sync with it, and you can reach the same notes from any browser. This guide walks through it in order, including the backup you will be glad of later.

The short version
  1. The project to install is TriliumNext/Trilium, which now owns the original repository
  2. Your whole knowledge base is one folder, with a SQLite file called document.db at its heart
  3. Set the password privately first: an unclaimed server lets the first visitor in
  4. If an update changes the sync protocol, server and desktops on different versions stop syncing

What you get from a server of your own

Trilium works offline on your desktop, and a server turns several desktops into one knowledge base. The synchronization docs describe a star: each desktop talks to one central sync server, automatically, with nothing to press. The same server also gives you Trilium in any browser, with a mobile layout for your phone; there is no official phone app.

Which Trilium to install today

Install TriliumNext/Trilium. The original author, zadam, marked his version as being in maintenance mode, and the TriliumNext README now explains that he handed the repository to that community project. The documentation lives at docs.triliumnotes.org and the Docker image is called triliumnext/trilium.

If you still run zadam's last release, v0.63.7, you can point the new version at your existing data with no conversion step, per the README. Just know that only TriliumNext releases up to v0.90.4 sync with v0.63.7, so move every device across at the same time.

How much server you need

The project does not publish memory or processor figures for a server, so here is what it does say, followed by our own view:

  • Docker images are built for AMD64, ARM64/v8 and ARMv7, so any ordinary x86 server will run it.
  • Everything is stored in one SQLite database, document.db, which the docs say holds all notes, the tree, metadata and most settings.
  • The README says Trilium stays usable and fast past 100,000 notes.
  • Our view: for one person's notes this is a light job, and disk space matters more than processor power as attachments pile up.

The server that fits

Because a notes server sits quietly all day and only works when you save something, one of the small cartridge servers on our dedicated server page is usually plenty. You get the whole machine, root access and an unmetered port, which means loading a big attachment library onto a new laptop never shows up on your invoice. Ready servers are yours within 30 minutes of approval; servers built to your own specification take longer.

Two things to check when you choose. If you want the console screen and power button for the day a firewall mistake locks you out, pick a rack server, a blade, or the m510 or m710x cartridge. Don't pick the X-Gene ARM cartridge either, since its only choices are Ubuntu 16.04 or 18.04, older than this guide's Docker needs. Choose Ubuntu 24.04 on whichever server you take.

Up and running in an evening

First install Docker Engine using Docker's own instructions for Ubuntu. Then paste the block below. It switches the firewall on with only SSH allowed, writes the settings file only if you do not have one yet, and starts Trilium only when Docker is version 28 or later. Why 28: Docker's own port-publishing page warns that before 28.0, other machines on the same local network segment could connect to a service you had bound to 127.0.0.1. That risk is the reason this block refuses an older engine. Pasting it a second time is safe: your existing settings file, with whatever version you have upgraded to since, stays as it is, so it can never wind the tag back against a database that newer Trilium has already converted.

sudo sed -i 's/^IPV6=.*/IPV6=yes/' /etc/default/ufw
sudo ufw allow OpenSSH && sudo ufw --force enable
sudo install -d -m 750 /opt/trilium
[ -e /opt/trilium/compose.yaml ] || sudo tee /opt/trilium/compose.yaml >/dev/null <<'EOF'
services:
  trilium:
    image: triliumnext/trilium:v0.106.1
    restart: unless-stopped
    environment:
      - TRILIUM_DATA_DIR=/home/node/trilium-data
      - TRILIUM_NETWORK_TRUSTEDREVERSEPROXY=uniquelocal
      - TZ=Europe/London
    ports:
      - "127.0.0.1:8080:8080"
    volumes:
      - /opt/trilium/data:/home/node/trilium-data
EOF
ver=$(sudo docker version --format '{{.Server.Version}}')
if [ "${ver%%.*}" -ge 28 ] 2>/dev/null; then
  sudo docker compose -f /opt/trilium/compose.yaml up -d && echo running
else
  echo "Found Docker '$ver'. Upgrade to 28+ first; Trilium was not started."
fi

Compared with the compose file the project publishes, five things differ, and that is the complete list: a fixed version tag instead of latest (the Docker guide warns that latest may land you on a new release at the next pull or through an auto-updater, and upset sync); the port limited to 127.0.0.1; the data kept in /opt/trilium/data; the trusted-proxy setting added; and the TZ variable from the Docker guide used in place of the two mounted timezone files. Put your own time zone in TZ. Why the 127.0.0.1 matters: without an address, Docker publishes the port on every IPv4 and IPv6 address (0.0.0.0 and [::]), and such traffic slips past ufw for IPv4, and for IPv6 as well when IPv6 networking is enabled in Docker itself.

Claim it before anyone else can

A brand-new Trilium server asks no questions: whoever reaches it first runs the setup wizard and picks the password. So do this part over your own SSH connection while the port is still private. On your own computer, run ssh -N -L 9080:127.0.0.1:8080 you@your-server and leave it open. Then follow whichever path matches you.

Your first Trilium, or a clean start:

  1. Visit http://localhost:9080 and choose your language.
  2. Click New knowledge base.
  3. Decide between With demo content, which fills it with example notes, and Empty.
  4. On the screen after that, set your password. The server code accepts as few as four characters, but pick something long: Trilium also uses it to encrypt your protected notes, and they cannot be recovered without it.

Notes already in a desktop app: don't touch the wizard at all, so the server stays unset. In that desktop open Options, then Sync, put http://localhost:9080 in Server address and run the connection test while the tunnel is up. According to the synchronization docs, the desktop uploads everything and the server then shows its login page. Because Trilium marks the password settings as synced, we expect the server to take your desktop's password, so log in with that one. If your desktop never had a password, the server's code sends you to a set-password screen instead; choose one right away, still through the tunnel, before any proxy is public. The docs themselves don't spell that case out.

Any other desktop picks Connect to an existing server during its own setup and enters the address and password. When HTTPS is working in the next step, point every desktop at your https name instead. Wrong passwords are limited to ten per address in 15 minutes.

Letting your devices in safely

Your devices should reach Trilium only through HTTPS on a reverse proxy, which the docs recommend over Trilium's built-in TLS. The proxy has to forward the WebSocket upgrade, because sync and live updates travel over one; without it the page loads but never updates. Make your chosen hostname resolve to the server, then run this. It is a trimmed version of the docs' nginx examples, and it sets every timeout their sync-timeout page lists to 300 seconds. We added two lines of our own: the HTTP/1.1 setting for the proxy, plus client_max_body_size 0, which lifts nginx's upload size cap. The --redirect flag tells certbot to forward plain http visits to https:

sudo apt install -y nginx certbot python3-certbot-nginx
sudo ufw allow 80,443/tcp
sudo tee /etc/nginx/sites-available/trilium.conf >/dev/null <<'EOF'
server {
    listen 80;
    listen [::]:80;
    server_name trilium.example.com;
    client_max_body_size 0;
    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_connect_timeout 300;
        proxy_send_timeout 300;
        proxy_read_timeout 300;
        send_timeout 300;
    }
}
EOF
sudo ln -sf /etc/nginx/sites-available/trilium.conf /etc/nginx/sites-enabled/trilium.conf
sudo nginx -t && sudo systemctl reload nginx && sudo certbot --nginx --redirect -d trilium.example.com

The uniquelocal value in the compose file is one of the two settings the trusted-proxy page suggests when Trilium runs in Docker and nginx sits directly on the host; it lets Trilium see each visitor's real address for its sign-in limit. It trusts all private ranges, which is fine here because port 8080 never leaves the machine. One thing not to add: a login wall such as Authelia in front of Trilium. The docs explain that it breaks sync unless the sync and setup paths are left out of it.

Keeping your notes safe

You manage the server; we keep the hardware and the network running, so backups are yours. Trilium helps: it saves copies of document.db daily, weekly, monthly and just before any database upgrade, into a backup folder in its data directory, and Options, then Backup, has a Backup now button. The docs call this basic and suggest a second copy somewhere else. Since the data folder holds everything, this nightly job pauses Trilium for a moment, copies the data folder plus compose.yaml into a compressed archive, tests that archive, starts Trilium again and clears out archives older than two weeks. Your desktops keep working offline meanwhile.

sudo install -d -m 700 /var/backups/trilium-notes
sudo tee /usr/local/sbin/trilium-snapshot.sh >/dev/null <<'EOF'
set -euo pipefail
umask 077
yml=/opt/trilium/compose.yaml
out=/var/backups/trilium-notes/notes-$(date +%F-%H%M).tar.gz
docker compose -f "$yml" stop trilium
trap 'docker compose -f "$yml" start trilium' EXIT
tar -C /opt/trilium -czf "$out" data compose.yaml
tar -tzf "$out" >/dev/null
echo "ok $out"
find /var/backups/trilium-notes -name 'notes-*.tar.gz' -mmin -60 | grep -q . && find /var/backups/trilium-notes -name 'notes-*.tar.gz' -mtime +14 -delete
EOF
echo '40 2 * * * root bash /usr/local/sbin/trilium-snapshot.sh > /var/log/trilium-snapshot.log 2>&1' | sudo tee /etc/cron.d/trilium-snapshot >/dev/null
sudo bash /usr/local/sbin/trilium-snapshot.sh

You should see ok on the last line. Then copy the archives to a different computer on a schedule, using plain rsync -a without --delete, so a wiped server can never wipe your copies too. Bringing an archive back, as root:

  1. Run the snapshot script once more, so what you have now is saved and checked as well. Check the archive you want with tar -tzf ARCHIVE >/dev/null && echo ok, and stop if ok does not appear.
  2. Stop Trilium with docker compose -f /opt/trilium/compose.yaml down. If /opt/trilium/data.previous already exists, sort that out first; otherwise mv /opt/trilium/data /opt/trilium/data.previous.
  3. Unpack with tar -C /opt/trilium -xzf ARCHIVE data. Give it to the container's node user (UID 1000): chown -R 1000:1000 /opt/trilium/data.
  4. Start with docker compose -f /opt/trilium/compose.yaml up -d, then log in and open a few recent notes. If something is wrong, take it down, move the restored folder out of the way and put data.previous back. Once you are happy, rm -rf /opt/trilium/data.previous.

About the document.db-wal and document.db-shm files: an archive made while Trilium was stopped has the database and its WAL file from the same moment, so they go back together. It is different when you restore a backup-*.db file that Trilium made itself. The docs' steps there are: stop Trilium, delete document.db and both side files, copy the backup in under the name document.db, make sure it is writable, and start Trilium, because, in the docs' words, those side files are auto-generated; our own reasoning is that any left behind would belong to the database you replaced.

If you sync, the docs also ask you to restore every device in the sync group, or the restored copy simply gets synced back up to the newer state.

Staying current, the licence, and when to choose something else

Trilium does not update itself. When a release comes out, read its notes, change the version tag in /opt/trilium/compose.yaml, pull the new image with docker compose -f /opt/trilium/compose.yaml pull, then up -d. On first start the database is upgraded, and older Trilium versions cannot read it afterwards; the automatic pre-upgrade backup is how you would go back. Sync uses a versioned protocol that every member must share, so whenever a release changes that protocol, update your desktop apps at the same time. Some releases matter more than others: v0.106.1 is a security fix for servers and Docker images, with a request to update quickly if yours faces the internet.

The licence is the GNU AGPL, version 3 or later. As our general reading, and not as legal advice: using it unchanged for yourself asks nothing of you; hand someone a copy, changed or unchanged, and they must also get its source, under the AGPL too; and if you modify it and people use your modified copy across a network, the AGPL says they are owed an offer of its source.

One limit to know: a Trilium server knows only one password and holds one tree of notes, so it is built for one person. For a team with separate logins, look at Joplin Server, or Memos for quick shared notes.

Questions people ask

Do I need the server if I only use Trilium on one computer?
No. The desktop app keeps everything locally and works fine alone. The server earns its place once you have a second device, want your notes in a browser, or want an always-on copy away from your laptop.
What happens to my desktop if the server goes down?
You keep working. Trilium is offline-first, so your desktop has its own full copy and syncs again when the server comes back. Edits made to the same note in two places are resolved by keeping the newer one, with the older text still in the note's revisions.
Can I just use the latest Docker tag?
The project's own Docker guide advises against it, because your next image pull, or any auto-updater you run, could move you to a new release that your desktops cannot sync with yet. Pin a version tag and upgrade on purpose, server and desktops together.
What if I forget my Trilium password?
The docs describe two recovery routes, both needing access to the server itself. Your ordinary notes survive, but protected notes are encrypted with the old password and are lost for good, so store it somewhere safe, such as a password manager.

Give your notes a home of their own

Pick a small server, put Ubuntu 24.04 on it and follow the steps above; your notes stay on hardware only you use.

Get a server for your notes

Read next