Which Trilium is current
The live project is TriliumNext/Trilium on GitHub, with its documentation at docs.triliumnotes.org. zadam, who wrote Trilium, put the original project into maintenance mode (the banner at the top of the v0.63.7 README), and the current README states that he then gave the repository to the community project; the old zadam/trilium address now leads there. Releases continue in the 0.10x series, and the Docker image is triliumnext/trilium.
One server is the sync target for the desktop apps and also serves the web and mobile interfaces. Coming from an old zadam instance needs no migration step: the README says TriliumNext reads the existing database. The catch is sync. Versions up to v0.90.4 are sync-compatible with zadam's last release, v0.63.7; later versions raised the sync version, so an old desktop cannot sync with a current server until both run the same release.
What the server needs
The documentation publishes no CPU or memory figure for a server. It states the platforms, the storage model and a scale claim, and the rest is our reading:
| Item | What the project states | Our reading |
|---|---|---|
| Platforms | Docker images for AMD64, ARMv7 and ARM64/v8; native server builds for Linux x64 and aarch64 | Any x86 line runs the amd64 image |
| Storage | One SQLite file, document.db, holding notes, tree, metadata and most settings | Disk use grows with what you paste and attach |
| Scale | README: performs well upwards of 100,000 notes | A personal or small-team tree is a light load |
| Network | Clients reach it over HTTPS; sync uses a WebSocket | A new device pulls the whole database once, then only changes |
| Clock | Client and server must agree within five minutes | Keep time sync running on the server |
Picking the line
A notes server is always on and rarely busy, so a small whole machine is enough. On our dedicated server range a Moonshot cartridge fits: one service, one box, root access, an unmetered port. If you want a remote console for the night a firewall rule locks you out, take an m510 or m710x cartridge or a racked single-socket server; the other cartridges have power control only. Racked machines are running about 30 minutes from payment approval; built-to-order lines take 4 to 24 hours.
Install Ubuntu 24.04 where the line offers it. Leave out the X-Gene ARM cartridge: it boots only Ubuntu 16.04 and 18.04, too old for a current Docker Engine. The Ampere Altra line on the ARM servers page runs the arm64 image, but it is built to order and oversized for this job.
Installing with Docker Compose
Install Docker Engine from Docker's own repository first. This block enables ufw ahead of any listener, writes a compose file if none exists yet, and starts Trilium only on Engine 28 or later. Docker's port-publishing docs warn that in releases before 28.0, hosts on the same layer-2 segment could reach a port published to 127.0.0.1, so the block refuses older engines. A re-run leaves an existing compose.yaml untouched and so keeps any tag you have since upgraded to; rewriting it would put the old tag back in front of a database that has already migrated.
sudo sed -i 's/^IPV6=.*/IPV6=yes/' /etc/default/ufw
sudo ufw allow OpenSSH && sudo ufw --force enable
sudo install -d -m 750 /srv/trilium
if [ ! -e /srv/trilium/compose.yaml ]; then
sudo tee /srv/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=Etc/UTC
ports:
- "127.0.0.1:8080:8080"
volumes:
- /srv/trilium/trilium-data:/home/node/trilium-data
EOF
fi
v=$(sudo docker version --format '{{.Server.Version}}')
if [ "${v%%.*}" -ge 28 ] 2>/dev/null; then
sudo docker compose -f /srv/trilium/compose.yaml up -d && echo started
else
echo "Docker Engine is '$v', need 28 or newer: not starting"
fi
Our changes to the project's docker-compose.yml, all of them: the latest tag is replaced by a version tag (the Docker page warns that with latest the next pull, or an auto-updater, can move a sync setup to a new release); the port is bound to 127.0.0.1; the data folder is a fixed path; the trusted-proxy variable is added; and the two timezone file mounts are replaced by the TZ variable the Docker page describes. Set TZ to your own zone. A port mapping without an address would publish on 0.0.0.0 and [::], and Docker-published traffic skips ufw on IPv4 (and on IPv6 when Docker's own IPv6 networking is on), which is why the bind matters.
First run and the password, through a tunnel
An uninitialised Trilium server belongs to whoever opens it first: the setup wizard and the set-password screen ask for nothing. Do this through your own SSH login before any proxy exists. Open the tunnel from your workstation with ssh -N -L 28080:127.0.0.1:8080 you@trilium-host and keep it running.
Starting fresh. In v0.106.1 the wizard runs in this order (setup.tsx):
- Browse to
http://localhost:28080and pick the language. - Choose New knowledge base.
- Choose With demo content or Empty.
- Set the password on the screen that follows.
login.tsaccepts four characters; use a long one, because the app warns that it also encrypts protected notes and a lost password loses them.
Bringing notes from an existing desktop. Skip all four steps above and leave the server uninitialised. In the desktop, set Options → Sync → Server address to http://localhost:28080 while the tunnel is open, and press the sync test; per Synchronization.md the desktop then pushes everything and the server ends on its login page. The password options are created as synced in password.ts, so our reading is that the server takes the desktop's password: sign in with that. If the desktop never had a password, auth.ts sends a server without one to the set-password screen; set it then, still through the tunnel, before nginx goes public. The docs do not describe that last case themselves.
Further desktops choose Connect to an existing server at their own setup and give the address and password. Once nginx is live, change each desktop's server address to the https name. The sign-in limit is per IP: ten failures in 15 minutes, per Authentication.md.
nginx, TLS and the WebSocket
Trilium itself speaks plain HTTP on 8080; the reverse proxy page puts TLS on the proxy and lists three requirements: pass the Upgrade and Connection headers (sync and live updates use a WebSocket), configure the trusted proxy so the sign-in limit sees real client addresses, and terminate HTTPS there. Your hostname must already resolve to this machine. The server block is simplified from the docs' two nginx examples, with the four 300-second timeouts from their sync-timeout page. proxy_http_version 1.1 and client_max_body_size 0 are our additions, and --redirect makes certbot send plain HTTP to HTTPS:
sudo apt install -y nginx certbot python3-certbot-nginx
sudo ufw allow 80,443/tcp
sudo tee /etc/nginx/sites-available/trilium >/dev/null <<'EOF'
server {
listen 80;
listen [::]:80;
server_name notes.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 /etc/nginx/sites-enabled/trilium
sudo nginx -t && sudo systemctl reload nginx && sudo certbot --nginx --redirect -d notes.example.com
For Trilium in Docker with the proxy on the host, the trusted-proxy page gives two choices: the Docker network's subnet, or uniquelocal, used here. It trusts every private range, which is acceptable here only because the port is bound to 127.0.0.1. Do not put a forward-auth login (Authelia and the like) in front of Trilium: the docs say it breaks sync, unless /api/setup/*, /api/login/sync and /api/sync/* are exempted.
Backups and a clean restore
Trilium already keeps copies of document.db in trilium-data/backup: one daily, one weekly, one monthly, and one before each database migration, with a Backup now button under Options → Backup. The docs call it a basic scheme and ask for a real copy elsewhere. The data directory page says the one folder holds everything, so the script below stops the container briefly (desktops are offline-first and catch up), archives the folder and the compose file, checks the archive, restarts, and prunes old archives:
sudo install -d -m 700 /var/backups/trilium
sudo tee /usr/local/sbin/trilium-backup.sh >/dev/null <<'EOF'
set -euo pipefail
umask 077
C=/srv/trilium/compose.yaml
F=/var/backups/trilium/trilium-$(date +%F-%H%M).tar.gz
docker compose -f "$C" stop trilium
trap 'docker compose -f "$C" start trilium' EXIT
tar -C /srv/trilium -czf "$F" trilium-data compose.yaml
tar -tzf "$F" >/dev/null
echo "ok $F"
find /var/backups/trilium -name 'trilium-*.tar.gz' -mmin -60 | grep -q . && find /var/backups/trilium -name 'trilium-*.tar.gz' -mtime +14 -delete
EOF
echo '20 3 * * * root bash /usr/local/sbin/trilium-backup.sh > /var/log/trilium-backup.log 2>&1' | sudo tee /etc/cron.d/trilium-backup >/dev/null
sudo bash /usr/local/sbin/trilium-backup.sh
The last line must print ok. Pull the archives to another machine with plain rsync -a, never --delete, and prune on that side. To restore a whole archive, as root:
- Run the script once more so the current state sits in its own verified archive, and test the one you are restoring:
tar -tzf FILE >/dev/null && echo ok. Stop unless it prints ok. docker compose -f /srv/trilium/compose.yaml down. If/srv/trilium/trilium-data.previousexists, stop and deal with it first; otherwisemv /srv/trilium/trilium-data /srv/trilium/trilium-data.previous.tar -C /srv/trilium -xzf FILE trilium-data, thenchown -R 1000:1000 /srv/trilium/trilium-data(the container's node user).docker compose -f /srv/trilium/compose.yaml up -d, sign in and check recent notes. Rollback: down, move the restored folder aside, move.previousback, up. Only when satisfied,rm -rf /srv/trilium/trilium-data.previous.
The -wal and -shm files. An archive taken with the container stopped holds document.db and any -wal from the same moment: restore them together. Restoring one of Trilium's own backup-*.db files is the other case: the docs say to stop Trilium, delete document.db, document.db-wal and document.db-shm, copy the backup in as document.db, make it writable and start again, since, as the docs note, the -wal and -shm files are auto-generated; our reasoning is that a leftover WAL would belong to the old database.
With sync configured, the docs warn that a restore has to be done on every member of the sync cluster; otherwise sync brings the restored copy forward to the newer state.
Upgrades and version matching
The upgrade page says Trilium never updates itself. On Docker you change the tag, then docker compose -f /srv/trilium/compose.yaml pull and up -d. On start the database migrates, after which older releases can no longer open it; the pre-migration backup is your way back. The sync protocol is versioned and every member of a sync cluster must use the same protocol version, so when a release changes it, upgrade the desktops with the server. Read the release notes first: v0.106.1, for example, is a security release that asks internet-facing servers to update promptly.
Licence and limits
Trilium is licensed AGPL-3.0, with the option of any later version, per the README and LICENSE. Read this as our general interpretation, not legal advice: running it unchanged for yourself triggers nothing; if you distribute copies, modified or not, you pass the source on under the same licence; and the AGPL adds a duty for modified versions that people reach over a network: those users are entitled to the source of what they use.
Trilium is single-user: one password, one tree. A team that needs separate accounts wants another tool, such as Joplin Server for per-user note sync or BookStack for a shared wiki.
Questions
Is TriliumNext the same software as Trilium Notes?
Yes. TriliumNext is the community project that the original author handed the Trilium repository to after putting his own version into maintenance mode, so TriliumNext/Trilium is now the main line. Its databases are compatible with zadam's, but sync versions changed after v0.90.4, so mixed old and new installs will not sync.
Which port does the Trilium Docker image listen on?
Port 8080 inside the container, set by TRILIUM_NETWORK_PORT or the port key in config.ini. On a public machine, publish it on 127.0.0.1 and let the reverse proxy take 443.
Can several people share one Trilium server?
Not as separate users. Each server holds a single password and a single note tree, and every device that syncs with it sees all of it. Shared publishing of single notes exists, but per-person accounts do not.
Does the mobile app sync with my server?
The project ships no official mobile app; the server's own mobile web frontend is the supported route. Community apps exist, and the README warns that their sync version must match the server's, so you may need to hold back server upgrades for them.