Plausible Analytics: Privacy-First Web Analytics for Your Homelab
Deploy Plausible Analytics, a privacy-friendly Google Analytics alternative. Self-hosted, GDPR compliant, and no cookie consent needed.
Introduction
Web analytics shouldn’t require sacrificing user privacy or dealing with complex cookie consent banners. Plausible Analytics offers a refreshing alternative to Google Analytics: lightweight, privacy-focused, and remarkably straightforward.
With 19,000+ stars on GitHub and adoption by companies like Firefox, GitLab, and Tailwind CSS, Plausible has proven that ethical analytics can be both powerful and practical. Its clean interface shows you everything you need while respecting visitor privacy.
What You’ll Learn:
- Deploy Plausible Analytics with Docker Compose in under 30 minutes
- Configure privacy-first tracking without cookies or consent banners
- Set up GDPR-compliant analytics that work across countries
- Create custom event tracking for conversions and goals
- Integrate with reverse proxies for secure access
- Share public dashboards with stakeholders
Prerequisites:
- Docker and Docker Compose installed (v20.10+ and v2.0+)
- Basic understanding of DNS and web hosting
- 1GB+ free disk space (2-5GB recommended for production)
- Domain name with ability to add DNS records (optional but recommended)
Why Plausible Analytics?
The Privacy Problem with Traditional Analytics
Traditional analytics platforms like Google Analytics face several challenges:
- Privacy violations: Track users across websites, collect personal data
- Cookie consent: Require annoying banners that hurt conversion rates
- Complex interfaces: GA4’s learning curve frustrates non-technical users
- Data ownership: Your analytics data lives on someone else’s servers
- GDPR compliance: Difficult to ensure full compliance with EU regulations
How Plausible Is Different
Plausible Analytics takes a radically different approach:
Privacy by Design:
- No cookies, no fingerprinting, no cross-site tracking
- All data is aggregate only—never personal
- GDPR, CCPA, and PECR compliant by default
- No need for cookie consent banners
Simplicity:
- One-page dashboard shows everything at a glance
- Setup takes minutes, not hours
- Lightweight script (less than 1KB vs GA’s 45KB+)
- Clean, intuitive interface anyone can understand
Self-Hosted Control:
- All data stays on your infrastructure
- No third-party access to your analytics
- Full ownership and control
- Export data anytime in CSV or JSON
Key Features
| Feature | Details |
|---|---|
| Script Size | Under 1KB (GA is 45KB+) |
| Cookie-Free | No cookies, no consent banners needed |
| Privacy | GDPR/CCPA compliant, no personal data collection |
| Dashboard | Single-page overview, real-time updates |
| Goals & Events | Custom event tracking, conversion funnels |
| Public Sharing | Share dashboards with links or embeds |
| Data Retention | Forever (or configure custom retention) |
| API Access | Full REST API for custom integrations |
Understanding Plausible Architecture
How Plausible Works
Website Visitor → Lightweight Script (1KB) → Plausible Server → ClickHouse Database
↓
PostgreSQL (metadata)
↓
Dashboard & API (Elixir/Phoenix) Components:
- JavaScript Tracker: Ultra-light script embedded in your website
- Plausible Server: Elixir/Phoenix application processing events
- ClickHouse: Columnar database storing analytics events
- PostgreSQL: Stores site configurations and user accounts
What Gets Tracked:
- Page URL and referrer source
- Browser and device type (aggregated)
- Country derived from IP (not stored)
- Screen size categories
What’s NEVER Tracked:
- IP addresses (immediately discarded)
- Personal information
- Cross-site behavior
- Cookies or local storage
- Exact geographic location
Installation: Docker Compose Method
Step 1: Create Project Directory
mkdir -p ~/plausible-analytics
cd ~/plausible-analytics Step 2: Create docker-compose.yml
This configuration provides a production-ready Plausible deployment:
version: "3.8"
services:
plausible:
image: plausible/analytics:v2.0
container_name: plausible
restart: always
command: sh -c "sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh run"
depends_on:
- plausible_db
- plausible_events_db
- mail
ports:
- "8000:8000"
env_file:
- plausible-conf.env
networks:
- plausible-network
plausible_db:
image: postgres:14-alpine
container_name: plausible_db
restart: always
volumes:
- plausible-db-data:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=postgres
networks:
- plausible-network
plausible_events_db:
image: clickhouse/clickhouse-server:23.3.7.5-alpine
container_name: plausible_events_db
restart: always
volumes:
- plausible-events-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
networks:
- plausible-network
mail:
image: bytemark/smtp
container_name: plausible_mail
restart: always
networks:
- plausible-network
volumes:
plausible-db-data:
driver: local
plausible-events-data:
driver: local
networks:
plausible-network:
driver: bridge Step 3: Create Configuration File
Create plausible-conf.env with your settings:
# Base URL for your Plausible instance
BASE_URL=https://analytics.yourdomain.com
# Secret key base (generate with: openssl rand -base64 64)
SECRET_KEY_BASE=REPLACE_WITH_RANDOM_64_CHAR_STRING
# Database URLs
DATABASE_URL=postgres://postgres:postgres@plausible_db:5432/plausible_db
CLICKHOUSE_DATABASE_URL=http://plausible_events_db:8123/plausible_events_db
# Admin user credentials (change these!)
[email protected]
ADMIN_USER_NAME=Admin
ADMIN_USER_PWD=changeme123
# Disable registration after creating your account
DISABLE_REGISTRATION=false
# SMTP settings for email notifications (optional)
[email protected]
SMTP_HOST_ADDR=mail
SMTP_HOST_PORT=25
# Timezone (optional, defaults to UTC)
# TZ=America/New_York Generate SECRET_KEY_BASE:
openssl rand -base64 64 Copy the output and replace REPLACE_WITH_RANDOM_64_CHAR_STRING in the config file.
Step 4: Create ClickHouse Configuration
Create the ClickHouse config directory and files:
mkdir -p clickhouse Create clickhouse/clickhouse-config.xml:
<clickhouse>
<logger>
<level>warning</level>
<console>true</console>
</logger>
<!-- Stop all the unnecessary logging -->
<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"/>
</clickhouse> Create clickhouse/clickhouse-user-config.xml:
<clickhouse>
<profiles>
<default>
<log_queries>0</log_queries>
<log_query_threads>0</log_query_threads>
</default>
</profiles>
</clickhouse> Step 5: Start the Stack
# Start all services
docker compose up -d
# Check container status
docker compose ps
# Watch logs for initialization
docker compose logs -f plausible Expected output:
plausible | Running migrations...
plausible | Migrations complete!
plausible | Starting Plausible Analytics...
plausible | [info] Running PlausibleWeb.Endpoint with cowboy 2.9.0 at http://0.0.0.0:8000 Step 6: Access Web Interface
Navigate to: http://YOUR_SERVER_IP:8000
Initial setup:
- You’ll be redirected to the registration page
- Enter your admin email and password (from plausible-conf.env)
- Click “Register” to create your account
- Verify your email (if SMTP is configured)
- After registration, set
DISABLE_REGISTRATION=truein plausible-conf.env - Restart:
docker compose restart plausible
Adding Your First Website
Step 1: Create Site in Dashboard
- Log into Plausible dashboard
- Click “Add a website”
- Enter your domain (e.g.,
example.com) - Select timezone for reports
- Click “Add snippet”
Step 2: Install Tracking Script
Copy the tracking script provided (looks like this):
<script defer data-domain="yourdomain.com" src="https://analytics.yourdomain.com/js/script.js"></script> Installation locations:
Static HTML sites:
<!DOCTYPE html>
<html>
<head>
<title>My Website</title>
<!-- Add just before closing head tag -->
<script defer data-domain="example.com" src="https://analytics.yourdomain.com/js/script.js"></script>
</head>
<body>
<!-- Your content -->
</body>
</html> WordPress:
- Install “Insert Headers and Footers” plugin
- Paste script in “Scripts in Header” section
- Save changes
Ghost: Add to Settings → Code Injection → Site Header
Hugo: Add to layouts/partials/head.html or your theme’s header partial
Next.js: Add to pages/_app.js or app/layout.js:
import Script from 'next/script'
export default function RootLayout({ children }) {
return (
<html>
<head>
<Script
defer
data-domain="yourdomain.com"
src="https://analytics.yourdomain.com/js/script.js"
/>
</head>
<body>{children}</body>
</html>
)
} Step 3: Verify Installation
Method 1: Real-time Dashboard
- Visit your website in a browser
- Open Plausible dashboard
- Should see “1 current visitor” within 10 seconds
Method 2: Browser DevTools
- Visit your website
- Open browser DevTools (F12)
- Go to Network tab
- Filter by “script.js”
- Should see successful request (200 OK)
Method 3: Using curl
# Test that the script is accessible
curl -I https://analytics.yourdomain.com/js/script.js
# Should return: HTTP/2 200 Setting Up Goals and Conversions
Goals track specific actions visitors take on your site: button clicks, form submissions, purchases, etc.
Creating Custom Events
Step 1: Define Goal in Dashboard
- Go to your site settings
- Click “Goals” section
- Click ”+ Add Goal”
- Choose “Custom Event”
- Name it (e.g.,
signup,purchase,download) - Save
Step 2: Trigger Events in Your Code
Button click tracking:
<button onclick="plausible('signup')">Sign Up</button> Form submission:
<form onsubmit="plausible('newsletter_signup'); return true;">
<input type="email" name="email" required>
<button type="submit">Subscribe</button>
</form> JavaScript event:
// Track any custom event
function trackDownload(filename) {
plausible('file_download', {
props: {
filename: filename
}
});
}
document.getElementById('download-btn').addEventListener('click', () => {
trackDownload('user-guide.pdf');
}); React example:
import { useEffect } from 'react';
function PricingPage() {
const handlePurchase = () => {
window.plausible('purchase', {
props: {
plan: 'pro',
price: 49
}
});
// Continue with purchase logic
};
return (
<button onClick={handlePurchase}>
Buy Pro Plan
</button>
);
} Pageview Goals
Track specific page visits:
- Go to site settings → Goals
- Click ”+ Add Goal”
- Choose “Pageview”
- Enter path (e.g.,
/thank-you,/checkout/complete) - Save
Now Plausible automatically tracks when users visit that page.
Tracking Revenue
For e-commerce tracking:
// Track purchase with monetary value
plausible('purchase', {
props: {
amount: 99.99,
currency: 'USD',
product: 'Premium Subscription'
}
}); Configure revenue goal:
- Create custom event goal named
purchase - In goal settings, enable “Track revenue”
- Plausible will sum the
amountprop for revenue reports
Reverse Proxy Configuration
For production deployments, run Plausible behind a reverse proxy with SSL.
Nginx Configuration
# /etc/nginx/sites-available/plausible
server {
listen 443 ssl http2;
server_name analytics.yourdomain.com;
# SSL certificates (Let's Encrypt recommended)
ssl_certificate /etc/letsencrypt/live/analytics.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/analytics.yourdomain.com/privkey.pem;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Plausible proxy
location / {
proxy_pass http://localhost:8000;
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_buffering off;
}
# Optional: Increase body size for imports
client_max_body_size 10M;
}
# Redirect HTTP to HTTPS
server {
listen 80;
server_name analytics.yourdomain.com;
return 301 https://$server_name$request_uri;
} Enable configuration:
sudo ln -s /etc/nginx/sites-available/plausible /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx Caddy Configuration
# /etc/caddy/Caddyfile
analytics.yourdomain.com {
reverse_proxy localhost:8000 {
header_up X-Real-IP {remote_host}
}
# Security headers
header {
X-Frame-Options "SAMEORIGIN"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
}
} Reload Caddy:
sudo systemctl reload caddy Traefik Configuration
Add labels to your docker-compose.yml:
services:
plausible:
image: plausible/analytics:v2.0
container_name: plausible
labels:
- "traefik.enable=true"
- "traefik.http.routers.plausible.rule=Host(`analytics.yourdomain.com`)"
- "traefik.http.routers.plausible.entrypoints=websecure"
- "traefik.http.routers.plausible.tls.certresolver=letsencrypt"
- "traefik.http.services.plausible.loadbalancer.server.port=8000"
networks:
- traefik-proxy
- plausible-network
networks:
traefik-proxy:
external: true
plausible-network:
driver: bridge GDPR Compliance and Data Privacy
Why Plausible Is GDPR Compliant
Plausible is designed to be compliant with GDPR, CCPA, and PECR by default:
No Personal Data Collection:
- IP addresses are not stored (used only for country detection, then discarded)
- No cross-site or cross-device tracking
- No fingerprinting techniques
- No cookies or persistent identifiers
Data Processing:
- All data is aggregate and anonymized
- No way to identify individual visitors
- Complies with “Privacy by Design” principle
Data Rights:
- No personal data means no data subject requests
- Easy data export and deletion
- Full transparency in data processing
Cookie Consent Not Required
According to GDPR guidelines, Plausible does not require cookie consent banners because:
- It doesn’t use cookies
- It doesn’t collect personal data
- It doesn’t track users across sites
- Data processing is legitimate interest (website analytics)
Legal basis: Article 6(1)(f) GDPR - Legitimate interests
Privacy Policy Language
Add this to your privacy policy:
Analytics
We use Plausible Analytics, a privacy-friendly web analytics tool, to understand
how visitors use our website. Plausible does not use cookies and does not collect
personal data. All analytics data is aggregated and anonymized.
The following information is collected:
- Page URL and referrer
- Browser type and device category (mobile, desktop, tablet)
- Country derived from IP address (IP is not stored)
- Screen size category
Data is processed on our own server at [your location] and is never shared with
third parties. You can read more about Plausible's privacy approach at:
https://plausible.io/data-policy Data Retention Settings
Configure data retention in plausible-conf.env:
# Keep data forever (default)
# No configuration needed
# Or set custom retention (in months)
# DATA_RETENTION_MONTHS=12 Sharing Dashboards
Public Dashboard Links
Share your analytics publicly without requiring login:
- Go to site settings
- Enable “Public Dashboard”
- Copy the generated link (e.g.,
https://analytics.yourdomain.com/share/example.com?auth=TOKEN) - Share the link with stakeholders
Privacy: Public dashboards show analytics but hide:
- Real-time visitor count
- Geographic details beyond country
- Specific source URLs (only domains shown)
Embed Dashboards
Embed your public dashboard in any website:
<iframe
plausible-embed
src="https://analytics.yourdomain.com/share/example.com?auth=TOKEN&embed=true&theme=light"
scrolling="no"
frameborder="0"
loading="lazy"
style="width: 1px; min-width: 100%; height: 1600px;">
</iframe>
<script async src="https://analytics.yourdomain.com/js/embed.host.js"></script> Dark theme: Change theme=light to theme=dark
Advanced Configuration
Proxy Tracking Script
To avoid ad blockers, serve the Plausible script from your own domain:
Nginx proxy:
location /js/script.js {
proxy_pass https://analytics.yourdomain.com/js/script.js;
proxy_set_header Host $host;
}
location /api/event {
proxy_pass https://analytics.yourdomain.com/api/event;
proxy_buffering on;
} Update tracking code:
<script defer data-domain="yourdomain.com" src="/js/script.js"></script> Exclude Specific Pages
Exclude admin pages or internal paths from tracking:
<script
defer
data-domain="yourdomain.com"
data-exclude="/admin/*,/api/*"
src="https://analytics.yourdomain.com/js/script.js">
</script> Track Local Development
By default, Plausible ignores localhost. To enable:
<script
defer
data-domain="yourdomain.com"
src="https://analytics.yourdomain.com/js/script.local.js">
</script> Note: Use script.local.js instead of script.js
Hash-Based Routing (SPAs)
For single-page apps with hash-based routing (e.g., example.com/#/page):
<script
defer
data-domain="yourdomain.com"
src="https://analytics.yourdomain.com/js/script.hash.js">
</script> Troubleshooting Common Issues
Issue 1: No Data Showing in Dashboard
Diagnosis:
# Check container logs
docker compose logs plausible
# Check if script loads
curl -I https://analytics.yourdomain.com/js/script.js
# Test event API
curl -X POST https://analytics.yourdomain.com/api/event -H "Content-Type: application/json" -d '{"domain":"yourdomain.com","name":"pageview","url":"https://yourdomain.com/test"}' Solutions:
- Verify BASE_URL in
plausible-conf.envmatches your access URL - Check browser console for CORS errors
- Ensure tracking script
data-domainmatches site domain in Plausible - Disable browser ad blockers for testing
- Check your website domain is correctly added in Plausible
Issue 2: Database Migration Errors
Symptoms: Plausible container crashes on startup with migration errors
Solution:
# Reset and reinitialize databases
docker compose down -v
docker volume rm plausible-analytics_plausible-db-data
docker volume rm plausible-analytics_plausible-events-data
docker compose up -d Warning: This deletes all data. Only use for fresh installations.
Issue 3: ClickHouse Memory Issues
Symptoms: plausible_events_db container using excessive memory
Solution: Limit ClickHouse memory usage in docker-compose.yml:
plausible_events_db:
image: clickhouse/clickhouse-server:23.3.7.5-alpine
environment:
- CLICKHOUSE_DB=plausible_events_db
- MAX_MEMORY_USAGE=2000000000 # 2GB limit
deploy:
resources:
limits:
memory: 2G Issue 4: Email Notifications Not Sending
Symptoms: Weekly/monthly reports not arriving
External SMTP configuration: Update plausible-conf.env:
[email protected]
SMTP_HOST_ADDR=smtp.gmail.com
SMTP_HOST_PORT=587
[email protected]
SMTP_USER_PWD=your-app-password
SMTP_HOST_SSL_ENABLED=true
SMTP_RETRIES=2 Test SMTP:
docker compose restart plausible
docker compose logs plausible | grep -i smtp Issue 5: High Disk Usage
Check storage:
docker system df -v | grep plausible Solution: Configure data retention in plausible-conf.env:
# Retain data for 12 months
DATA_RETENTION_MONTHS=12 Manual cleanup:
# Access ClickHouse
docker exec -it plausible_events_db clickhouse-client
# Check table sizes
SELECT
table,
formatReadableSize(sum(bytes)) as size
FROM system.parts
WHERE database = 'plausible_events_db'
GROUP BY table;
# Delete old data (older than 12 months)
ALTER TABLE plausible_events_db.events
DELETE WHERE timestamp < now() - INTERVAL 12 MONTH; Backup and Recovery
Backup Script
#!/bin/bash
# backup-plausible.sh
BACKUP_DIR="/backups/plausible"
DATE=$(date +%Y%m%d-%H%M%S)
mkdir -p "$BACKUP_DIR"
# Stop Plausible (keeps databases running)
cd ~/plausible-analytics
docker compose stop plausible
# Backup PostgreSQL
docker exec plausible_db pg_dump -U postgres plausible_db | gzip > "$BACKUP_DIR/postgres-$DATE.sql.gz"
# Backup ClickHouse
docker exec plausible_events_db clickhouse-client --query="BACKUP DATABASE plausible_events_db TO Disk('backups', '$DATE.zip')"
docker cp plausible_events_db:/var/lib/clickhouse/backups/$DATE.zip "$BACKUP_DIR/clickhouse-$DATE.zip"
# Backup config
cp plausible-conf.env "$BACKUP_DIR/plausible-conf-$DATE.env"
# Restart Plausible
docker compose start plausible
# Cleanup old backups (keep 30 days)
find "$BACKUP_DIR" -type f -mtime +30 -delete
echo "Backup completed: $DATE" Automate with cron:
# Daily backup at 3 AM
0 3 * * * /root/backup-plausible.sh >> /var/log/plausible-backup.log 2>&1 Restore from Backup
# Stop services
docker compose down
# Restore PostgreSQL
gunzip -c /backups/plausible/postgres-YYYYMMDD-HHMMSS.sql.gz | docker exec -i plausible_db psql -U postgres plausible_db
# Restore ClickHouse
docker cp /backups/plausible/clickhouse-YYYYMMDD-HHMMSS.zip plausible_events_db:/tmp/
docker exec plausible_events_db clickhouse-client --query="RESTORE DATABASE plausible_events_db FROM Disk('backups', 'YYYYMMDD-HHMMSS.zip')"
# Restart services
docker compose up -d Maintenance Best Practices
Update Schedule
Monthly:
# Pull latest images
cd ~/plausible-analytics
docker compose pull
# Backup before update
./backup-plausible.sh
# Update containers
docker compose up -d
# Verify functionality
docker compose logs -f plausible Monitoring:
# Check resource usage
docker stats plausible plausible_db plausible_events_db
# Check disk usage
du -sh ~/plausible-analytics/ Performance Optimization
For high-traffic sites (100k+ pageviews/month):
- Increase PostgreSQL resources:
plausible_db:
environment:
- POSTGRES_SHARED_BUFFERS=256MB
- POSTGRES_EFFECTIVE_CACHE_SIZE=1GB - Tune ClickHouse:
plausible_events_db:
environment:
- MAX_CONCURRENT_QUERIES=50
- MAX_MEMORY_USAGE=4000000000 # 4GB - Consider dedicated server for ClickHouse
Conclusion
You now have a production-ready, privacy-first analytics system that:
- Respects user privacy without sacrificing insights
- Requires no cookie consent banners
- Keeps all data under your control
- Provides beautiful, actionable dashboards
- Complies with GDPR, CCPA, and PECR
Next Steps:
- Add tracking to all your websites
- Set up goals for key conversions
- Configure email reports for weekly summaries
- Share public dashboards with stakeholders
- Explore the API for custom integrations
Remember: Start simple with basic pageview tracking, then gradually add custom events as you identify what metrics truly matter for your websites.
Additional Resources
Official:
Community:
Integrations:
Last Updated: January 2025 Version Covered: Plausible Analytics v2.0+