
Google Analytics isn’t your only option for pageviews and events. It’s also often more than you need.
If you want a small, self-hosted tracker with a clean UI and predictable costs, you can run Umami analytics on your own VPS. Your data stays in your environment.
This is an open-source spotlight for people who run real servers. You’ll get a practical VPS deployment using Docker Compose, Nginx with HTTPS, plus backups and a safe update routine.
Why Umami exists (and who it’s for)
Umami is an open-source web analytics app focused on the useful basics. It tracks pageviews, referrers, countries, devices, and custom events.
It’s built for site owners who want answers quickly. It’s not a reporting platform you need to learn.
Umami is a good fit if you run:
- Marketing sites and docs where you mostly care about traffic sources and top pages
- SaaS dashboards where you want lightweight event tracking without shipping a heavy client bundle
- Multiple small properties where “good enough” beats a sprawling analytics stack
It also helps if your policies push you away from third-party trackers. Self-hosting doesn’t magically equal compliance.
What it does give you is tighter control over retention, access, and data location.
What you get when you run Umami analytics
Umami’s appeal is how little ceremony it requires. Add a small script tag, then view real-time traffic and trends.
The UI stays out of the way.
- Pageview tracking with referrers and UTM support
- Event tracking (custom events you send from your app)
- Multiple websites under one admin
- Teams/users depending on how you configure access
- API access for pulling stats into your own dashboards (useful for internal reporting)
Licensing note: Umami is open source. Before you embed it into a commercial distribution or rehost it for customers, confirm the current license in the upstream repository.
For typical “run it for your own sites” use, you’re in familiar territory.
Umami vs hosted analytics (GA, Plausible, and friends)
Hosted analytics reduces ops work. In exchange, you give up some control and take on a monthly bill.
Umami makes the opposite trade. You operate a small service and keep the data on your infrastructure.
- Google Analytics: feature-rich, but heavy and policy-driven. Data sits with a third party, and reporting can feel like a product of its own.
- Hosted Plausible/Umami: clean UX and fewer knobs. You hand off uptime and database care, but accept ongoing subscription costs.
- Self-hosted Umami: small footprint, simple schema, and easy backups. You own updates, TLS, and storage.
If you already run production workloads on a VPS, Umami is a straightforward add-on.
The moving parts stay predictable: containers, a database, and a reverse proxy.
Resource sizing for a VPS (realistic numbers)
Analytics traffic is bursty. A quiet site might send a few hits per minute.
Then a campaign spike can generate thousands. The upside is that Umami usually stays modest on CPU and RAM.
- Small setup (1–5 low/medium-traffic sites): 1 vCPU, 1–2 GB RAM, 20+ GB NVMe storage
- Medium setup (multiple sites, steady traffic): 2 vCPU, 4 GB RAM, 40+ GB NVMe storage
- Growing setup: 4 vCPU, 8 GB RAM if you expect sustained bursts, plus more IOPS for the database
Disk performance matters more than most people expect. The database does frequent small writes.
NVMe-backed storage keeps latency down. It also keeps the UI responsive under load.
If you want predictable performance without sharing noisy neighbors, a HostMyCode VPS is a sensible baseline.
If you’d rather not maintain the stack yourself, managed VPS hosting can take the edge off patching and routine server care.
Deployment plan: Docker Compose + PostgreSQL on Ubuntu
Umami supports multiple database backends, but PostgreSQL is a common production choice. It offers strong write performance, straightforward backups, and easy monitoring.
A VPS setup that works well looks like this:
- Docker Engine + Docker Compose plugin
- Umami container
- PostgreSQL container with a persistent volume
- Nginx on the host for TLS termination and security headers
If you’re still building your VPS hardening checklist, these HostMyCode guides are worth a pass: setting up Fail2ban for Nginx and enabling automated security updates.
Prereqs you should have before you start
- A VPS running Ubuntu 24.04 LTS (or Debian 12/13). Commands below assume Ubuntu.
- A domain or subdomain pointing to the server (for example,
analytics.example.com). - Ports 80/443 open in your firewall/security group.
DNS causes most early failures.
If you need a clean cutover, follow this Cloudflare DNS migration walkthrough. It helps you avoid downtime while swapping records.
Install Docker and prepare directories
On a fresh Ubuntu VPS, install Docker from the official repository. This is the recommended path if you want current packages:
sudo apt update
sudo apt install -y ca-certificates curl gnupg
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
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
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Create a working directory and keep permissions tight:
sudo mkdir -p /opt/umami
sudo chown -R root:root /opt/umami
sudo chmod 0750 /opt/umami
Docker Compose file for Umami + PostgreSQL
This Compose layout keeps state in named volumes. It also avoids exposing PostgreSQL to the public network.
Create /opt/umami/compose.yml:
services:
db:
image: postgres:16
container_name: umami-db
environment:
POSTGRES_DB: umami
POSTGRES_USER: umami
POSTGRES_PASSWORD: "CHANGE_ME_LONG_RANDOM"
volumes:
- umami_db:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U umami -d umami"]
interval: 10s
timeout: 5s
retries: 10
restart: unless-stopped
umami:
image: ghcr.io/umami-software/umami:postgresql
container_name: umami
depends_on:
db:
condition: service_healthy
environment:
DATABASE_URL: postgresql://umami:CHANGE_ME_LONG_RANDOM@db:5432/umami
APP_SECRET: "CHANGE_ME_64_CHARS_RANDOM"
# Optional: set BASE_URL to your public URL
# BASE_URL: https://analytics.example.com
ports:
- "127.0.0.1:3000:3000"
restart: unless-stopped
volumes:
umami_db:
Two operator notes before you hit deploy:
- Bind Umami to localhost (
127.0.0.1:3000) so only Nginx can reach it. Don’t publish analytics directly on the public interface. - Use long random secrets. For example:
openssl rand -base64 48for passwords andopenssl rand -hex 32for app secrets.
Start the stack:
cd /opt/umami
sudo docker compose up -d
Check health and logs:
sudo docker compose ps
sudo docker logs --tail=100 umami
Reverse proxy with Nginx + HTTPS (Let’s Encrypt)
Run Nginx on the host so it can handle TLS termination, buffering, and shared security headers across your apps.
It also lets you choose HTTP/2/3 settings in one place.
Install Nginx and Certbot:
sudo apt install -y nginx certbot python3-certbot-nginx
Create an Nginx server block at /etc/nginx/sites-available/umami.conf:
server {
listen 80;
server_name analytics.example.com;
location /.well-known/acme-challenge/ {
root /var/www/html;
}
location / {
return 301 https://$host$request_uri;
}
}
Enable it and reload:
sudo ln -s /etc/nginx/sites-available/umami.conf /etc/nginx/sites-enabled/umami.conf
sudo nginx -t
sudo systemctl reload nginx
Issue a certificate:
sudo certbot --nginx -d analytics.example.com
Edit the HTTPS server block that Certbot creates (often the same file). Configure it to proxy to Umami on localhost.
This is a minimal, production-friendly proxy:
server {
listen 443 ssl http2;
server_name analytics.example.com;
ssl_certificate /etc/letsencrypt/live/analytics.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/analytics.example.com/privkey.pem;
# Basic security headers (tune for your environment)
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options SAMEORIGIN always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
# Keep logs (useful when tracking client-side script issues)
access_log /var/log/nginx/umami_access.log;
error_log /var/log/nginx/umami_error.log;
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;
proxy_read_timeout 60s;
}
}
Reload Nginx:
sudo nginx -t
sudo systemctl reload nginx
If you prefer Caddy for automatic TLS and simpler configs, HostMyCode also has a practical guide: Let’s Encrypt with Caddy on a VPS.
This post sticks with Nginx because it’s still a common choice when you host multiple apps.
Getting data in: add the tracking script (and keep it sane)
Once Umami is reachable at your domain, log in to the UI.
Create a website, then copy the tracking snippet into your site.
Two habits keep dashboards clean over time:
- Filter your own traffic. If you’re constantly testing pages, your “top pages” will be nonsense. Use Umami’s built-in filters or block internal IP ranges at the reverse proxy.
- Pick an event naming convention early. Having both
signupandSign Uplater is painful. Choose one format (for example, lowercase with underscores) and enforce it.
Backups: what to back up (and what to ignore)
With Umami, the database is the product. If you back up PostgreSQL, you’ve backed up the analytics.
Container images are replaceable.
Nginx configs are easy to recreate, but you still want them in version control or backups.
A sensible minimum set:
- PostgreSQL dumps (logical backups) stored offsite
/opt/umami/compose.yml(and any.envfile if you use one)- Nginx site config:
/etc/nginx/sites-available/umami.conf - Let’s Encrypt directory:
/etc/letsencrypt(optional, but it speeds up rebuilds)
Here’s a straightforward pg_dump backup that runs from the host and writes to /var/backups/umami:
sudo mkdir -p /var/backups/umami
sudo chmod 0750 /var/backups/umami
# Backup (creates a compressed custom-format dump)
sudo docker exec -t umami-db pg_dump -U umami -d umami -F c \
> /var/backups/umami/umami_$(date +%F).dump
Copy that dump to offsite storage. Keeping backups on the same VPS is just failure with a longer timeline.
Test restores at least once. Restore into a scratch database on a small VPS or local Docker environment.
Then confirm the UI comes up.
If you want a PostgreSQL baseline for secure access and operational checks, see Install PostgreSQL on Ubuntu and secure remote access.
Updates: safe routine that won’t break your tracking
Umami updates are usually smooth, but don’t pull and pray right before the weekend.
Treat analytics as production. It’s how you measure campaigns and product work.
A cautious update flow:
- Take a fresh database dump.
- Pull new images.
- Recreate containers.
- Watch logs for migration messages and errors.
cd /opt/umami
sudo docker compose pull
sudo docker compose up -d
sudo docker compose logs --tail=200 umami
Prune old images occasionally to reclaim disk:
sudo docker image prune -f
Operational guardrails (small things that prevent big headaches)
Your analytics endpoint is public-facing by design. Keep the surface area tight anyway.
- Lock down the database. Don’t map PostgreSQL ports to the public interface. The Compose file above keeps it internal.
- Harden Nginx. If you see brute force attempts, rate-limit login endpoints and use Fail2ban jails for repeat offenders. Start with Fail2ban for Nginx.
- Watch disk growth. Analytics grows until you stop it. Set a retention policy that matches your reporting needs (for example, 12–24 months), then prune old data.
- Keep the tracker fast. If your web server is already busy, give Umami its own server block and avoid expensive Nginx logging formats.
If you run a few VPS workloads, basic monitoring is worth the effort. Disk and uptime alerts catch the quiet failures.
If you already use Grafana, the Umami host can fit into the same stack. See this Grafana VPS deployment.
Common pitfalls (and quick fixes)
- Tracking script loads, but no data appears: confirm the website ID matches, and check browser devtools for blocked requests (ad blockers can interfere). Also check
/var/log/nginx/umami_access.logfor hits to the tracking endpoint. - Nginx shows 502 errors: make sure Umami is listening on
127.0.0.1:3000and the container is healthy. This HostMyCode guide helps: Nginx 502 Bad Gateway fix on Ubuntu VPS. - High CPU during traffic spikes: the database may be the bottleneck. Move to a bigger VPS, put PostgreSQL on faster storage, or split app and DB onto separate nodes if you’re growing.
Summary: Umami is small enough to self-host, serious enough to operate
Umami sits in a useful middle ground: pageviews and events without building an analytics department.
On a VPS, the ops story stays manageable.
Use Docker Compose for repeatable deploys, Nginx for TLS, and PostgreSQL dumps for backups.
If you’re paying for hosted analytics mainly to avoid setup work, this is one of the few self-hosted services that stays comfortable long-term.
Treat it like any other internet-facing app. Patch the OS, keep TLS current, and prove your restores work.
Need infrastructure that stays predictable under write-heavy workloads? Start with a HostMyCode VPS, and if you want help with migrations and cutover planning, use HostMyCode migrations to move cleanly without breaking your sites.
If you want Umami on stable infrastructure without paying for oversized resources, put it on a properly sized VPS and keep the database on fast storage. HostMyCode offers VPS hosting for hands-on operators and managed VPS hosting if you’d rather hand off routine server work.
FAQ
Do I need a separate VPS to run Umami analytics?
Not always. For low to moderate traffic, Umami can share a VPS with other small services.
If your main app is latency-sensitive, isolate Umami on its own VPS. That way database writes won’t compete with your app’s workload.
Should I use PostgreSQL or MySQL for Umami?
Both can work depending on your Umami deployment choice.
PostgreSQL is a comfortable default for self-hosting because backups and restores are simple and reliable with pg_dump.
How often should I back up the Umami database?
Daily is a reasonable baseline.
If you run active campaigns or rely on event tracking for product decisions, back up more often (for example, every 6–12 hours) and always store copies offsite.
Can I put Umami behind Cloudflare?
Yes. You can proxy analytics.example.com through Cloudflare and still terminate TLS at Nginx.
Just ensure the tracker endpoint isn’t cached.
Also make sure you’re logging the right client IPs (using the appropriate Cloudflare real-IP config if needed).
What’s the fastest way to roll back after an update?
Restore the database dump you took before updating, then redeploy the previous container image tag.
Rollbacks are why you take a backup first and why you avoid “latest-only” workflows in production.