Why self-host Umami on a VPS
Umami is a lightweight alternative to Google Analytics: open source, cookieless, no data sent to third parties. On a VPS, you retain full control — no SaaS account, no site limit, no shared data. The Docker stack is minimal: a Node.js container for the app and a PostgreSQL container for the database. The whole setup takes under ten minutes on a standard VM with root access, and your data stays on your server.
Compared to Google Analytics 4, Umami doesn't require consent for anonymised statistics in most European jurisdictions. Compared to Matomo, its memory footprint is significantly lower, and its Docker setup is a single docker-compose.yml. For an agency managing ten clients, a single Umami instance with ten sites replaces a per-user or per-domain SaaS subscription. Version 2.14 (July 2026) added native custom events support and a CSV-exportable dashboard — two features long awaited by marketing teams.
The concrete benefits of a self-hosted Umami
- Privacy-compliant: no cookies, no personal data, often no consent banner required.
- Your audience statistics stay on your server, with no sharing to a third party.
- A tracking script of a few kilobytes, with no cookie and no personal data collected.
- Clear, multi-site dashboard from a single instance.
- Fixed cost regardless of the volume of page views.
Hardware and software requirements
Umami runs on any VPS with Docker installed. The recommended minimum is 1 vCPU and 512 MB RAM for personal or SMB use; above 200,000 monthly page views, 1 GB RAM avoids PostgreSQL slowdowns. You will need: Docker Engine (≥ 20.10) and Docker Compose V2 (docker compose, not docker-compose), a domain or subdomain pointing to your VPS, and a reverse proxy (nginx or Caddy) to serve Umami over HTTPS. Debian 12 and Ubuntu 22 are the reference distributions; the commands below assume root or sudo access.
On the server side, open ports 80 and 443 in your firewall (ufw allow 80/tcp && ufw allow 443/tcp). Umami only needs HTTPS exposed externally; the application port (3000 by default) stays internal and is never opened directly. If you're using a ServOrbit VPS, the default security group already allows 80 and 443.
Deploy Umami with Docker and HTTPS
Prepare the VPS and Docker
Over SSH, update the system and install Docker via
curl -fsSL https://get.docker.com | sh. Createmkdir -p /opt/umami && cd /opt/umami.Write the docker-compose.yml
Define two services:
ghcr.io/umami-software/umami:postgresql-latestand apostgresdatabase. Fill inDATABASE_URLand a randomAPP_SECRET.Start the containers
Run
docker compose up -d. Umami listens locally on port 3000; the database is initialised automatically on first start.Configure the reverse proxy
With Caddy:
stats.your-domain.com { reverse_proxy localhost:3000 }. The Let's Encrypt certificate is obtained and renewed automatically.Add your site and the script
Log in (admin/umami by default, change it immediately), add your site and paste the tracking snippet provided into the
<head>of your pages.
Common startup issues
Three root causes account for the vast majority of failures reported in 2026 community guides. Here they are with their diagnosis and fix.
1. Restart loop "Can't reach database server" — DATABASE_URL does not match POSTGRES_PASSWORD
This is the most common copy-paste trap: DATABASE_URL contains the password in plain text inside the connection string, and that password must be exactly the same as the value of POSTGRES_PASSWORD in the db service. If you change one without the other, Umami starts, fails to reach the database, and restarts in a loop — with no clear error message in the Docker logs.
Verify that both variables are consistent:
# in .env or in the compose file
POSTGRES_PASSWORD=strong_password
# in DATABASE_URL, the same password in position 3
DATABASE_URL=postgresql://umami:strong_password@db:5432/umamiIf you use variable interpolation (${POSTGRES_PASSWORD}), make sure Docker Compose is actually loading your .env file.
2. Umami crashes 5 to 10 seconds after startup — depends_on without healthcheck
By default, depends_on: db only waits for the PostgreSQL container to be started, not for it to be ready to accept connections. Umami then tries to connect while PostgreSQL is still initialising its data files, fails, and exits.
Add a healthcheck on the db service and a condition: service_healthy in Umami's depends_on:
services:
db:
image: postgres:15-alpine
healthcheck:
test: ["CMD-SHELL", "pg_isready -U umami"]
interval: 10s
timeout: 5s
retries: 5
umami:
image: ghcr.io/umami-software/umami:postgresql-latest
depends_on:
db:
condition: service_healthyWith this configuration, Umami only starts once PostgreSQL is responding on its socket.
3. 502 error after docker compose pull — published port has changed
After an update (docker compose pull + docker compose up -d), nginx may return a 502 error if the configuration changed between image versions: the internal port may have been remapped or the host binding changed. First check the actual state of the containers:
docker compose psLook at the PORTS column to confirm that the Umami container is publishing on the expected port (3000 by default). If the port has changed, update the proxy_pass (or reverse_proxy) directive in nginx or Caddy to reflect the new port, then reload the service (nginx -s reload or systemctl reload caddy).
4. docker-compose (v1) vs docker compose (v2) — silent failure
Guides published before 2025 often use docker-compose (hyphen), the unmaintained Python client. On a fresh Docker Engine install, this command doesn't exist: the terminal just returns command not found, without saying that v2 is called docker compose (space). Verify with docker compose version — if it responds, you have v2. All examples in this guide use docker compose (v2).
Updating Umami
Back up the database
Before any update:
docker compose exec db pg_dump -U umami umami > umami-backup-$(date +%F).sql. Keep this file outside the Docker volume — it won't survive a volume removal.Pull the latest image
docker compose pull. Umami publishes releases on GitHub (github.com/umami-software/umami/releases) — check release notes for schema migrations or environment variable changes.Restart the stack
docker compose up -d. Umami runs migrations automatically on startup. Check logs withdocker compose logs -f appand wait for theListening on port 3000message.Verify the version in the UI
Settings → About. If the version is unchanged, the image may not have been re-pulled:
docker compose imagesshows the digest of each running image.Check tracking works
Open a tracked site in a private tab and wait 30 seconds — the Realtime page should record the visit. A persistent 0 after a restart usually indicates a tracking script issue on the frontend side.
Umami Cloud vs self-hosted
Umami offers a managed Cloud plan (umami.is/pricing) from $9/month for teams who prefer not to manage infrastructure. Self-hosting on a VPS makes sense if you already have a VM, manage multiple sites, or if your organisation's data policy prohibits sending data to an external SaaS — even GDPR-compliant ones. Both options share the same codebase, features, and export format.
Change the default administrator password on your first login, and back up the PostgreSQL database regularly: it is what holds your entire audience history.
The official documentation
For advanced configuration and options specific to the tool, refer to the official Umami documentation. This guide covers going live on a VPS; the vendor's documentation remains the reference for fine-tuning, major updates and specific use cases.