Deployment Guide

    Umami Analytics

    Privacy-focused, open-source web analytics. No cookies, no personal data, GDPR compliant by default.

    20–30 minutes
    1GB+ RAM
    VPS

    1GB RAM min / KVM from $5/mo

    OS

    Ubuntu 22.04 / 24.04 LTS

    Also Needed

    Domain name with DNS access

    Introduction

    Umami is a modern, open-source, privacy-focused web analytics platform. It provides essential insights — pageviews, unique visitors, bounce rate, referral sources, device breakdowns, and custom event tracking — while fully respecting visitor privacy. No cookies, no personal data, GDPR/PECR compliant out of the box.

    Privacy-first

    No cookies, no personal data, GDPR compliant by default

    Lightweight

    Tracking script under 2KB — won't impact page load

    Full data ownership

    All data stays on your server, no third-party access

    Clean dashboard

    Pageviews, sessions, devices, UTM, funnels, custom events

    When choosing a subdomain, avoid names like "analytics.yourdomain.com" as ad blockers may block them. Use something like stats.yourdomain.com or insight.yourdomain.com instead.

    Step 1: Initial Server Setup

    1Connect & Update

    ssh root@YOUR_SERVER_IP
    apt update && apt upgrade -y

    2Create a Non-Root User (Recommended)

    adduser umami
    usermod -aG sudo umami
    su - umami

    3Configure the Firewall

    sudo ufw allow OpenSSH
    sudo ufw allow 80/tcp
    sudo ufw allow 443/tcp
    sudo ufw enable

    Step 2: Install Docker & Docker Compose

    # Install prerequisites
    sudo apt install -y ca-certificates curl gnupg
    
    # Add Docker's 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 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
    sudo apt update
    sudo apt install -y docker-ce docker-ce-cli \
      containerd.io docker-buildx-plugin docker-compose-plugin
    # Add user to Docker group
    sudo usermod -aG docker $USER
    newgrp docker
    
    # Verify installation
    docker --version
    docker compose version

    Step 3: Deploy Umami with Docker Compose

    1Create Project Directory & Generate Secret

    mkdir -p ~/umami && cd ~/umami
    openssl rand -base64 32

    Copy the output — you'll need it in the next step.

    2Create the Environment File

    ~/umami/.env
    POSTGRES_DB=umami
    POSTGRES_USER=umami
    POSTGRES_PASSWORD=YOUR_STRONG_DB_PASSWORD
    DATABASE_URL=postgresql://umami:YOUR_STRONG_DB_PASSWORD@db:5432/umami
    APP_SECRET=YOUR_GENERATED_SECRET
    TRACKER_SCRIPT_NAME=getinfo

    TRACKER_SCRIPT_NAME renames the default "script.js" to a custom name (e.g., "getinfo") to prevent ad blockers from blocking the analytics script.

    3Create the Docker Compose File

    ~/umami/docker-compose.yml
    services:
      umami:
        image: docker.umami.is/umami-software/umami:postgresql-latest
        ports:
          - "3000:3000"
        environment:
          DATABASE_URL: ${DATABASE_URL}
          APP_SECRET: ${APP_SECRET}
          TRACKER_SCRIPT_NAME: ${TRACKER_SCRIPT_NAME}
        depends_on:
          db:
            condition: service_healthy
        restart: always
        healthcheck:
          test: ["CMD-SHELL", "curl -f http://localhost:3000/api/heartbeat || exit 1"]
          interval: 30s
          timeout: 5s
          retries: 3
    
      db:
        image: postgres:15-alpine
        environment:
          POSTGRES_DB: ${POSTGRES_DB}
          POSTGRES_USER: ${POSTGRES_USER}
          POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
          TZ: UTC
        volumes:
          - umami-db-data:/var/lib/postgresql/data
        restart: always
        healthcheck:
          test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
          interval: 10s
          timeout: 5s
          retries: 5
    
    volumes:
      umami-db-data:

    4Launch the Stack

    docker compose up -d
    docker compose ps

    Both umami and db containers should show as healthy/running. Umami is now accessible at http://YOUR_SERVER_IP:3000.

    Step 4: Nginx Reverse Proxy

    sudo apt install -y nginx
    sudo systemctl enable nginx
    /etc/nginx/sites-available/umami
    server {
        listen 80;
        server_name stats.yourdomain.com;
    
        location / {
            proxy_pass http://127.0.0.1:3000;
            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_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
        }
    }
    sudo ln -s /etc/nginx/sites-available/umami /etc/nginx/sites-enabled/
    sudo nginx -t
    sudo systemctl reload nginx

    Step 5: SSL with Let's Encrypt

    sudo apt install -y certbot python3-certbot-nginx
    sudo certbot --nginx -d stats.yourdomain.com

    Verify auto-renewal:

    sudo certbot renew --dry-run

    Your Umami instance is now accessible at https://stats.yourdomain.com.

    Step 6: Initial Login & Configuration

    Default Credentials

    FieldValue
    Usernameadmin
    Passwordumami

    Change the default password immediately after first login. Go to Settings → Profile.

    Add Your First Website

    1. Navigate to Settings → Websites and click Add Website.
    2. Enter a display name and your website's domain.
    3. Click Save, then Edit to copy the tracking snippet.

    Install the Tracking Code

    Add to the <head> section of every page:

    HTML tracking snippet
    <script defer src="https://stats.yourdomain.com/getinfo"
      data-website-id="YOUR-WEBSITE-ID"></script>

    The script filename is "getinfo" instead of "script.js" because of the TRACKER_SCRIPT_NAME variable. Visitors appear in your dashboard within seconds.

    Step 7: Database Backup Strategy

    Manual Backup

    docker compose exec db pg_dump -U umami umami \
      > umami_backup_$(date +%Y%m%d).sql

    Automated Daily Backups

    mkdir -p ~/umami/backups
    ~/umami/backup.sh
    #!/bin/bash
    BACKUP_DIR=~/umami/backups
    TIMESTAMP=$(date +%Y%m%d_%H%M%S)
    
    cd ~/umami
    docker compose exec -T db pg_dump -U umami umami \
      | gzip > $BACKUP_DIR/umami_$TIMESTAMP.sql.gz
    
    # Keep only the last 14 days of backups
    find $BACKUP_DIR -name "umami_*.sql.gz" -mtime +14 -delete
    chmod +x ~/umami/backup.sh
    crontab -e

    Add this line to run daily at 2:00 AM:

    0 2 * * * /home/umami/umami/backup.sh

    Step 8: Updating Umami

    cd ~/umami
    docker compose pull
    docker compose up -d

    Check the Umami GitHub releases before updating to review any breaking changes or migration notes.

    Step 9: Performance Tuning

    Nginx Caching Headers

    Add caching for the tracking script to reduce server load:

    Add to Nginx config
    location ~* \.(js)$ {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        expires 1h;
        add_header Cache-Control "public, immutable";
    }

    PostgreSQL Tuning (100K+ monthly pageviews)

    Add to db service in docker-compose.yml
    db:
      image: postgres:15-alpine
      command:
        - postgres
        - -c
        - shared_buffers=256MB
        - -c
        - effective_cache_size=512MB
        - -c
        - work_mem=4MB

    Troubleshooting

    IssueSolution
    Container won't startRun docker compose logs umami. Verify .env has correct DATABASE_URL formatting.
    502 Bad GatewayEnsure Umami container is running (docker compose ps). Check Nginx config with nginx -t.
    No data in dashboardVerify tracking script loads (DevTools Network tab). Check data-website-id matches. Disable ad blockers to test.
    SSL not renewingRun sudo certbot renew --dry-run. Ensure port 80 is accessible and certbot timer is active.
    Database errorsEnsure PostgreSQL is healthy. Verify DATABASE_URL matches POSTGRES_USER and POSTGRES_PASSWORD.

    View real-time logs for debugging:

    docker compose logs -f umami
    docker compose logs -f db