When you self-host Umami on a managed VPS, the analytics of your websites stay on a machine of your own, which our teams keep up to date and monitor day and night. Umami is free software under the MIT license that measures traffic without cookies. This guide follows its official documentation: installing with Docker Compose and PostgreSQL, HTTPS, the first login, the tracking script, what the project says about personal data, useful settings, backups and updates.
By Simon Vasconcelos Lee
Published on 15 November 202612 minute read
APP_SECRET and TWO_FACTOR_ENCRYPTION_KEY at random, serve Umami over HTTPS on its own subdomain, then change the admin account’s password right away.<head> of your pages: the tracker does not use cookies.pg_dump, keep the .env file somewhere safe, and update with docker compose pull.Umami is an open-source web and product analytics platform. Its documentation describes a tool that brings traffic, campaigns, visitor behavior, conversions, retention and revenue together, from the first visit to the outcomes that matter to your business. It is written in TypeScript on Next.js, and its Docker image runs under an unprivileged account.
One instance tracks several websites: each one gets its own ID and tracking script, and your colleagues find their way around through accounts and teams.
Umami is published under the MIT license, in the name of Umami Software, Inc. It grants, free of charge, the right to use, copy, modify, merge, publish, distribute, sublicense and sell copies of the software. You can therefore run it for your business and for your clients’ websites, with no license to buy.
Its only condition: the copyright notice and the permission notice go with all copies or substantial portions of the software. Like any MIT license, it provides the software “as is”, without warranty.
The documentation presents Umami as analytics without cookies, without cross-site tracking and without the automatic collection of personal data. Self-hosted, it keeps your analytics on your own server: you decide where they live and how long they stay.
| Question | What the project says | Source |
|---|---|---|
| Cookies | The web tracker does not use cookies; whether your site needs a cookie notice depends on the other services you use and the requirements that apply | FAQ |
| Data collected | Pageviews, referrer URLs, browsers, operating systems, device types and approximate location | FAQ |
| IP address | The sessions table has no column for the IP address; a session ID is a hash of the website, the address, the browser and a salt that rotates every month by default | Schema and code, version 3.4.1 |
| Retention | Self-hosted, data is kept until you delete it | FAQ |
| GDPR | Umami is designed with privacy in mind, and compliance depends on how you configure and use it | FAQ |
| Data you send | A visitor ID or event properties you choose to send are your responsibility | Introduction |
Three tracking script settings go further, if you wish: data-exclude-search removes search parameters from the recorded URLs, data-exclude-hash removes the hash, and data-do-not-track turns tracking off for browsers that expose the Do Not Track preference. The documentation notes that this last setting is not, on its own, a complete consent management solution.
If you identify logged-in visitors with data-distinct-id or umami.identify(), the documentation recommends an internal user ID or a hash rather than an email address: the value can be read in the page’s HTML.
The repository ships a docker-compose.yml that starts Umami and its PostgreSQL database. Since version 3, PostgreSQL is the only supported database, version 12.14 at minimum, and the documentation recommends setting it to the UTC timezone. On a server with Docker installed, create a folder and start with its secrets, generated at random with openssl, as the documentation suggests:
cat > .env <<EOF
POSTGRES_PASSWORD=$(openssl rand -hex 24)
APP_SECRET=$(openssl rand -hex 32)
TWO_FACTOR_ENCRYPTION_KEY=$(openssl rand -hex 32)
EOF
chmod 600 .env
Then place this compose.yaml next to it. It follows the project’s file at version 3.4.1, with two changes: the secrets come from the .env file, and the port is published on 127.0.0.1 only.
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:
docker compose up -d
curl -s http://127.0.0.1:3000/api/heartbeat
DATABASE_URL: the only required variable, the connection string for the database. The hexadecimal password goes in as it is; a password with special characters would need percent-encoding.APP_SECRET: a random string that secures authentication tokens, unique to each installation. In the code of version 3.4.1, it also goes into the computation of session IDs: keep it stable.TWO_FACTOR_ENCRYPTION_KEY: 64 hexadecimal characters that encrypt two-factor authentication secrets. Without it, nobody can enable two-factor authentication.admin account. The /api/heartbeat probe answers {"ok":true} once Umami is ready.latest tag follows the latest published release. The project’s registry also publishes version tags, such as 3.4.1, to pin the one you run.The installation documentation gives no figure for memory or CPU. The size depends on the traffic of the tracked websites and on the volume of data you keep. On a managed VPS, tell us about your usage and we recommend the right size.
Umami listens over HTTP on port 3000, and the documentation suggests serving it behind your web server, through a reverse proxy. Give it its own subdomain, such as stats.example.com, pointed at the server with A and AAAA records. Serving Umami under a path goes through BASE_PATH, a variable read when the application is built: with the published image, the subdomain is the direct route.
The proxy must also pass on the visitor’s address. Umami reads it from the headers it receives, including X-Real-IP and X-Forwarded-For: it uses it to tell visitors apart and to locate their country. If your proxy uses another header, CLIENT_IP_HEADER names it.
Caddy obtains the Let’s Encrypt certificate and enables HTTPS by itself. Its reverse_proxy directive sets X-Forwarded-For by default: these three lines are all you need.
stats.example.com {
reverse_proxy 127.0.0.1:3000
}
This block follows the Nginx example from the Umami documentation, listening on port 80 so that Certbot can set up HTTPS:
server {
listen 80;
listen [::]:80;
server_name stats.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Certbot then installs the certificate and takes care of renewing it:
sudo certbot --nginx
When it creates its database, Umami opens an administrator account: username admin, password umami. The documentation asks you to change this password at your first login.
admin and umami.Keep TWO_FACTOR_ENCRYPTION_KEY with your backups: the documentation warns that losing or changing this key makes stored secrets unreadable, and each user must then enroll in two-factor authentication again.
<head> of your pages, then visit your website: data appears in the dashboard right away.<script defer src="https://stats.example.com/script.js" data-website-id="your-website-id"></script>
next/script component rather than a plain <script> tag.data-domains limits tracking to your production domains, which keeps your test environments out of the statistics.localStorage.setItem('umami.disabled', 1).Some ad blockers block anything that looks like analytics. Self-hosted, Umami is already served from your own domain, and two variables go further: TRACKER_SCRIPT_NAME gives the script another name than script.js, and COLLECT_API_ENDPOINT renames the collection endpoint, instead of /api/send. The script then calls the new endpoint by itself; only the source of the tracking code changes on your pages.
TRACKER_SCRIPT_NAME: "stats.js"
COLLECT_API_ENDPOINT: "/api/collect"
These environment variables go into the umami service of compose.yaml, then docker compose up -d recreates the container to pick them up:
| Variable | What it does | Value |
|---|---|---|
DISABLE_TELEMETRY | Turns off the anonymous telemetry Umami sends to the project to improve the application | 1 |
PRIVATE_MODE | Turns off all external network calls, including website icons, served from DuckDuckGo by default | 1 |
DISABLE_UPDATES | Turns off the check for new versions | 1 |
IGNORE_IP | Excludes IP addresses and CIDR ranges from data collection, such as those of your offices | A comma-separated list |
SALT_ROTATION | Sets how often the salt behind session IDs rotates | month by default |
REDIS_URL | Adds a Redis cache, which the documentation recommends for high-traffic websites or several instances | Your Redis address |
MCP_ENABLED | Opens the /mcp endpoint: your AI assistant reads your analytics read-only, with an API key created under Settings, API keys | 1 |
Everything Umami keeps lives in its PostgreSQL database: accounts, websites, teams and analytics. Dump it every day and right before each update: self-hosted, this data is kept with no time limit. Next to the database, the .env file holds the secrets that make it usable:
| Item | What it holds | Back up |
|---|---|---|
| The PostgreSQL database | Accounts, websites, teams, reports and all analytics | Required, with pg_dump |
.env | The database password, APP_SECRET and the two-factor authentication key | Required, encrypted |
compose.yaml | Your instance’s configuration | Recommended |
pg_dump makes a consistent dump while the database is in use. The custom format, -Fc, is compressed and restores with pg_restore:
docker compose exec -T db pg_dump -U umami -d umami -Fc > umami-$(date +%F).dump
Schedule this command on the server at least once a day, prune the dumps, which pile up, and send a copy elsewhere. Kept on the server’s disk, these dumps are also part of its nightly backups.
docker compose stop umami.docker compose stop umami
docker compose exec -T db pg_restore -U umami -d umami --clean --if-exists < umami-YYYY-MM-DD.dump
docker compose start umami
Repeat this drill from time to time, on a copy: that is how you check that a backup works.
The documentation gives two commands, from the compose.yaml folder. The database stays in its volume:
docker compose pull
docker compose up --force-recreate -d
On restart, the container applies the schema migrations by itself. Version 3.4.0, of 17 September 2026, carried two of them, for annotations and API keys; version 3.4.1, of 23 October 2026, carries none. After a major upgrade, such as the move to version 3, the documentation suggests running ANALYZE in PostgreSQL so that dashboards get their speed back:
docker compose exec db psql -U umami -d umami -c "ANALYZE;"
postgres:15-alpine tag. Moving to a new major PostgreSQL version takes a dump and restore, or pg_upgrade.Self-hosting means running the server on top of the application. On a managed VPS, our teams take charge of the server and proactively monitor your instance; Umami remains your software, with its settings and its data.
| Topic | Our teams | You |
|---|---|---|
| The server | System and security updates, server interventions included | The timing that suits your business |
| Monitoring | Server monitored 24/7, with intervention without waiting for your call | Reading your instance’s logs |
| Umami | Proactive monitoring: errors, slowdowns and disk space spotted, fix recommended | Its installation, updates and settings, which you apply, yourself or through your AI assistant |
| Websites and analytics | The server that carries them, monitored and backed up | Accounts, tracked websites, the tracking script and how long data is kept |
| Backups | Every night, encrypted and kept off the machine; restores included | Umami’s own dumps, and the restore request, when you need one |
| Migration | Your current server migrated, free of charge | The timing of the switch |
If you would rather have our teams work inside the application itself, that service is billed by the hour or at a fixed rate, with an estimate shared before the work starts.
With a Simafri Managed VPS, our teams keep your Umami instance’s server up to date, monitor it day and night and step in without waiting for your call; they also proactively monitor your instance and recommend useful fixes, which you apply, yourself or through your AI assistant. Backups are encrypted every night and kept off the machine, restores are included, and migrating your current server is free.
Yes. Umami is under the MIT license: you can use, modify and redistribute it at work with no license to buy, as long as you keep its copyright notice. What remains is the cost of the server that runs it.
No. The project’s FAQ states that the Umami web tracker does not use cookies. Whether your website needs a cookie notice depends on the other services you use on it and the requirements that apply.
Not since version 3: Umami supports PostgreSQL, version 12.14 at minimum. A version 2 instance on MySQL first moves its data to PostgreSQL with the project’s migration guide, then upgrades to version 3.
Start from the project’s docker-compose.yml, which runs Umami and PostgreSQL, with random secrets in a .env file and the port published on 127.0.0.1. Run docker compose up -d, then put an HTTPS reverse proxy in front, on a subdomain.
At installation, Umami creates an admin account with the password umami. Change it at your first login, under Settings then Profile, and enable two-factor authentication in the Security tab once TWO_FACTOR_ENCRYPTION_KEY is set.
On a self-hosted instance, TRACKER_SCRIPT_NAME gives the script another name than script.js, and COLLECT_API_ENDPOINT renames the /api/send collection endpoint. Then change the source of the tracking code on your pages.
Dump the PostgreSQL database every day with pg_dump, from the database container, and keep the .env file separately, as it holds APP_SECRET and the two-factor authentication key. Restore with pg_restore, and test the restore regularly.
A managed VPS covers the server: system, security, monitoring and backups. Our teams also proactively monitor your instance and recommend useful fixes, which you apply. Work inside the application is billed by the hour or at a fixed rate.
Simafri
Let's talk about your project
Tell us what you need in a few words: we will get back to you quickly.
Thank you! Your request has been sent. We'll get back to you shortly.
Prefer email? Write to us at support@simafri.com.