$ xmrhost-cli docs show --topic=setup-plausible-self-host
[$ ] doc: setup-plausible-self-host
// Self-host Plausible Analytics on an offshore VPS — Docker compose, Caddy TLS, brand-distinct deployment
// published=2026-05-11 · updated=2026-05-11 · diff=intermediate · read=22min · tags=[plausible, analytics, self-host, docker, caddy, privacy] · by=0xLambda
// ABSTRACT
abstract
Full procedure for running Plausible Analytics Community Edition on a small offshore VPS, fronted by Caddy with direct TLS (no Cloudflare). Covers the Docker-compose layout, the env-var matrix, the events-database (ClickHouse) tuning for a single-tenant deployment, the brand-domain configuration, and the post-launch hardening checks. Aimed at small operators running a single analytics instance per brand. Plausible CE is AGPLv3 — self-hosting is permitted; redistribution under modified terms requires AGPL compliance.
Scope and assumptions
Self-hosted Plausible Analytics Community Edition (CE) on a dedicated offshore VPS, fronted by Caddy with direct Let’s Encrypt TLS (no Cloudflare, no CDN-edge layer — those would defeat the privacy-tech posture this runbook is wired into). Single-tenant deployment: one Plausible instance per brand domain. Plausible CE is AGPLv3, the official self-host route, maintained by Plausible Insights ehf (Iceland).
This runbook is opinionated:
- Dedicated VPS, not co-located. Plausible-ClickHouse-Postgres on the same machine as the production app surface contaminates the threat model. Pick a separate VPS for the analytics stack —
vps-1($15/mo) is enough for a single brand under 100k pageviews/month. - No Cloudflare. The brand voice across
/legal/privacyand/threat-models#matrix-homeserverrules out US-incorporated TLS-terminating intermediaries. Caddy serves direct. - No third-party analytics on top of Plausible. The point of self-hosting analytics is that no third party sees the visitor data. Layering Google Analytics or Hotjar on top defeats the deployment entirely.
- Domain on a brand-distinct subdomain.
plausible.<brand>.ioorstats.<brand>.io— never the apex. Reduces cross-domain cookie / fingerprinting concerns.
Step 1 — provision the VPS
Order a vps-1 plan at /node/vps/vps-1. $15/mo, 1 vCPU / 2 GB RAM / 30 GB NVMe in Iceland or Romania. Pay in Monero per /guide/buy-vps-with-monero — the analytics infrastructure should be on the same payment posture as the production brand.
Standard provisioning ships Debian 12 (bookworm), Ed25519-only sshd (/docs/harden-sshd), KSPP-baseline kernel, auditd, restrictive nftables. Plausible runs in containers on top of this baseline.
Step 2 — DNS
Point a brand-distinct subdomain at the VPS IP:
TTL 300s during the bring-up so you can fix mistakes fast. Bump to 3600s after the cert provisions.
Step 3 — install Docker + Compose
Step 4 — clone Plausible’s self-host repo
The official self-host repository is maintained at github.com/plausible/community-edition. As of 2026 the canonical clone path:
Step 5 — configure the env file
The repo ships a plausible-conf.env.example. Copy and edit:
Edit the values that matter:
# /opt/plausible/community-edition/plausible-conf.env
# REQUIRED — base URL where the dashboard lives
BASE_URL=https://plausible.<brand>.io
# REQUIRED — random base64 secrets (generate fresh, do NOT reuse)
SECRET_KEY_BASE=<openssl rand -base64 48>
TOTP_VAULT_KEY=<openssl rand -base64 32>
# OPTIONAL — disable registration after creating the first admin account
DISABLE_REGISTRATION=invite_only
# OPTIONAL — outbound email (for invites / password reset)
# If left blank, Plausible silent-skips email delivery
MAILER_EMAIL=noreply@<brand>.io
SMTP_HOST_ADDR=smtp.<your-relay>.io
SMTP_HOST_PORT=587
SMTP_USER_NAME=<smtp-user>
SMTP_USER_PWD=<smtp-pass>
# OPTIONAL — IP geolocation database
# Plausible CE supports MaxMind GeoLite2 via a separate license; for
# privacy-focused deployments, leave blank — country-level resolution
# isn't necessary for most operators
MAXMIND_LICENSE_KEY=
# OPTIONAL — disable subscription / billing features (CE has no billing,
# but the env var must be set to avoid the upgrade UI surfacing)
ENABLE_EMAIL_VERIFICATION=false
Generate the secrets:
Step 6 — set up Caddy ingress
Plausible’s official compose file binds the app on port 8000 by default. Front it with Caddy (already running on the brand’s standard offshore image):
Verify the cert provisions (Caddy hits Let’s Encrypt direct, no Cloudflare in the way):
Step 7 — boot the stack
First boot does the Postgres migration + the ClickHouse schema apply. Takes ~30-60 seconds. Watch for Started application in the logs — that’s the readiness signal.
If migrations fail, the most common cause is a stale plausible-conf.env that pre-dates the version pinned in step 4. Re-read the release notes and re-apply.
Step 8 — create the admin account
Browse to https://plausible.<brand>.io/register. Create the first admin account. Once that’s done, edit plausible-conf.env and set:
DISABLE_REGISTRATION=disable
Then docker compose up -d to apply. This locks the registration form so no other accounts can be created against the public URL.
Step 9 — add the site + paste the snippet
In the Plausible dashboard, click “Add a new website”. Enter your brand domain (e.g. xmrhost.io — NOT the Plausible subdomain; the domain you want to track). Plausible generates a snippet:
<script defer
data-domain="<your-brand>.io"
src="https://plausible.<brand>.io/js/script.js">
</script>
Paste in your site’s <head> — for an Astro site, this goes in src/layouts/Base.astro next to the existing <OrganizationJsonLd /> block. For a static site, in index.html. For XMRHost-family brands, the convention is to gate the snippet behind a PUBLIC_PLAUSIBLE_DOMAIN env var and inject only when set.
Visit the brand domain in a clean browser tab; refresh; check the Plausible dashboard. The first pageview lands in ~30 seconds.
Step 10 — hardening checks (post-launch)
All 6 must pass. If any return EXPOSED, the docker-compose-binding default leaked a service to 0.0.0.0; fix by setting 127.0.0.1: prefix on every ports: entry in the compose file.
Step 11 — backup strategy
Plausible’s state lives in two databases:
Rsync the backup directory off-host to a separate VPS in a different region (Romania backup of Iceland Plausible, or vice versa — same pattern as the brand’s archive backup, see /guide/offshore-hosting-for-journalists).
Step 12 — upgrade path
Plausible CE releases roughly every 6-8 weeks. The upgrade path:
Read the release notes before each upgrade. Plausible has had two breaking schema-migration events in the CE lifetime; both required a manual clickhouse migration step the standard docker compose up did not handle. The release notes flag these.
Step 13 — privacy properties of the deployment
What this deployment does NOT do, by design:
- No cookies, no localStorage. Plausible’s script uses no client-side state; visitor identification is by daily-rotating hash of (IP + UA + salt), never reaching individual identity.
- No IP retention. The hashing salt rotates daily; the IP is not stored in the events DB.
- No cross-site fingerprinting. Self-hosted means no third party sees any visitor data. The events stay on the VPS.
- No cookie-consent banner required in most jurisdictions (CNIL, ICO, EDPB guidance all treat properly-configured Plausible as exempt). Verify against your operating jurisdiction’s regulator. Plausible’s docs cover the GDPR / CCPA / PECR alignment.
- No outbound to plausible.io. The self-host stack does NOT phone home to the SaaS Plausible. Verifiable in step 10 check #2.
See also
- /docs/harden-sshd — the sshd baseline the analytics VPS inherits.
- /docs/kernel-hardening-checklist — KSPP kernel that Plausible runs on top of.
- /threat-models#matrix-homeserver — adjacent self-host threat model.
- /legal/privacy — privacy policy referencing the Plausible self-host deployment.
- /guide/offshore-hosting-for-journalists — newsroom topology including the analytics layer.
Upstream references:
// END OF DOC
$ cd /docs # back to the manual