Skip to content

How to Deploy Umami 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 Umami, the cookieless web analytics app, with Docker Compose and PostgreSQL. Covers generated secrets, the default login you must change, and the one script tag.

Before you start
  • A small VPS: 1 vCPU / 1–2 GB RAM
  • A fresh Ubuntu 24.04 or 26.04 server with root/sudo SSH access
  • A domain (or subdomain) for the analytics dashboard
  • 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 Umami is

Umami is privacy-focused web analytics under the MIT licence. It counts page views, visitors, referrers, devices and locations without cookies. The free build also includes funnels, user journeys, retention, goals, session replays and heatmaps. You add one <script> tag to your site and read the results in Umami's dashboard.

The app is Next.js. PostgreSQL is its only supported database, at v12.14 or newer. The official Docker Compose file runs two containers, the app and Postgres, which makes it one of the lighter analytics stacks to self-host.

If you are comparing options: Plausible vs Umami covers the closest alternative, Matomo vs Umami covers the heavyweight Google Analytics replacement, and Deploy Plausible on a VPS is the matching guide for Plausible.

Server sizing

The catalog lists 1 GB RAM as the minimum. On our test box (GCP e2-standard-2, Ubuntu 26.04, Docker 29.8.1), the idle stack measured about 165 MB of RAM and about 1.8 GB of disk for the images and an empty database.

  • 1 vCPU / 1 GB RAM is enough for a handful of small sites.
  • 2 GB RAM gives PostgreSQL room once you track busier sites or keep long history. Every page view is a row.
  • Disk grows with traffic. Start with 20–40 GB and watch the database volume.

Run it on its own small box, or next to other light services. Analytics traffic comes from your visitors' browsers, so the server has to be reachable from the public internet over HTTPS.

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, start with Docker & Compose on Ubuntu.

sudo ufw allow OpenSSH
sudo ufw allow 80
sudo ufw allow 443
sudo ufw --force enable
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 Umami (Docker Compose)

The upstream compose file ships with placeholder secrets: APP_SECRET, TWO_FACTOR_ENCRYPTION_KEY and a database password of umami. Below is the same file with those values moved into a generated .env, and the port bound to loopback so the reverse proxy is the only public way in.

Two of the secrets have rules in Umami's docs:

  • APP_SECRET secures authentication tokens. Each installation should have its own unique value.
  • TWO_FACTOR_ENCRYPTION_KEY must be a 64-character hex string, and openssl rand -hex 32 produces exactly that. Without it, nobody can enable 2FA. Changing or losing it later makes the stored 2FA secrets unreadable.
mkdir -p ~/umami && cd ~/umami
[ -f .env ] || printf 'POSTGRES_PASSWORD=%s\nAPP_SECRET=%s\nTWO_FACTOR_ENCRYPTION_KEY=%s\n' \
  "$(openssl rand -hex 24)" "$(openssl rand -hex 32)" "$(openssl rand -hex 32)" > .env
chmod 600 .env
cd ~/umami
cat > docker-compose.yml <<'YAML'
services:
  umami:
    image: ghcr.io/umami-software/umami:latest
    ports:
      # Loopback only: Caddy is the only way in from outside.
      - "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:
YAML

Start it. On first boot Umami creates its tables and a default admin account:

cd ~/umami
docker compose up -d
timeout 300 bash -c 'until curl -fsS -o /dev/null http://127.0.0.1:3000/api/heartbeat; do sleep 3; done'
docker compose ps

HTTPS + domain

Point an A record such as stats.example.com at the server, wait for it to resolve, then put Caddy in front of the loopback port, following Automatic HTTPS with Caddy:

stats.example.com {
    reverse_proxy 127.0.0.1:3000
}

HTTPS matters more here than for most apps. Your tracked sites are served over HTTPS, and browsers block a tracker script loaded over plain HTTP from an HTTPS page.

If Umami sits behind a proxy or CDN that puts the visitor's address in a non-standard header, set CLIENT_IP_HEADER to that header name. Otherwise every visitor's location resolves to the proxy's location. Umami's docs have a separate page for Cloudflare's headers.

First login: change the default password

Open https://stats.example.com. Umami's docs give the default credentials as username admin, password umami, with a warning to change the password immediately after the first login. Do that before anything else. Until you do, anyone who finds the URL can log in.

Then:

  1. Turn on two-factor authentication: click your profile in the side nav, then Settings, then Security. This works because you set TWO_FACTOR_ENCRYPTION_KEY. Admins can also require 2FA for everyone.
  2. Create a separate login for each person instead of sharing admin. Umami has users and teams for this.

Add a site and install the tracker

In the dashboard, go to Websites → Add website. Enter a name, and the site's real domain in the Domain field. Umami uses that domain to keep your own site out of the referrer list.

Open the website's Edit screen and copy the snippet from the Tracking code section into the <head> of every page. It looks like this:

<script defer src="https://stats.example.com/script.js" data-website-id="…"></script>

Visit your site and the visit shows up in the dashboard right away. On a Next.js site, add the tag with next/script rather than a plain <script>. Single-page apps need no extra setup, because Umami tracks client-side navigation automatically.

Ad blockers. Some block script.js on analytics hosts. Umami supports renaming the script with the TRACKER_SCRIPT_NAME environment variable. Add it to the compose environment block and recreate the container.

Backups

Everything is in PostgreSQL, plus the .env that holds its password and the app secrets. Dump the database from inside its container:

cd ~/umami
mkdir -p ~/backups/umami
docker compose exec -T db pg_dump -U umami umami | gzip > ~/backups/umami/umami-$(date +%F).sql.gz
cp .env ~/backups/umami/env-$(date +%F)
ls -lh ~/backups/umami

Copy the folder off the box, and keep the .env copy somewhere private. Losing TWO_FACTOR_ENCRYPTION_KEY forces every 2FA user to enrol again. Restoring means starting a fresh db container and piping the dump into psql -U umami umami.

Upgrades

Upstream's Docker instructions are to pull the new image and restart:

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

Back up first. Umami runs schema migrations when it starts. After a major upgrade, the docs recommend running ANALYZE; in PostgreSQL to refresh the query planner's statistics, because stale statistics can make the dashboard slow on large instances. It is safe on a live database:

cd ~/umami
docker compose exec -T db psql -U umami -d umami -c 'ANALYZE;'

Troubleshooting

No data appears. Open your site with the browser's developer tools on the Network tab and look for the request to your Umami host. If the script fails to load, check that the src URL is HTTPS with a valid certificate, and try from a browser without an ad blocker.

Every visitor shows the same country. Umami is reading the proxy's IP address. Set CLIENT_IP_HEADER to the header your proxy or CDN uses.

The app restarts in a loop right after install. Read docker compose logs umami. A DATABASE_URL whose password doesn't match the one Postgres was initialised with is the usual cause. That happens when .env is regenerated after the first start. The [ -f .env ] || guard above prevents it.

Two-factor can't be enabled. TWO_FACTOR_ENCRYPTION_KEY is missing or is not 64 hex characters. Fix it in .env and run docker compose up -d.

Verification + next steps

You're done when https://stats.example.com loads over a valid certificate, the admin/umami login no longer works, your own account has 2FA on, a test visit to a tracked site appears in the dashboard, and an off-box pg_dump has been restored at least once.

From there: add goals and funnels for the pages that matter, and give each site's owner their own team. If you outgrow Umami's feature set, Matomo vs Umami and Deploy Matomo on a VPS cover the heavier option.

Next steps

How to self-host Umami →More self-hosted web analytics 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 Trilium on a VPS →How to Deploy Twenty CRM 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.