
Modern web analytics is plagued by heavyweight tracking scripts, invasive cross-site profiling, and labyrinthine compliance requirements under GDPR, CCPA, and PECR. Traditional platforms like Google Analytics 4 (GA4) inject multi-megabyte scripts, demand intrusive cookie consent banners, and route sensitive user metadata through centralized third-party servers. For engineering teams and privacy-conscious operators, this represents a severe performance bottleneck and compliance liability.
Plausible Analytics provides a lightweight, open-source, and privacy-focused alternative. Its tracking script is under 1 KB—over 45 times smaller than GA4—and collects zero personal data, completely eliminating the legal requirement for cookie banners. Behind the scenes, Plausible pairs PostgreSQL for transactional data (sites, user accounts, and billing) with ClickHouse—an ultra-fast columnar analytical database capable of processing millions of event rows per second on minimal CPU and RAM.
Architecture & Data Pipeline
Understanding the interplay between Plausible’s microservices is essential for building a reliable, production-ready deployment:
+-------------------------------------------------------------------------------+
| CLIENT BROWSER |
| Fetches lightweight script (<1KB) & dispatches beacon POST |
+-------------------------------------------------------------------------------+
|
HTTPS (Port 443 / TLS)
v
+-------------------------------------------------------------------------------+
| REVERSE PROXY (Caddy / Nginx / Traefik) |
| Terminates TLS, sets X-Forwarded-For, proxies to :8000 |
+-------------------------------------------------------------------------------+
|
Internal Bridge Network
v
+-------------------------------------------------------------------------------+
| PLAUSIBLE ANALYTICS CORE ENGINE |
| (Elixir / Phoenix Application - Port 8000) |
| |
| +-----------------------+ +-----------------------+ |
| | PostgreSQL 16 Engine | | ClickHouse Database | |
| | Users, Sites, Goals | | Raw Analytics Events | |
| | (Transactional DB) | | (Columnar Storage) | |
| +-----------------------+ +-----------------------+ |
+-------------------------------------------------------------------------------+
Step 1: System Requirements & Host Preparation
ClickHouse is exceptionally efficient at analytical aggregation, but requires baseline system headroom during schema migrations and bulk writes. For moderate traffic (up to 1,000,000 monthly pageviews), the following baseline is recommended:
- Compute: 2 vCPUs (x86_64 or ARM64)
- Memory: 4 GB RAM (with 2 GB swap space)
- Storage: 20 GB fast NVMe or SSD storage
- Operating System: Ubuntu 24.04 LTS, Debian 12, or AlmaLinux 9
Create the project directory structure on your host machine:
sudo mkdir -p /opt/plausible/data/db-data
sudo mkdir -p /opt/plausible/data/event-data
sudo mkdir -p /opt/plausible/data/event-logs
sudo mkdir -p /opt/plausible/config
cd /opt/plausible
Step 2: Configuring ClickHouse Custom Parameters
Plausible requires custom ClickHouse XML configuration files to disable unnecessary logging tables and configure user access. Create /opt/plausible/config/clickhouse-config.xml:
<clickhouse>
<logger>
<level>warning</level>
<console>1</console>
</logger>
<query_log remove="1"/>
<trace_log remove="1"/>
<text_log remove="1"/>
<metric_log remove="1"/>
<asynchronous_metric_log remove="1"/>
<session_log remove="1"/>
<part_log remove="1"/>
</clickhouse>
Next, configure ClickHouse user access rules in /opt/plausible/config/clickhouse-user-config.xml:
<clickhouse>
<profiles>
<default>
<max_memory_usage>2000000000</max_memory_usage>
<use_uncompressed_cache>0</use_uncompressed_cache>
<load_balancing>random</load_balancing>
</default>
</profiles>
<users>
<default>
<password remove="1"/>
<password>plausible_clickhouse_secret</password>
<networks>
<ip>::/0</ip>
</networks>
<profile>default</profile>
<quota>default</quota>
</default>
</users>
</clickhouse>
Step 3: Generating Application Secrets & Environment File
Plausible relies on a 64-byte base64 secret key for session cookie encryption. Generate this string directly using OpenSSL:
# Generate a 64-byte secret key base
openssl rand -base64 64 | tr -d '\n' ; echo
Create the environment file /opt/plausible/plausible-conf.env with strict permissions:
# Operational Environment Configuration
BASE_URL=https://analytics.example.com
SECRET_KEY_BASE=PASTE_YOUR_GENERATED_64_BYTE_BASE64_KEY_HERE
# Database Connections
DATABASE_URL=postgres://plausible:postgres_secure_pass@plausible_db:5432/plausible_db
CLICKHOUSE_DATABASE_URL=http://default:plausible_clickhouse_secret@plausible_events_db:8123/plausible_events_db
# Security & Access Controls
DISABLE_REGISTRATION=invite_only
# Mail Delivery (SMTP Configuration for password resets & weekly digest reports)
MAILER_EMAIL=notifications@example.com
SMTP_HOST_ADDR=smtp.sendgrid.net
SMTP_HOST_PORT=587
SMTP_USER_NAME=apikey
SMTP_USER_PWD=YOUR_SMTP_API_KEY
SMTP_HOST_SSL_ENABLED=false
sudo chmod 600 /opt/plausible/plausible-conf.env
Step 4: Production-Ready Docker Compose Stack
Construct /opt/plausible/docker-compose.yml. The stack defines isolated volumes, health checks, and restart policies to guarantee zero downtime:
services:
mail:
image: bytemark/smtp
restart: unless-stopped
environment:
- RELAY_HOST=${SMTP_HOST_ADDR}
- RELAY_PORT=${SMTP_HOST_PORT}
- RELAY_USER=${SMTP_USER_NAME}
- RELAY_PASSWORD=${SMTP_USER_PWD}
networks:
- plausible-net
plausible_db:
image: postgres:16-alpine
restart: unless-stopped
environment:
- POSTGRES_USER=plausible
- POSTGRES_PASSWORD=postgres_secure_pass
- POSTGRES_DB=plausible_db
volumes:
- /opt/plausible/data/db-data:/var/lib/postgresql/data
networks:
- plausible-net
healthcheck:
test: ["CMD-SHELL", "pg_isready -U plausible -d plausible_db"]
interval: 10s
timeout: 5s
retries: 5
plausible_events_db:
image: clickhouse/clickhouse-server:24.3-alpine
restart: unless-stopped
volumes:
- /opt/plausible/data/event-data:/var/lib/clickhouse
- /opt/plausible/data/event-logs:/var/log/clickhouse-server
- /opt/plausible/config/clickhouse-config.xml:/etc/clickhouse-server/config.d/logging.xml:ro
- /opt/plausible/config/clickhouse-user-config.xml:/etc/clickhouse-server/users.d/logging.xml:ro
networks:
- plausible-net
ulimits:
nofile:
soft: 262144
hard: 262144
plausible:
image: plausible/analytics:v2.1.4
restart: unless-stopped
command: sh -c "sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh run"
depends_on:
plausible_db:
condition: service_healthy
plausible_events_db:
condition: service_started
env_file:
- /opt/plausible/plausible-conf.env
ports:
- "127.0.0.1:8000:8000"
networks:
- plausible-net
networks:
plausible-net:
name: plausible-net
driver: bridge
Step 5: Launching & Provisioning the First Admin Account
Launch the stack and track the database migration logs:
cd /opt/plausible
docker compose up -d
# Follow the startup logs
docker compose logs -f plausible
Once you see Running Plausible.Web.Endpoint with Bandit, the server is listening. Create your primary administrative account using the CLI entrypoint:
docker compose exec plausible /entrypoint.sh db admin \
"Admin User" \
"admin@example.com" \
"YourStrongPassword123!"
Step 6: Reverse Proxy Integration (Caddy & Nginx)
Never expose Plausible’s port 8000 directly to the internet. Always terminate SSL via a hardened reverse proxy.
Option A: Hardened Caddy Configuration
analytics.example.com {
encode gzip zstd
reverse_proxy 127.0.0.1:8000 {
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
}
# Strict Security Headers
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
Referrer-Policy "strict-origin-when-cross-origin"
}
}
Option B: Nginx Virtual Host
server {
listen 443 ssl http2;
server_name analytics.example.com;
ssl_certificate /etc/letsencrypt/live/analytics.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/analytics.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1: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;
}
}
Step 7: Embedding the Tracking Script
Log in to your Plausible dashboard at https://analytics.example.com, click + Add Website, and register your domain name (e.g., example.com). Add the generated snippet into the <head> section of your site:
<!-- Plausible Analytics: Zero-Cookie, Lightweight Privacy Tracking -->
<script defer data-domain="example.com" src="https://analytics.example.com/js/script.js"></script>
Because Plausible does not collect identifiers, place persistent device cookies, or track visitors across disparate websites, no cookie banner or user consent dialog is legally required under the EU ePrivacy Directive.
Troubleshooting Common Deployment Issues
1. ClickHouse Fails with “Cannot allocate memory” or Crashing Under Load
Cause: ClickHouse attempts to reserve significant memory buffers by default. On low-memory VPS instances (under 4 GB RAM), the Linux OOM killer terminates the process.
Solution: Ensure clickhouse-user-config.xml specifies <max_memory_usage>2000000000</max_memory_usage> (capping memory to 2 GB). Additionally, verify that ulimits.nofile is set to at least 262144 in docker-compose.yml.
2. Browser AdBlockers Intercepting Tracking Events
Cause: Aggressive browser extensions (like uBlock Origin) maintain heuristics that match /js/script.js and /api/event queries on popular domains.
Solution: Plausible supports custom script extensions and URL proxying. You can proxy the script through your website’s main domain (e.g. https://example.com/stats/telemetry.js) via Nginx or Cloudflare Workers, ensuring 100% telemetry capture for legitimate users.
3. CSRF Verification Failures or “Invalid Base URL”
Cause: The BASE_URL defined in plausible-conf.env does not precisely match the protocol and domain in the visitor’s browser bar (e.g., mismatch between http:// and https:// or missing port numbers).
Solution: Ensure BASE_URL strictly reflects your public HTTPS URL (e.g. BASE_URL=https://analytics.example.com) without any trailing slashes, and confirm your reverse proxy passes X-Forwarded-Proto https upstream.
Conclusion
Self-hosting Plausible Analytics with Docker Compose and ClickHouse gives you total data sovereignty over your web metrics. You get clean, real-time analytics with sub-second dashboard rendering, eliminate third-party tracking liability, and deliver a faster, privacy-respecting browsing experience to your audience.
Hi, I’m Mark, the author of Clever IT Solutions: Mastering Technology for Success. I am passionate about empowering individuals to navigate the ever-changing world of information technology. With years of experience in the industry, I have honed my skills and knowledge to share with you. At Clever IT Solutions, we are dedicated to teaching you how to tackle any IT challenge, helping you stay ahead in today’s digital world. From troubleshooting common issues to mastering complex technologies, I am here to guide you every step of the way. Join me on this journey as we unlock the secrets to IT success.


