Skip to content

TutorialsAnalytics

How to install Umami analytics with Docker Compose and HTTPS

Self-host Umami web analytics with Docker Compose and PostgreSQL, change the default login, serve it over HTTPS with Caddy, add your sites and back it up.

  • Beginner
  • 30 min read
  • Updated

Tested on: Ubuntu 24.04 LTS, Ubuntu 26.04 LTS, Debian 12, Debian 13

This guide is not available in your language yet, so it is shown in English.

On this page
  1. Prerequisites
  2. Step 1 — Create the project folder and secrets
  3. Step 2 — Write the Compose file
  4. Step 3 — Start Umami
  5. Step 4 — Replace the default login before going public
  6. Step 5 — Serve Umami over HTTPS with Caddy
  7. Step 6 — Add a website and the tracking script
  8. Step 7 — Rename the tracker for ad blockers (optional)
  9. Back up and restore
  10. Update Umami
  11. Troubleshooting
  12. The umami container keeps restarting or stays unhealthy
  13. Visits from some browsers never show up
  14. All visitors appear to come from the same place
  15. Two-factor authentication cannot be enabled
  16. Caddy returns 502 Bad Gateway
  17. Next steps

Umami is an open-source, privacy-focused web analytics tool. It shows page views, visitors, referrers, devices, locations and custom events on a clean dashboard, and it is light enough to track several websites from one small server. Because you host it yourself, the collected data stays in your own PostgreSQL database.

This guide installs the current major version, Umami 3, with Docker Compose and PostgreSQL, based on the Compose file in Umami's repository. Umami listens only on 127.0.0.1:3000; you replace the default admin password through an SSH tunnel before anything is public, then publish the dashboard over HTTPS with Caddy, add a website and its tracking script, and set up backups and updates.

Prerequisites

Umami's documentation names PostgreSQL 12.14 as the minimum database version; the Compose file below runs PostgreSQL 15 in a container. The project does not publish minimum CPU or memory requirements, so the figures below are a conservative starting point, not an official or benchmarked number:

ResourceMinimum (official)Suggested starting point
CPUNot published1 vCPU
MemoryNot published1 GB RAM
DiskNot published10 GB, more for busy sites with long retention
DatabasePostgreSQL 12.14 or newerPostgreSQL 15 from the Compose file

Step 1 — Create the project folder and secrets

Create a folder for Umami owned by your user and generate three secrets: the database password, APP_SECRET, which Umami uses to secure authentication tokens, and TWO_FACTOR_ENCRYPTION_KEY, a 64-character hex key that encrypts two-factor secrets and must be set before any user can enable 2FA:

Bash
sudo mkdir -p /opt/umami && sudo chown $USER:$USER /opt/umami
cd /opt/umami
echo "POSTGRES_PASSWORD=$(openssl rand -hex 24)" > .env
echo "APP_SECRET=$(openssl rand -hex 32)" >> .env
echo "TWO_FACTOR_ENCRYPTION_KEY=$(openssl rand -hex 32)" >> .env
chmod 600 .env
cat .env

You should see three lines with long random values. Hex values contain only letters and digits, so the password can be used inside the database URL without escaping. Keep APP_SECRET and TWO_FACTOR_ENCRYPTION_KEY unchanged once Umami is in use.

Step 2 — Write the Compose file

Create docker-compose.yml:

Bash
nano /opt/umami/docker-compose.yml

The file below follows docker-compose.yml in Umami's repository, with two changes: the secrets come from .env instead of placeholder values, and port 3000 is published on 127.0.0.1 only:

YAML
services:
  umami:
    image: ghcr.io/umami-software/umami:latest
    ports:
      - "127.0.0.1:3000:3000"
    environment:
      DATABASE_URL: postgresql://umami:${POSTGRES_PASSWORD}@db:5432/umami
      APP_SECRET: ${APP_SECRET}
      TWO_FACTOR_ENCRYPTION_KEY: ${TWO_FACTOR_ENCRYPTION_KEY}
    depends_on:
      db:
        condition: service_healthy
    init: true
    restart: always
    healthcheck:
      test: ["CMD-SHELL", "curl http://localhost:3000/api/heartbeat"]
      interval: 5s
      timeout: 5s
      retries: 5
  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: umami
      POSTGRES_USER: umami
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - umami-db-data:/var/lib/postgresql/data
    restart: always
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
      interval: 5s
      timeout: 5s
      retries: 5

volumes:
  umami-db-data:

The image ghcr.io/umami-software/umami:latest is the one the repository uses; Umami's docs also publish it as docker.umami.is/umami-software/umami:latest. Check that Compose reads the file and the secrets:

Bash
cd /opt/umami
docker compose config --quiet && echo "compose file OK"

Step 3 — Start Umami

Bash
docker compose up -d
docker compose ps
docker compose logs umami --tail 30

Both containers should show as running and, after a few seconds, healthy. On the first start Umami creates its tables and the default administrator account. Check the heartbeat endpoint that the health check uses:

Bash
curl -i http://127.0.0.1:3000/api/heartbeat

You should get HTTP/1.1 200 OK. If the umami container keeps restarting, read the logs; a wrong database password is the most common cause (see Troubleshooting).

Step 4 — Replace the default login before going public

Every new Umami installation starts with the username admin and the password umami. Change it before the dashboard is reachable from the internet. From your own computer, open an SSH tunnel to the server:

Bash
ssh -L 3000:127.0.0.1:3000 your-user@203.0.113.10

Keep that session open and browse to http://localhost:3000 on your computer. Log in as admin with password umami, click the profile button in the side navigation, open Settings → Profile and click Change password. Use a long, unique password. On the Security tab you can also turn on two-factor authentication, which works because TWO_FACTOR_ENCRYPTION_KEY is set. Close the tunnel when you are done.

Step 5 — Serve Umami over HTTPS with Caddy

Add a site block to /etc/caddy/Caddyfile:

Caddyfile
analytics.example.com {
    reverse_proxy 127.0.0.1:3000
}

Reload Caddy, check the login page, and make sure the firewall allows only SSH and web traffic:

Bash
sudo systemctl reload caddy
curl -I https://analytics.example.com/login
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose

Caddy passes the visitor's address to Umami in the X-Forwarded-For header, which Umami needs for unique visitors and locations. If you put another proxy or CDN in front that uses a non-standard header, set CLIENT_IP_HEADER to that header name. Nginx (Nginx with Certbot) and Traefik (Traefik) work as alternatives to Caddy.

Step 6 — Add a website and the tracking script

Sign in at https://analytics.example.com, select Websites in the sidebar and click Add website. Enter a Name and the site's real Domain (Umami uses it to exclude your own site from the referrer list), then click Save.

Open the new website, click Edit and find the Tracking code section. Copy the snippet and paste it into the head section of every page you want to track. It looks similar to this, with your own website ID:

Text
<script defer src="https://analytics.example.com/script.js" data-website-id="your-website-id"></script>

Open your site in a browser, then check the website's dashboard in Umami: the visit should appear within seconds. The script accepts optional data- attributes, for example data-domains to track only listed hostnames, data-exclude-search to drop query strings, and data-do-not-track to honour the browser's Do Not Track setting.

Step 7 — Rename the tracker for ad blockers (optional)

Many ad blockers block requests to script.js and /api/send on known analytics hosts. On self-hosted instances you can rename both with environment variables. Add them to the environment: section of the umami service in docker-compose.yml:

YAML
      TRACKER_SCRIPT_NAME: insights.js
      COLLECT_API_ENDPOINT: /api/insights

Apply the change and update the src of your tracking code to https://analytics.example.com/insights.js. The tracker sends its data to the new endpoint automatically:

Bash
docker compose up -d
curl -I https://analytics.example.com/insights.js

Other useful runtime variables include IGNORE_IP (IP addresses or ranges to exclude, such as your office), DISABLE_TELEMETRY and DISABLE_UPDATES.

Back up and restore

Everything Umami stores, including users, websites and events, is in PostgreSQL. Dump it regularly and keep the Compose file and .env with it:

Bash
sudo mkdir -p /opt/backups && sudo chown $USER:$USER /opt/backups && chmod 700 /opt/backups
cd /opt/umami
docker compose exec -T db pg_dump -U umami -Fc umami > /opt/backups/umami-db-$(date +%F).dump
tar czf /opt/backups/umami-config-$(date +%F).tar.gz -C /opt/umami docker-compose.yml .env
ls -lh /opt/backups

Copy the files off the server and schedule the commands with cron once a test restore has worked. To restore, put docker-compose.yml and .env back in /opt/umami, then load the dump into an empty database:

Bash
cd /opt/umami
docker compose stop umami
docker compose up -d --wait db
docker compose exec -T db dropdb -U umami --if-exists umami
docker compose exec -T db createdb -U umami umami
docker compose exec -T db pg_restore -U umami -d umami < /opt/backups/umami-db-2026-10-09.dump
docker compose up -d

Use the date of your own backup file. Sign in and confirm your websites and statistics are back.

Update Umami

Read the release notes and take a database backup first. Then follow Umami's documented update procedure for Docker Compose:

Bash
cd /opt/umami
docker compose pull
docker compose up --force-recreate -d
docker compose logs umami --tail 30

Database migrations run automatically when the new container starts. After a major upgrade, Umami recommends running ANALYZE so PostgreSQL refreshes its query planner statistics; without it, dashboard queries on large instances can be slow:

Bash
docker compose exec db psql -U umami -d umami -c 'ANALYZE;'

If you pinned a version tag, change it in docker-compose.yml before you pull. Do not change the PostgreSQL major version in the image: line without a dump and restore, because a newer PostgreSQL major version cannot open the old data directory directly.

Troubleshooting

The umami container keeps restarting or stays unhealthy

Read docker compose logs umami --tail 50. If it reports that authentication failed for user umami, the database volume was created with a different password than the one in .env. PostgreSQL only sets the password on the first start; on a new installation without data, remove the volume with docker compose down -v and start again.

Visits from some browsers never show up

An ad blocker or privacy extension is blocking the script or the collection request. Open the browser's developer tools on your site and look for blocked requests to script.js or /api/send. Rename both as shown in Step 7. Also check that data-domains, if you set it, includes the exact hostname, with or without www.

All visitors appear to come from the same place

Umami sees the proxy's address instead of the visitor's. Make sure requests reach Umami only through Caddy on 127.0.0.1:3000, and if a CDN or another proxy sits in front, set CLIENT_IP_HEADER to the header it uses for the client address.

Two-factor authentication cannot be enabled

TWO_FACTOR_ENCRYPTION_KEY is missing or empty. Check that .env contains a 64-character hex value and that docker-compose.yml passes it to the umami service, then run docker compose up -d.

Caddy returns 502 Bad Gateway

Umami is not answering on 127.0.0.1:3000. Run docker compose ps in /opt/umami; the umami service waits for the database health check, so a failing db container also keeps it down.

Next steps

Frequently asked questions

Does Umami still support MySQL?

No. Umami 3 supports only PostgreSQL. If you run Umami 2 on MySQL, follow Umami's MySQL to PostgreSQL migration guide before you upgrade to version 3.

What is the default Umami login?

A new installation creates the user admin with the password umami. Change it immediately after the first login under Settings, Profile, Change password. This guide does that over an SSH tunnel before the site is public.

Why are some visits missing from Umami?

Ad blockers and privacy extensions often block analytics scripts by name or path. Umami lets self-hosted instances rename the tracker script with TRACKER_SCRIPT_NAME and the collection endpoint with COLLECT_API_ENDPOINT, which reduces but does not remove this effect.

What should I back up?

All Umami data, including websites, users and collected events, lives in PostgreSQL. Back up the database with pg_dump and keep the Compose file and the .env file with your secrets.

Which HyperDC servers can run Umami?

Umami runs in Docker on any HyperDC Linux VPS, VDS or dedicated server with root access and a supported Ubuntu or Debian release. One server can host analytics for many websites.

Sources

Passwort generieren

Please confirm