# 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.

Difficulty: Beginner\
Tested on: Ubuntu 24.04 LTS, Ubuntu 26.04 LTS, Debian 12, Debian 13

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.

> **Note**
>
> Umami 3 dropped MySQL and supports only PostgreSQL. If you are moving an existing Umami 2 installation that uses MySQL, migrate the data with Umami's MySQL to PostgreSQL guide first.

## Prerequisites

- A server running **Ubuntu 24.04 LTS**, **Ubuntu 26.04 LTS**, **Debian 12** or **Debian 13**.
- A non-root user with `sudo` rights and SSH key login. See [Secure a new Linux server](/guides/secure-a-new-linux-server) and [Set up SSH keys](/guides/ssh-keys).
- Docker Engine with the Compose plugin: [Install Docker on Ubuntu](/guides/install-docker-ubuntu) or [Install Docker on Debian](/guides/install-docker-debian).
- Caddy installed on the host as described in [Caddy as a reverse proxy](/guides/caddy-reverse-proxy).
- A domain name such as `analytics.example.com` with an A record (and AAAA record if you use IPv6) pointing at your server.

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:

| Resource | Minimum (official) | Suggested starting point |
|---|---|---|
| CPU | Not published | 1 vCPU |
| Memory | Not published | 1 GB RAM |
| Disk | Not published | 10 GB, more for busy sites with long retention |
| Database | PostgreSQL 12.14 or newer | PostgreSQL 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"
```

> **Tip**
>
> To control when you upgrade, pin a version instead of `latest`. The GitHub container registry page for Umami lists plain version tags such as `3.4.0`; use the newest one listed there, for example `ghcr.io/umami-software/umami:3.4.0`.

## 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](/guides/nginx-reverse-proxy-certbot)) and Traefik ([Traefik](/guides/traefik-reverse-proxy)) 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.

> **Note**
>
> Some website builders and tag managers strip `data-` attributes from script tags. Umami's documentation describes using a custom HTML tag that creates the script element in JavaScript for Google Tag Manager.

## 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:

> **Warning**
>
> These commands delete the current Umami database before loading the backup. Take a fresh dump first if the data on this server matters.

```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](https://github.com/umami-software/umami/releases) 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

- Compare with a full-featured analytics suite: [How to install Matomo](/guides/install-matomo).
- Try another lightweight option: [Plausible Community Edition](/guides/install-plausible-ce).
- Host more apps behind the same proxy with [Caddy as a reverse proxy](/guides/caddy-reverse-proxy).
- See servers for self-hosted analytics on the [web analytics hosting](/web-analytics-hosting) page.
- Explore events, reports and the API in the [Umami documentation](https://docs.umami.is/docs).

## 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.

---

Source: <https://hyperdc.com/guides/tutorials/install-umami>\
Updated: 2026-10-09
