How to Self-Host Plausible Analytics: Privacy-First Web Analytics 2026
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
- Visit
https://analytics.yourdomain.com - Register (first user = admin)
- + Add site
- 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
- Site settings → Goals → + Add goal
- Goal type: Custom event → name:
Signup - Or Pageview → path:
/thank-you - 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.
Part 7: Shared Dashboard Links
Share your analytics publicly:
- Site settings → Visibility → Make dashboard public
- 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 all open source analytics tools at OSSAlt.com/categories/analytics.