Skip to main content

How to Self-Host Plausible Analytics: Privacy-First Web Analytics 2026

·OSSAlt Team
plausibleanalyticsprivacygdprself-hostingdocker2026

TL;DR

Plausible Analytics (AGPL 3.0, ~19K GitHub stars, Elixir/Phoenix) is a lightweight, privacy-first web analytics tool. It tracks page views, unique visitors, referrers, and goals — all without cookies, without collecting personal data, and fully GDPR-compliant. Google Analytics is free but sends all your visitor data to Google and requires a cookie banner. Plausible's tracking script is 45x smaller than Google Analytics. The hosted plan is $9/month; self-hosted is free.

Key Takeaways

  • Plausible: AGPL 3.0, ~19K stars, Elixir — privacy-first analytics, no cookies, GDPR-ready
  • No cookie banner required: No personal data collected, no consent needed in EU
  • Tiny script: 1KB tracking script vs 45KB for Google Analytics (45x smaller)
  • Simple dashboard: One beautiful page — no learning curve required
  • Custom events: Track button clicks, form submissions, file downloads
  • vs Umami: Plausible has better UX; Umami is lighter; both are excellent

Part 1: Docker Setup

# docker-compose.yml
services:
  plausible_db:
    image: postgres:15-alpine
    restart: unless-stopped
    volumes:
      - db-data:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: plausible_db
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s

  plausible_events_db:
    image: clickhouse/clickhouse-server:23.3.7.5-alpine
    restart: unless-stopped
    volumes:
      - event-data:/var/lib/clickhouse
      - ./clickhouse/clickhouse-config.xml:/etc/clickhouse-server/config.d/logging.xml:ro
      - ./clickhouse/clickhouse-user-config.xml:/etc/clickhouse-server/users.d/logging.xml:ro
    ulimits:
      nofile:
        soft: 262144
        hard: 262144

  plausible:
    image: ghcr.io/plausible/community-edition:latest
    restart: unless-stopped
    command: sh -c "sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh run"
    ports:
      - "8000:8000"
    environment:
      BASE_URL: "https://analytics.yourdomain.com"
      SECRET_KEY_BASE: "${SECRET_KEY_BASE}"
      DATABASE_URL: "postgres://postgres:${POSTGRES_PASSWORD}@plausible_db:5432/plausible_db"
      CLICKHOUSE_DATABASE_URL: "http://plausible_events_db:8123/plausible_events_db"
      DISABLE_REGISTRATION: "true"  # Prevent public sign-ups
      MAILER_EMAIL: analytics@yourdomain.com
      SMTP_HOST_ADDR: mail.yourdomain.com
      SMTP_HOST_PORT: 587
      SMTP_USER_NAME: analytics@yourdomain.com
      SMTP_USER_PWD: "${SMTP_PASSWORD}"
    depends_on:
      plausible_db:
        condition: service_healthy

volumes:
  db-data:
  event-data:
# Create ClickHouse config files (required):
mkdir -p clickhouse

cat > clickhouse/clickhouse-config.xml << 'EOF'
<yandex>
  <logger>
    <level>warning</level>
    <console>true</console>
  </logger>
  <query_thread_log remove="remove"/>
  <query_log remove="remove"/>
  <text_log remove="remove"/>
  <trace_log remove="remove"/>
  <metric_log remove="remove"/>
  <asynchronous_metric_log remove="remove"/>
  <session_log remove="remove"/>
  <part_log remove="remove"/>
</yandex>
EOF

cat > clickhouse/clickhouse-user-config.xml << 'EOF'
<yandex>
  <profiles>
    <default>
      <log_queries>0</log_queries>
      <log_query_threads>0</log_query_threads>
    </default>
  </profiles>
</yandex>
EOF

docker compose up -d

Part 2: HTTPS with Caddy

analytics.yourdomain.com {
    reverse_proxy localhost:8000
}

Part 3: Add Your Site

  1. Visit https://analytics.yourdomain.com
  2. Register (first user = admin)
  3. + Add site
  4. Domain: yourdomain.com

You get a tracking snippet:

<script defer data-domain="yourdomain.com"
  src="https://analytics.yourdomain.com/js/script.js">
</script>

Part 4: Install Tracking Script

HTML sites

<!-- Add before </head> in your HTML: -->
<script defer data-domain="yourdomain.com"
  src="https://analytics.yourdomain.com/js/script.js">
</script>

Next.js

npm install next-plausible
// pages/_app.js or app/layout.tsx:
import PlausibleProvider from 'next-plausible'

export default function App({ Component, pageProps }) {
  return (
    <PlausibleProvider domain="yourdomain.com" 
      selfHosted
      customDomain="https://analytics.yourdomain.com">
      <Component {...pageProps} />
    </PlausibleProvider>
  )
}

WordPress

Plugin: Plausible Analytics (official plugin)
Settings → Plausible Analytics:
- Self-hosted domain: https://analytics.yourdomain.com
- Domain to track: yourdomain.com

Ghost

Admin → Settings → Code injection → Site header:
<script defer data-domain="yourdomain.com" src="https://analytics.yourdomain.com/js/script.js"></script>

Part 5: Custom Events

Track button clicks, form submissions, and custom interactions:

<!-- Track outbound links automatically: -->
<script defer data-domain="yourdomain.com"
  src="https://analytics.yourdomain.com/js/script.outbound-links.js">
</script>

<!-- Track file downloads: -->
<script defer data-domain="yourdomain.com"
  src="https://analytics.yourdomain.com/js/script.file-downloads.js">
</script>

Custom events via JavaScript

// Track a custom event:
plausible('Signup', {props: {plan: 'Pro'}})

// Track button clicks:
document.getElementById('cta-button').addEventListener('click', function() {
  plausible('CTA Click', {props: {location: 'hero'}})
})

// Track form submissions:
document.getElementById('contact-form').addEventListener('submit', function() {
  plausible('Contact Form Submit')
})

Goals

  1. Site settings → Goals → + Add goal
  2. Goal type: Custom event → name: Signup
  3. Or Pageview → path: /thank-you
  4. Dashboard shows conversion rate for this goal

Part 6: Revenue Tracking

Track revenue events for ecommerce:

// Track a purchase:
plausible('Purchase', {
  revenue: {currency: 'USD', amount: 49.99},
  props: {plan: 'Pro', billing: 'annual'}
})

Revenue goals show total revenue + average revenue per conversion.


Share your analytics publicly:

  1. Site settings → Visibility → Make dashboard public
  2. Or: Generate a shared link with a secret token
Public stats: https://analytics.yourdomain.com/yourdomain.com
Shared link: https://analytics.yourdomain.com/share/yourdomain.com?auth=TOKEN

Useful for sharing traffic data with clients or team members without giving them Plausible login access.


Part 8: Stats API

# API with your site key:
SITE_ID="yourdomain.com"
API_KEY="your-api-key"  # Settings → Account → API Keys
BASE="https://analytics.yourdomain.com/api/v1"

# Get total visitors (last 30 days):
curl "$BASE/stats/aggregate?site_id=$SITE_ID&period=30d&metrics=visitors,pageviews" \
  -H "Authorization: Bearer $API_KEY" | jq '.results'

# Top pages:
curl "$BASE/stats/breakdown?site_id=$SITE_ID&property=event:page&period=30d" \
  -H "Authorization: Bearer $API_KEY" | jq '.results[0:10]'

# Top referrers:
curl "$BASE/stats/breakdown?site_id=$SITE_ID&property=visit:source&period=30d" \
  -H "Authorization: Bearer $API_KEY" | jq '.results[0:5]'

# Real-time visitors:
curl "$BASE/stats/realtime/visitors?site_id=$SITE_ID" \
  -H "Authorization: Bearer $API_KEY"

Maintenance

# Update:
docker compose pull
docker compose up -d

# Backup:
docker exec plausible-plausible_db-1 pg_dump -U postgres plausible_db \
  | gzip > plausible-pg-$(date +%Y%m%d).sql.gz

# ClickHouse backup (event data):
tar -czf plausible-events-$(date +%Y%m%d).tar.gz \
  $(docker volume inspect plausible_event-data --format '{{.Mountpoint}}')

# Logs:
docker compose logs -f plausible

See also: Umami and Matomo

See all open source analytics tools at OSSAlt.com/categories/analytics.

Comments