Skip to content

How to Deploy Trilium on a VPS

Updated Sep 2026

verified on Ubuntu 26.04 · Sep 2026
We earn commissions when you shop through the links below. Full disclosure →

Self-host the Trilium Notes server on a small VPS — a pinned Docker image, one data directory, HTTPS through Caddy, the trusted-proxy setting, and backups beyond the built-in ones.

Before you start
  • A small VPS — 1 vCPU / 512 MB–1 GB RAM is plenty
  • A fresh Ubuntu 24.04 or 26.04 server with root/sudo SSH access
  • A domain or subdomain you can point at the server
  • Docker Engine + Compose installed (see the base guide below)
Need a box for this guide? Kamatera's free tier lets you spin one up now.Start free on Kamatera → (opens in new tab)

What Trilium is

Trilium Notes is a hierarchical note-taking app for building a personal knowledge base, maintained today by the TriliumNext project under the AGPL-3.0 licence. Notes live in a tree, a single note can be cloned into several places in that tree, individual notes can be encrypted, and there are code notes and a scripting layer for people who want to automate their notes.

There are two ways to run it: the desktop app on its own, or a server that serves the same interface in a browser and acts as the sync server for the desktop apps. This guide sets up the server. It is a Node.js application with an SQLite database, so it is one container and one data directory — no separate database service.

It's a personal tool first. If you need a team wiki with permissions and co-editing, look at Docmost or BookStack instead.

Server sizing

Trilium is light. The catalog lists a 512 MB RAM floor, and on our install-verification run (GCP e2-standard-2, Ubuntu 26.04) the server idled at about 61 MB of RAM and used roughly 490 MB of disk for the image and data.

  • 512 MB–1 GB RAM / 1 vCPU — plenty for one person or a household.
  • Disk grows with attachments and images you paste into notes, plus the automatic backup copies of the database (see Backups). 10–20 GB is comfortable.

The official image is published for AMD64 and ARM64, so an ARM VPS works too.

Prepare the server

This guide assumes Docker Engine and the Compose plugin are installed, along with a non-root user and a ufw firewall. If not, work through Docker & Compose on Ubuntu first.

Only SSH, 80 and 443 should be reachable from outside:

sudo ufw status verbose

Upstream's compose file carries a warning worth repeating: Docker's published ports bypass ufw. A port mapped as 8080:8080 is reachable from the internet even if ufw denies 8080. That's why the compose file below binds Trilium to 127.0.0.1 only.

Where to host itaffiliate disclosure
Hetzner Cloudrun it on
2 vCPU · 4 GB RAM · 80 GB SSD · $23.59/mo
Get Hetzner Cloud (opens in new tab)
Kamaterafree trial
1 vCPU · 1 GB RAM · 20 GB SSD · $4.00/mo
Start free on Kamatera → (opens in new tab)
DigitalOceanalso works on
1 vCPU · 1 GB RAM · 25 GB SSD · $6.00/mo
Deploy on DigitalOcean → (opens in new tab)

Paid link — we earn a commission if you shop through it.

Install Trilium (Docker Compose)

Create the project directory and a data directory next to it:

mkdir -p ~/trilium/trilium-data && cd ~/trilium

Upstream's Docker docs warn against the latest tag, because it can move your instance to a new version on its own and disrupt sync with your desktop apps. Pin a version instead — v0.106.0 was the current release when this guide was verified — and change it deliberately when you upgrade:

cat > docker-compose.yml <<'YAML'
services:
  trilium:
    image: triliumnext/trilium:v0.106.0
    container_name: trilium
    restart: unless-stopped
    environment:
      - TRILIUM_DATA_DIR=/home/node/trilium-data
      # Behind Caddy on the same host: trust X-Forwarded-For from private
      # addresses (Docker's bridge network is one).
      - TRILIUM_NETWORK_TRUSTEDREVERSEPROXY=uniquelocal
    ports:
      # Loopback only: Caddy is the only way in from outside.
      - "127.0.0.1:8080:8080"
    volumes:
      - ./trilium-data:/home/node/trilium-data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
YAML

The trilium-data directory holds everything: the document.db SQLite database, config.ini, logs and the automatic backups. TRILIUM_NETWORK_TRUSTEDREVERSEPROXY is the environment-variable form of the [Network] trustedReverseProxy setting; upstream asks you to set it behind a reverse proxy so authentication and rate limiting see real client addresses rather than the proxy's. The docs accept true or an address list; use an address shortcut such as uniquelocal here. On our verification run, v0.106.0 refused to start with the environment variable set to true ("invalid IP address: true"), so don't copy true from config.ini examples into the environment.

Start it and wait for the server to answer:

docker compose up -d
for i in $(seq 1 30); do
  code=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8080/)
  case "$code" in 200|302) break ;; esac
  sleep 5
done
docker compose ps
echo "Trilium answered HTTP $code"
case "$code" in 200|302) true ;; *) false ;; esac

A 200 or a 302 redirect to the setup page means it's up.

HTTPS + domain

Point an A record for notes.example.com at the server, wait for it to resolve, then terminate TLS in front of 127.0.0.1:8080 with Automatic HTTPS with Caddy:

notes.example.com {
    reverse_proxy 127.0.0.1:8080
}

Trilium's interface uses WebSockets for live updates; Caddy proxies them without extra configuration. If you run Caddy as a container, proxy to trilium:8080 on a shared network and remove the host port publish.

First run

Open https://notes.example.com. A fresh server shows a setup screen:

  • Choose to start a new document if this server will be your primary copy.
  • Choose to sync from a desktop app if you already have notes in Trilium desktop and want this server to receive them.
  • A restore from backup option is also offered on a new instance.

Then set the password. There is one password per server — Trilium is a single-user application — so make it long and unique. From then on the login page protects the instance. Two further options are in the upstream docs:

  • TOTP multi-factor authentication and OpenID Connect sign-in are supported; both are worth enabling on an internet-facing server.
  • noAuthentication=true turns the login off entirely. Upstream intends it for localhost-only instances or when something in front already authenticates users. Don't use it on a public server.

To connect a desktop app, choose the sync option on its first start and enter https://notes.example.com as the server address, then the server password.

Backups

Trilium already keeps some backups on its own: it copies the database once a day, once a week, once a month, and before every database migration into trilium-data/backup/. That protects against a bad upgrade or a mistaken edit, but those copies sit on the same disk as the original, so they don't survive losing the server.

For an off-box copy, stop the container briefly so the SQLite file is consistent, archive the whole data directory, and start it again:

cd ~/trilium
docker compose stop
sudo tar czf trilium-backup-$(date +%F).tar.gz trilium-data
docker compose start
ls -lh trilium-backup-*.tar.gz

Copy the archive off the server. To restore by hand, upstream's procedure is: stop Trilium, delete document.db plus the document.db-wal and document.db-shm files, put your backup in place as document.db, and start it again. If you sync with desktop apps, restore on every member of the sync cluster, otherwise the older data can be synced over newer notes.

A synced desktop app is also a copy of your notes, but a sync client replicates deletions too — it isn't a substitute for the archive.

Upgrades

Edit the image tag to the new version, then:

cd ~/trilium
docker compose pull
docker compose up -d

Trilium backs up the database before running a migration. Keep the server and your desktop apps on the same version: sync between different versions can fail, which is the reason upstream steers you away from latest. Take an archive backup before a version jump and read the release notes.

Troubleshooting

The container starts but the page never loads. Check docker compose logs trilium. Fatal error ... invalid IP address: true means the trusted-proxy variable is set to true; use an address value instead. A permissions error on the data directory is the common cause; upstream says the container needs root to set itself up and supports USER_UID / USER_GID environment variables to change the owner of the saved data. The --user directive is not supported.

Desktop sync fails after an upgrade. The server and desktop versions don't match. Upgrade both to the same release.

Login attempts from everyone are rate-limited together. Trilium is seeing the proxy's address for every client. Make sure TRILIUM_NETWORK_TRUSTEDREVERSEPROXY is set (to an address shortcut such as uniquelocal, as above) and the container was recreated after adding it.

The data directory is on a network share and the database locks. Upstream notes that an SMB/CIFS share needs the nobrl and noperm mount options. A local disk avoids the problem.

Verification + next steps

You're done when https://notes.example.com loads with a valid certificate, the login page asks for your password, a desktop app can sync against the server if you use one, and a trilium-backup-*.tar.gz sits somewhere off the box.

Next: turn on TOTP, schedule the archive with cron, and decide how long you want to keep the built-in backups. Considering a team tool instead? The Wiki.js guide and the Outline guide cover shared wikis. For host picks, see Best VPS for Self-Hosting.

Next steps

How to self-host Trilium Notes →More self-hosted notes tools →Automatic HTTPS with Caddy →Run Claude Code with Ollama on Your Own VPS →Deploy Coolify on a VPS →How to Deploy Actual Budget on a VPS →How to Deploy AnythingLLM on a VPS →How to Deploy Appwrite on a VPS →How to Deploy Audiobookshelf on a VPS →How to Deploy Authelia on a VPS →How to Deploy authentik on a VPS →How to Deploy Baserow on a VPS →How to Deploy Beszel on a VPS →How to Deploy Bitwarden on a VPS →How to Deploy BookStack on a VPS →How to Deploy CapRover on a VPS →How to Deploy Checkmate on a VPS →How to Deploy Directus on a VPS →How to Deploy docker-mailserver on a VPS →How to Deploy Docmost on a VPS →How to Deploy Dokku on a VPS →How to Deploy Dokploy on a VPS →How to Deploy Firefly III on a VPS →How to Deploy Forgejo on a VPS →How to Deploy Gatus on a VPS →How to Deploy Ghostfolio on a VPS →How to Deploy Gitea on a VPS →How to Deploy GitLab on a VPS →How to Deploy GlitchTip on a VPS →How to Deploy Grafana on a VPS →How to Deploy Graylog on a VPS →How to Deploy Headscale on a VPS →How to Deploy Healthchecks on a VPS →How to Deploy Home Assistant on a VPS →How to Deploy Immich on a VPS →How to Deploy Jan on a VPS →How to Deploy Jellyfin on a VPS →How to Deploy Karakeep on a VPS →How to Deploy Keycloak on a VPS →How to Deploy Leantime on a VPS →How to Deploy LibreChat on a VPS →How to Deploy Linkwarden on a VPS →How to Deploy LocalAI on a VPS →How to Deploy Mailcow on a VPS →How to Deploy Mailu on a VPS →How to Deploy Matomo on a VPS →How to Deploy Mattermost on a VPS →How to Deploy Meilisearch on a VPS →How to Deploy Memos on a VPS →How to Deploy n8n on a VPS →How to Deploy Navidrome on a VPS →How to Deploy NetBird on a VPS →How to Deploy Netdata on a VPS →How to Deploy Nextcloud on a VPS →How to Deploy Next.js to a VPS →How to Deploy Nginx Proxy Manager on a VPS →How to Deploy NocoDB on a VPS →How to Deploy ntfy on a VPS →How to Deploy Ollama on a VPS →How to Deploy Open WebUI on a VPS →How to Deploy OpenHands on a VPS →How to Deploy OpenObserve on a VPS →How to Deploy OpenProject on a VPS →How to Deploy Outline on a VPS →How to Deploy Pangolin on a VPS →How to Deploy Paperless-ngx on a VPS →How to Deploy Passbolt on a VPS →How to Deploy Plane on a VPS →How to Deploy Plausible Analytics on a VPS →How to Deploy Pocket ID on a VPS →How to Deploy PocketBase on a VPS →How to Deploy Prometheus on a VPS →How to Deploy Psono on a VPS →How to Deploy Radarr on a VPS →How to Deploy Rocket.Chat on a VPS →How to Deploy SigNoz on a VPS →How to Deploy Sonarr on a VPS →How to Deploy Stalwart on a VPS →How to Deploy Stirling-PDF on a VPS →How to Deploy Supabase on a VPS →How to Deploy Synapse on a VPS →How to Deploy Taiga on a VPS →How to Deploy TeamPass on a VPS →How to Deploy Tinyauth on a VPS →How to Deploy Traefik on a VPS →How to Deploy Twenty CRM on a VPS →How to Deploy Umami on a VPS →How to Deploy Uptime Kuma on a VPS →How to Deploy Vaultwarden on a VPS →How to Deploy Vikunja on a VPS →How to Deploy wg-easy on a VPS →How to Deploy Wiki.js on a VPS →How to Deploy Zabbix on a VPS →How to Deploy Zitadel on a VPS →How to Deploy Zulip on a VPS →Docker & Compose on Ubuntu 26.04 →Building AI Workflows with n8n →Install Open WebUI with Ollama →Adding AI-Powered Insights to Plausible Analytics →Building AI-Powered Apps with Supabase and pgvector →

We use analytics cookies (Google Analytics, PostHog) to see which guides are useful. No ad networks, no cross-site tracking. See our privacy policy.