How to Self-Host Umami: Simple Web Analytics 2026
TL;DR
Umami (MIT, ~21K GitHub stars, Next.js/TypeScript) is a simple, lightweight self-hosted web analytics tool. It's faster and simpler than Plausible, with a clean real-time dashboard, custom event tracking, and multi-site support. No cookies, no personal data, GDPR-compliant out of the box. The tracking script is under 2KB. Google Analytics is free but tracks users extensively; Umami gives you traffic insights without surveillance.
Key Takeaways
- Umami: MIT, ~21K stars, Next.js — simple analytics, no cookies, real-time dashboard
- Multi-site: Track unlimited websites from a single Umami instance
- Teams: Share analytics access with team members per site
- Custom events: Track clicks, form submissions, any user interaction
- vs Plausible: Umami is simpler and lighter; Plausible has more features (funnels, revenue)
- Share URL: Public dashboard URLs for client reporting
Part 1: Docker Setup
# docker-compose.yml
services:
umami:
image: ghcr.io/umami-software/umami:postgresql-latest
container_name: umami
restart: unless-stopped
ports:
- "3000:3000"
environment:
DATABASE_URL: "postgresql://umami:${POSTGRES_PASSWORD}@db:5432/umami"
DATABASE_TYPE: postgresql
APP_SECRET: "${APP_SECRET}"
depends_on:
db:
condition: service_healthy
db:
image: postgres:15-alpine
restart: unless-stopped
environment:
POSTGRES_DB: umami
POSTGRES_USER: umami
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U umami"]
interval: 5s
start_period: 20s
volumes:
db_data:
# .env
POSTGRES_PASSWORD=your-db-password
APP_SECRET=$(openssl rand -hex 32)
docker compose up -d
Visit http://your-server:3000 → log in with admin / umami → change password immediately.
Part 2: HTTPS with Caddy
stats.yourdomain.com {
reverse_proxy localhost:3000
}
Part 3: Add a Website
- Settings → Websites → + Add website
- Name:
My Blog - Domain:
yourdomain.com - Copy the tracking script
<!-- Add to your site's <head>: -->
<script async src="https://stats.yourdomain.com/script.js"
data-website-id="YOUR-WEBSITE-UUID">
</script>
Part 4: Install Tracking Script
Static HTML
<head>
<script async src="https://stats.yourdomain.com/script.js"
data-website-id="YOUR-UUID">
</script>
</head>
Next.js
npm install @umami/analytics
// app/layout.tsx:
import Script from 'next/script'
export default function RootLayout({ children }) {
return (
<html>
<head>
<Script
src="https://stats.yourdomain.com/script.js"
data-website-id="YOUR-UUID"
strategy="afterInteractive"
/>
</head>
<body>{children}</body>
</html>
)
}
WordPress
Plugin: Insert Headers and Footers
Header section: paste script tag
Or use the official Umami WordPress plugin.
Ghost
Admin → Settings → Code injection → Site header:
<script async src="https://stats.yourdomain.com/script.js" data-website-id="UUID"></script>
Part 5: Custom Events
Track user interactions beyond page views:
// Track a button click:
document.getElementById('signup-btn').addEventListener('click', function() {
umami.track('signup-click', { plan: 'pro' })
})
// Track form submission:
document.getElementById('contact-form').addEventListener('submit', function() {
umami.track('form-submit', { form: 'contact' })
})
// Track with properties:
umami.track('purchase', {
amount: 49.99,
product: 'Pro Plan',
currency: 'USD'
})
HTML data attributes (no JavaScript needed)
<!-- Automatically track this button click: -->
<button data-umami-event="CTA Click" data-umami-event-location="hero">
Get Started
</button>
<!-- Track link clicks: -->
<a href="/pricing" data-umami-event="Pricing Link">View Pricing</a>
Part 6: Multi-Site Management
Add multiple sites
Settings → Websites:
├── yourdomain.com (Website 1)
├── blog.yourdomain.com (Website 2)
├── client-a.com (Website 3)
└── client-b.com (Website 4)
Each site gets its own unique tracking script with a different data-website-id.
Teams
- Settings → Teams → + Create team
- Add members:
client@example.com→ Viewer access - Share only the sites you want them to see
Shared links
- Website → Edit → Enable share URL
- Share the read-only link with clients:
https://stats.yourdomain.com/share/TOKEN/site-name
Part 7: Dashboard Overview
The Umami dashboard shows:
Overview:
├── Visitors (unique)
├── Views (total page views)
├── Sessions
├── Bounce rate
└── Average session duration
Sources:
├── Referrers
├── Browsers
├── Operating systems
└── Devices (desktop/mobile/tablet)
Pages:
├── Most visited pages
└── Entry/exit pages
Location:
├── Countries
├── Regions
└── Cities (if enabled)
Events:
└── Custom event counts and properties
Part 8: API
API_KEY="your-api-key" # Settings → API Keys
BASE="https://stats.yourdomain.com/api"
WEBSITE_ID="your-website-uuid"
# Get website stats:
curl "$BASE/websites/$WEBSITE_ID/stats?startAt=1704067200000&endAt=$(date +%s)000" \
-H "x-umami-api-key: $API_KEY" | jq '.pageviews'
# Get top pages:
curl "$BASE/websites/$WEBSITE_ID/metrics?startAt=1704067200000&endAt=$(date +%s)000&type=url" \
-H "x-umami-api-key: $API_KEY" | jq '.[0:10]'
# Get active visitors right now:
curl "$BASE/websites/$WEBSITE_ID/active" \
-H "x-umami-api-key: $API_KEY"
# Send a custom event via API (server-side tracking):
curl -X POST "$BASE/send" \
-H "Content-Type: application/json" \
-d '{
"payload": {
"website": "YOUR-WEBSITE-UUID",
"url": "/api/checkout",
"name": "Purchase",
"data": {"amount": 49.99, "plan": "pro"}
},
"type": "event"
}'
Maintenance
# Update:
docker compose pull
docker compose up -d
# Backup:
docker exec umami-db-1 pg_dump -U umami umami \
| gzip > umami-db-$(date +%Y%m%d).sql.gz
# Logs:
docker compose logs -f umami
See also: Plausible Analytics — more features; Matomo — enterprise-grade
See all open source analytics tools at OSSAlt.com/categories/analytics.