Trilium Notes is an open source, hierarchical note-taking application built for large personal knowledge bases: notes live in a tree, can be cloned into several branches, and support rich text, code, diagrams and scripting. The project is now maintained by the TriliumNext community under the original Trilium Notes name. In this tutorial you will run the Trilium server on Ubuntu 24.04 with Docker Compose, publish it over HTTPS behind Nginx, connect the desktop app to it for sync, and schedule daily backups of the database.

Prerequisites

To follow this guide you need:

  • A server running Ubuntu 24.04 LTS, for example a CubePath VPS, with at least 1 GB of RAM.
  • A non-root user with sudo privileges.
  • Docker Engine and the Docker Compose plugin installed from Docker's official repository.
  • A domain or subdomain (this guide uses notes.your_domain) with a DNS A record pointing to your_server_ip. Desktop sync and the web clipper work best over HTTPS.
  • Ports 22, 80 and 443 open in your firewall.

Step 1 - Creating the data directory

Trilium keeps everything, including the SQLite database document.db, its config.ini and its automatic backups, in a single data directory. Keeping it on the host under /opt/trilium/data makes backups and upgrades straightforward.

The container runs Trilium as the node user with UID and GID 1000, so give that UID ownership of the directory:

sudo mkdir -p /opt/trilium/data
sudo chown -R 1000:1000 /opt/trilium/data

Confirm the ownership:

ls -ld /opt/trilium/data
drwxr-xr-x 2 1000 1000 4096 Sep 25 10:12 /opt/trilium/data

The owner may be shown as a user name instead of 1000 if your sudo user happens to have that UID. That is fine.

Step 2 - Running Trilium with Docker Compose

Create the Compose file:

sudo nano /opt/trilium/compose.yaml

Add the following service definition. The image triliumnext/trilium is the maintained build of Trilium Notes:

services:
  trilium:
    image: triliumnext/trilium:latest
    container_name: trilium
    restart: unless-stopped
    environment:
      - TRILIUM_DATA_DIR=/home/node/trilium-data
    ports:
      - "127.0.0.1:8080:8080"
    volumes:
      - /opt/trilium/data:/home/node/trilium-data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro

The port is published only on 127.0.0.1. Ports published by Docker bypass UFW rules, so binding to the loopback interface is what actually keeps Trilium off the public internet until Nginx and TLS are in front of it.

Start the container:

cd /opt/trilium
sudo docker compose up -d

Check that it is running and that the web server started:

sudo docker compose ps
sudo docker compose logs --tail 20 trilium

Near the end of the logs you should see a line similar to this:

trilium  | App HTTP server starting up at port 8080

Test the local endpoint:

curl -sI http://127.0.0.1:8080 | head -n 1
HTTP/1.1 302 Found

A redirect to the setup page is expected on a fresh install. Listing the data directory with sudo ls /opt/trilium/data should now show at least config.ini and document.db, which confirms the volume is mounted and writable.

Step 3 - Configuring Nginx and HTTPS

Install Nginx and Certbot with its Nginx plugin:

sudo apt update
sudo apt install nginx certbot python3-certbot-nginx

Create a server block for Trilium:

sudo nano /etc/nginx/sites-available/trilium

Paste the following configuration, replacing notes.your_domain with your domain. Trilium uses WebSockets to push changes to open browser tabs, so the Upgrade and Connection headers are required:

server {
    listen 80;
    listen [::]:80;
    server_name notes.your_domain;

    client_max_body_size 50M;

    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";
    }
}

client_max_body_size 50M allows larger attachments and imports than the Nginx default of 1 MB.

Enable the site, test the configuration and reload Nginx:

sudo ln -s /etc/nginx/sites-available/trilium /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

If UFW is active, allow HTTP and HTTPS:

sudo ufw allow 'Nginx Full'

Request a Let's Encrypt certificate. Certbot edits the server block to add the TLS settings and an HTTP to HTTPS redirect:

sudo certbot --nginx -d notes.your_domain

When Certbot finishes, confirm that the site answers over HTTPS:

curl -sI https://notes.your_domain | head -n 1
HTTP/2 302

Certbot also installs a systemd timer that renews the certificate automatically. You can test renewal with sudo certbot renew --dry-run.

Step 4 - Completing the initial setup

Open https://notes.your_domain in your browser. Trilium shows a setup screen with three options:

  1. Choose I'm a new user, and I want to create a new Trilium document for my notes and continue.
  2. Set a strong password. This password protects the web interface and is also used by the desktop app and the web clipper to authenticate against the server.
  3. Log in with the new password.

Trilium creates a set of demo notes that explain its features. You can read them or delete the whole demo subtree from the note tree.

Step 5 - Syncing the desktop app

The desktop app for Linux, macOS and Windows keeps a full local copy of your notes and syncs it with the server, so you can work offline. Download the installer for your platform from the Releases page of the TriliumNext/Trilium repository on GitHub. Use a desktop version that matches the server version, because the sync protocol changes between major releases.

On first launch, the desktop app shows the same setup screen as the server:

  1. Choose I have a server instance already, and I want to set up sync with it.
  2. Enter https://notes.your_domain as the server address.
  3. Enter the password you set in Step 4 and click Finish setup.

The app downloads all notes from the server. When the progress indicator finishes, create a test note on the desktop and refresh the web interface: the note appears within a few seconds.

If you already use the desktop app and want to push your existing notes to a new, empty server, do it the other way around: set up the server with the I have a desktop instance already option and follow the instructions it shows, or configure the server address under Options > Sync in the desktop app.

Step 6 - Backing up the database

Trilium creates its own rotating backups inside the data directory:

sudo ls /opt/trilium/data/backup
backup-daily.db  backup-monthly.db  backup-weekly.db

These copies live on the same disk as the original, so they do not protect you from losing the server. Add a daily backup that writes consistent copies to a separate directory, which you can then ship off the server with your usual tool (rsync, restic, object storage).

Install the SQLite command-line tool. Its .backup command takes a consistent snapshot while Trilium is running:

sudo apt install sqlite3

Create the backup script:

sudo nano /usr/local/bin/trilium-backup
#!/usr/bin/env bash
set -euo pipefail

DATA_DIR="/opt/trilium/data"
BACKUP_DIR="/var/backups/trilium"
STAMP="$(date +%F)"

mkdir -p "$BACKUP_DIR"
sqlite3 "$DATA_DIR/document.db" ".backup '$BACKUP_DIR/document-$STAMP.db'"
cp "$DATA_DIR/config.ini" "$BACKUP_DIR/config-$STAMP.ini"
gzip -f "$BACKUP_DIR/document-$STAMP.db"

# Keep 30 days of backups
find "$BACKUP_DIR" -name 'document-*.db.gz' -mtime +30 -delete
find "$BACKUP_DIR" -name 'config-*.ini' -mtime +30 -delete

Make it executable and run it once to test it:

sudo chmod 750 /usr/local/bin/trilium-backup
sudo /usr/local/bin/trilium-backup
sudo ls -lh /var/backups/trilium
-rw-r--r-- 1 root root 1.1K Sep 25 10:40 config-2026-09-25.ini
-rw-r--r-- 1 root root 412K Sep 25 10:40 document-2026-09-25.db.gz

Schedule it every night at 02:30 with a cron file:

echo '30 2 * * * root /usr/local/bin/trilium-backup' | sudo tee /etc/cron.d/trilium-backup

To restore a backup, stop the container, replace the database and remove any leftover WAL files, then start it again:

cd /opt/trilium
sudo docker compose stop trilium
sudo gunzip -c /var/backups/trilium/document-2026-09-25.db.gz | sudo tee /opt/trilium/data/document.db > /dev/null
sudo rm -f /opt/trilium/data/document.db-wal /opt/trilium/data/document.db-shm
sudo chown 1000:1000 /opt/trilium/data/document.db
sudo docker compose start trilium

Step 7 - Upgrading Trilium

To upgrade, take a backup first, then pull the new image and recreate the container. Trilium migrates the database schema automatically on startup:

sudo /usr/local/bin/trilium-backup
cd /opt/trilium
sudo docker compose pull
sudo docker compose up -d
sudo docker compose logs --tail 20 trilium

After a major upgrade, update the desktop app to the same version so sync keeps working.

Troubleshooting

The browser shows 502 Bad Gateway. Nginx cannot reach the container. Check that it is running with sudo docker compose ps and read sudo docker compose logs trilium. A common cause is a data directory that the container cannot write to; fix it with sudo chown -R 1000:1000 /opt/trilium/data and restart the container.

Changes made in one tab do not appear in another until you reload. The WebSocket connection is failing. Make sure the Upgrade and Connection headers from Step 3 are in the HTTPS server block that Certbot created, then run sudo nginx -t && sudo systemctl reload nginx.

The desktop app reports a sync version mismatch. The desktop app and the server run different major versions. Upgrade the older side so both match.

Conclusion

You now have a self-hosted Trilium Notes server running in Docker on Ubuntu 24.04, reachable over HTTPS, syncing with the desktop app and backed up every night. From here you can install the Trilium Web Clipper browser extension to save pages straight into your tree, copy /var/backups/trilium to off-site storage, and explore note types such as Mermaid diagrams, canvas and code notes described in the demo notes.