How to Self-Host Umami Analytics: The Complete 2026 Guide
Updated: August 2026
Umami is an open-source, privacy-first web analytics platform that gives you full ownership of your visitor data with no cookies, no annoying consent banners, and no data shipped off to any third party company. Self-hosting Umami means the entire stack composed of the app, the database, and every pageview Umami records, lives on infrastructure you control. This guide walks through a how you can deploy Umami Analytics in a secure manner from a blank server to ongoing maintenance. We'll go through installing Docker, configuring PostgreSQL, securing the app with HTTPS, setting up backups, and hardening the server. his guide is for developers and website owners who want a Google Analytics alternative that doesn't send data to Google or any other provider regardless if the goal is privacy, cost control, or both.
Table of contents
- Prerequisites
- Step 1: Install Docker and Docker Compose
- Step 2: Create the Docker Compose Configuration
- Step 3: Understand the Database Setup
- Step 4: Deploy With Docker Compose
- Step 5: Configure Nginx as a Reverse Proxy With SSL
- Step 6: Create Your Admin Account and Add Websites
- Step 7: Set Up Automated Backups
- Step 8: Ongoing Maintenance
- Security Best Practices
- Is Self-Hosting Right For You?
- Frequently Asked Questions
Prerequisites
Before you start, you need to have the following:
- A Linux machine. 1 GB of RAM is a comfortable minimum for Umami plus PostgreSQL; the app itself will technically run on 512 MB, but you'll want more headroom if you expect to get meaningful traffic. Budget 10–20 GB of storage so your analytics database has room to grow. Reasonable starting points: Hetzner (from roughly €4–5/month), DigitalOcean (from $6/month), or a similarly priced ARM/shared VPS from any mainstream provider. Due to rising data center costs, the prices can vary since the publication of this article. Check current pricing before you commit to a provider.
- Docker and Docker Compose.To be installed in Step 1 if you don't have them already.
- A domain name.This should be pointed to your server's IP address (an A record is enough). Umami's dashboard and tracking script must be served over HTTPS as modern browsers and many ad blockers are stricter about scripts served over plain HTTP.
- SSH access.Configured to access the server and run basic terminal commands.
Step 1: Install Docker and Docker Compose
If Docker isn't already installed on your target machine, set it up with Docker's official convenience steps. On Ubuntu or Debian:
# Remove any old/partial installs (safe to skip on a fresh server)
sudo apt update
sudo apt install -y ca-certificates curl gnupg
# Add Docker's official GPG key
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# Add the Docker repository
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo \"$VERSION_CODENAME\") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Install Docker Engine and the Compose plugin
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-pluginVerify the install: run this terminal command to ensure everything is conifugred correctly. No errors means you're good to go.
sudo docker run hello-worldAdd your user to the docker group so you don't need sudo for every command, then log out and back in for it to take effect:
sudo usermod -aG docker $USER(If you're on Debian instead of Ubuntu, swap the repository URL to download.docker.com/linux/debian — everything else is the same.)
Step 2: Create the Docker Compose Configuration
Create a directory for the deployment and a docker-compose.yml file inside it:
mkdir -p ~/umami && cd ~/umami
nano docker-compose.ymlUmami's stack is intentionally simple: one container for the app, one for PostgreSQL. Here's a complete, up-to-date configuration file that you can use with Docker:
services:
umami:
image: ghcr.io/umami-software/umami:postgresql-latest
container_name: umami
ports:
- "127.0.0.1:3000:3000"
environment:
DATABASE_URL: postgresql://umami:${POSTGRES_PASSWORD}@db:5432/umami
APP_SECRET: ${APP_SECRET}
# Optional: renames the tracking script/endpoint so it's less likely
# to be caught by generic ad-blocker filter lists.
TRACKER_SCRIPT_NAME: script.js
depends_on:
db:
condition: service_healthy
restart: unless-stopped
db:
image: postgres:16-alpine
container_name: umami-db
environment:
POSTGRES_DB: umami
POSTGRES_USER: umami
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- umami-db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U umami"]
interval: 5s
timeout: 5s
retries: 5
restart: unless-stopped
volumes:
umami-db-data:Don't be afraid to modify some values like default usernames and passwords. That's actually recommended.
A few notes worth calling out, since older guides get these wrong:
- APP_SECRET, not HASH_SALT. Current Umami releases use the APP_SECRET environment variable to sign authentication tokens. HASH_SALT was the name used in older (v1-era) versions — if you're following a guide or template that still references it, know that it's legacy. Generate a strong random value with openssl
- The Umami port is bound to 127.0.0.1 only (127.0.0.1:3000:3000), not exposed to the public internet directly. Nginx, configured in Step 5, is what the outside world actually talks to. This is a meaningful security improvement over exposing port 3000 on all interfaces.
- depends_on with a healthcheck ensures the Umami container waits for PostgreSQL to actually be ready to accept connections, not just for the container to start. This avoids a common race-condition failure on first boot where umami checks and sees that the databases isn't working, so it just fails.
Store your secrets in a .env file in the same directory rather than hardcoding them:
cat > .env << 'EOF'
POSTGRES_PASSWORD=replace_with_a_long_random_password
APP_SECRET=replace_with_output_of_openssl_rand_-base64_32
EOFDocker Compose automatically reads a .env file in the same directory as docker-compose.yml.
Too much setup? Deploy Umami in minutes nowStep 3: Understand the Database Setup
Umami needs a database to store all analytics data (We used the postgresql-latest image, but a MySQL-tagged image also exists if you prefer that engine). You don't need to manually create any tables though. On first startup, Umami runs its own migrations against the empty database defined by POSTGRES_DB/POSTGRES_USER/POSTGRES_PASSWORD and creates the schema automatically.
That first startup also seeds a default administrator account:
- Username: admin
- Password: umami
This default is publicly documented and therefore not a secret. Treat it as a placeholder to be changed the moment you can log in, not as a real credential. Step 6 covers changing it.
Step 4: Deploy With Docker Compose
From the directory containing your docker-compose.yml, run the below command:
docker compose up -dThis pulls both images and starts the containers in detached mode. Expect the first run to take longer because of the downloads. Confirm both services are healthy:
docker compose ps
docker compose logs -f umamiWatch the logs until you see Umami report that it's listening and connected to the database. At this point Umami is running, but only reachable from the server itself (127.0.0.1:3000). That's expected, since Nginx hasn't been wired up yet.
Step 5: Configure Nginx as a Reverse Proxy With SSL
For production use, Umami should sit behind a reverse proxy that handles HTTPS. For this purpose, we will install Nginx and Certbot:
sudo apt install -y nginx certbot python3-certbot-nginxCreate /etc/nginx/sites-available/umami (replace yourdomain.com with your real domain):
server {
listen 80;
server_name yourdomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name yourdomain.com;
# Certbot will populate these two lines automatically
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
gzip on;
gzip_types text/plain application/javascript application/json text/css;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}Enable the site and issue a certificate:
sudo ln -s /etc/nginx/sites-available/umami /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d yourdomain.comCertbot rewrites the ssl_certificate lines for you and sets up a renewal timer, so certificates renew automatically without further action. To be safe, you can confirm the timer exists with systemctl list-timers | grep certbot.
Too much setup? Deploy Umami in minutes nowStep 6: Create Your Admin Account and Add Websites
Visit https://yourdomain.com and log in with the default credentials (admin / umami). Immediately go to Settings → Profile and change the password to something long and unique. Don't add any websites or share the URL before doing this.
Next, go to Websites → Add website, give it a name and domain, and save. Umami generates a small tracking snippet; paste it into the
of the site you want to track. Pageviews should start appearing in the dashboard within moments, refreshed roughly in real time.If data doesn't show up, the two most common causes are: the data-website-id in the script not matching the website's ID in Umami's settings, or the script being blocked by an ad blocker or browser extension. Test in a private/incognito window with extensions disabled before assuming something is broken. If ad-blocker interference is a recurring problem for your audience, the TRACKER_SCRIPT_NAME variable set in Step 2 helps by serving the script from a less recognizable path. See Umami's own ad-blocker bypass docs for the full technique.
Step 7: Set Up Automated Backups
Your PostgreSQL database is the only copy of your historical analytics and losing it means losing that history permanently. Automate daily dumps with pg_dump and cron.
Create ~/umami/backup.sh:
#!/bin/bash
set -e
BACKUP_DIR=~/umami/backups
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%F)
docker compose -f ~/umami/docker-compose.yml exec -T db \
pg_dump -U umami umami | gzip > "$BACKUP_DIR/umami-$TIMESTAMP.sql.gz"
# Keep the last 14 local backups
find "$BACKUP_DIR" -name "umami-*.sql.gz" -mtime +14 -deletechmod +x ~/umami/backup.shSchedule it daily and, critically, ship a copy off the server (S3, Backblaze B2, or a second machine) — a local-only backup doesn't protect you if the server itself is lost:
crontab -e
# Add:
0 2 * * * ~/umami/backup.shTest the restore path periodically. A backup you've never restored is unverified, not a backup:
gunzip -c backups/umami-2026-08-01.sql.gz | docker compose exec -T db psql -U umami -d umamiStep 8: Ongoing Maintenance
Self-hosting is not a one-time task. Build a light routine around it:
- Update regularly. Pull the latest image and recreate the containers: docker compose pull && docker compose up -d. Watch the Umami GitHub releases for changelog notes, especially around major version bumps.
- Monitor uptime. A free tier of UptimeRobot, Healthchecks.io, or a self-hosted option like Uptime Kuma will alert you if the instance goes down.
- Watch resource usage. Disk usage grows with your database as pageviews accumulate; keep an eye on df -h and docker system df.
- Confirm certificate renewal. Certbot's timer handles this automatically, but check it isn't silently failing every few months (sudo certbot renew --dry-run).
- Re-check your backups. Automation drifts; re-verify the restore process periodically, not just once at setup.
Security Best Practices
- Firewall. Allow only SSH (22), HTTP (80, for the redirect to HTTPS), and HTTPS (443). ufw allow 22,80,443/tcp and ufw enable is enough on Ubuntu.
- SSH keys, not passwords. Disable password authentication in /etc/ssh/sshd_config once your key-based login works.
- Keep the OS patched. sudo apt update && sudo apt upgrade -y on a regular cadence, or enable unattended upgrades for security patches.
- Consider fail2ban to throttle brute-force SSH attempts.
- Don't expose the database port. The Compose file above intentionally omits a published port for db — PostgreSQL should only be reachable from the umami container over the internal Docker network, never from the public internet.
- Use a strong, unique admin password for the Umami dashboard itself, and rotate APP_SECRET only if you're prepared to invalidate existing sessions.
Is Self-Hosting Right for You?
Self-hosting gives you full data ownership and a cost that's essentially just your existing server bill, especially if you're already tracking more than one site from a VPS you'd be paying for anyway. The trade-off is that you own the operations: updates, backups, uptime, and the occasional 2 a.m. disk-space alert.
If that trade doesn't appeal to you, or you'd rather not run infrastructure at all, well, this is why UmamiEngine was built. Join us and deploy Umami Analytics in minutes without worrying about maintenance or backups. We handle everything while you focus on building your projects.
Get fully managed Umami in minutesFrequently Asked Questions
What are Umami's minimum server requirements? Umami plus PostgreSQL will run on as little as 512 MB of RAM, but 1 GB or more is a safer baseline, especially if the same server runs Nginx or other services.
What's the default Umami login? admin / umami, seeded automatically on first startup. Change it immediately in Settings → Profile.
Is HASH_SALT still used? No — current Umami releases use APP_SECRET. HASH_SALT was the variable name in older (v1) versions; if a tutorial or template still uses it, treat it as outdated.
Does Umami require cookies or a consent banner? Umami is cookieless by design, which is why many sites run it without a cookie-consent banner. That said, whether you legally need a consent banner still depends on your jurisdiction and what else your site does — this isn't legal advice.
How is Umami different from Plausible? Both are open-source, cookie-free, and self-hostable. Umami's stack is simpler (PostgreSQL only), while Plausible adds ClickHouse for heavier aggregation workloads. For most sites under roughly a million monthly pageviews, the practical difference is small.
Get fully managed Umami in minutes