SELF-HOST, WITHOUT THE GUESSWORK

How to Self-Host PostHog on a VPS (Production Setup)

Run PostHog on your own VPS for product analytics without per-event pricing. Full control over your data.

How to Self-Host PostHog on a VPS (Production Setup)

PostHog is powerful until the event-based pricing catches up with your growth.

Self-hosting PostHog means you own your analytics data and pay flat server costs. The trade-off: PostHog is a complex stack (ClickHouse, Kafka, Postgres, Redis). This guide walks through a production-ready setup.

What you will have at the end

Step 1: Prepare the server

PostHog needs serious resources. Do not try this on a 2GB VPS.

sudo apt update && sudo apt upgrade -y

Install Docker if needed:

curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker

Step 2: Create directory structure

mkdir -p ~/apps/posthog
cd ~/apps/posthog

Step 3: Clone the PostHog Docker repository

PostHog maintains official Docker Compose files. Use them.

git clone https://github.com/PostHog/posthog.git
cd posthog

Alternatively, create a minimal docker-compose.yml:

services:
  traefik:
    image: traefik:v2.11
    command:
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
      - --certificatesresolvers.letsencrypt.acme.tlschallenge=true
      - [email protected]
      - --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
    ports:
      - 80:80
      - 443:443
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - letsencrypt:/letsencrypt
    restart: unless-stopped

  db:
    image: postgres:15-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: posthog
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: posthog
    volumes:
      - postgres_data:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    restart: unless-stopped
    volumes:
      - redis_data:/data

  clickhouse:
    image: clickhouse/clickhouse-server:23.8-alpine
    restart: unless-stopped
    volumes:
      - clickhouse_data:/var/lib/clickhouse
    ulimits:
      nofile:
        soft: 262144
        hard: 262144

  posthog:
    image: posthog/posthog:latest
    restart: unless-stopped
    depends_on:
      - db
      - redis
      - clickhouse
    environment:
      DATABASE_URL: postgres://posthog:${POSTGRES_PASSWORD}@db:5432/posthog
      REDIS_URL: redis://redis:6379
      CLICKHOUSE_HOST: clickhouse
      SECRET_KEY: ${SECRET_KEY}
      SITE_URL: https://posthog.your-domain.com
      IS_BEHIND_PROXY: "true"
    labels:
      - traefik.enable=true
      - traefik.http.routers.posthog.rule=Host(`posthog.your-domain.com`)
      - traefik.http.routers.posthog.entrypoints=websecure
      - traefik.http.routers.posthog.tls=true
      - traefik.http.routers.posthog.tls.certresolver=letsencrypt
      - traefik.http.services.posthog.loadbalancer.server.port=8000

volumes:
  letsencrypt:
  postgres_data:
  redis_data:
  clickhouse_data:

Step 4: Configure environment variables

Create .env file:

# Generate a secret key
SECRET_KEY=$(openssl rand -hex 32)

# Database password
POSTGRES_PASSWORD=$(openssl rand -hex 16)

# Site URL
SITE_URL=https://posthog.your-domain.com

Save these values securely. You will need them for recovery.

Step 5: Deploy

docker compose up -d

First startup takes several minutes as ClickHouse initializes.

Step 6: Verify installation

docker compose ps
docker compose logs -f posthog

Visit https://posthog.your-domain.com and create your admin account.

Backup strategy

PostHog stores data in multiple places. Back up all of them.

#!/bin/bash
# Daily backup script
BACKUP_DIR=/backups/posthog/$(date +%Y%m%d)
mkdir -p $BACKUP_DIR

# Postgres dump
docker exec posthog-db-1 pg_dump -U posthog posthog > $BACKUP_DIR/postgres.sql

# ClickHouse backup (large, consider incremental)
docker exec posthog-clickhouse-1 clickhouse-client --query "BACKUP DATABASE default TO Disk('backups', 'daily')"

# Compress
tar -czf $BACKUP_DIR.tar.gz $BACKUP_DIR

Consider using your VPS provider's snapshot feature for simpler recovery.

Troubleshooting

ClickHouse out of memory

ClickHouse is memory-hungry. If you see OOM errors:

PostHog web UI returns 502

Events not appearing in dashboard

Slow queries on large datasets

Internal links


Related in the StoicSoft network

If you're choosing a VPS provider or benchmarking real-world performance like the post above explores, StoicVPS is the StoicSoft network's independent tracker for VPS pricing, performance, and migration safety.

From across the StoicSoft network

Hand-curated reads on the same topic from sister sites in the StoicSoft family.