Plausible Analytics: privacy-vriendelijke website statistieken met self-hosting

· 7 min lezen

Elke website-eigenaar wil weten hoeveel bezoekers er zijn, waar ze vandaan komen en welke pagina’s ze bekijken. Google Analytics is de standaard, maar komt met een prijs: je verkoopt de privacy van je bezoekers aan Google’s advertentie-imperium. Plausible Analytics is het privacy-vriendelijke alternatief. In dit artikel beschrijf ik wat Plausible is, waarom ik het op mijn eigen server draai, en de technische hindernissen die ik moest overwinnen.

Wat is Plausible?

Plausible Analytics is een open source web analytics tool — een alternatief voor Google Analytics maar dan zonder cookies, zonder tracking van persoonlijke data, en zonder toestemmingsbanners. Het is volledig AVG/GDPR-compliant out-of-the-box.

Plausible toont je in één overzichtelijk dashboard:

  • Unieke bezoekers en paginaweergaven
  • Verkeersbronnen (direct, social, zoekmachines)
  • Best bezochte pagina’s
  • Bezoekerslocaties (op landniveau, geen exacte locatie)
  • Apparaat- en browserstatistieken

De tracking script is slechts 3 KB — een fractie van Google Analytics’ 45+ KB. Dit heeft nauwelijks impact op de laadtijd van je site.

Plausible Community Edition

Plausible biedt twee smaken:

Plausible CloudPlausible Community Edition
HostingManaged, EU-servers in DuitslandSelf-hosted op eigen infra
UpdatesContinu, meerdere keren per week2x per jaar (long-term release)
Premium featuresFunnels, ecommerce, user journeysBasis analytics
SupportPremium supportCommunity support
KostenVanaf €9/maandGratis (alleen serverkosten)
LicentieProprietaryAGPLv3

Plausible CE is “free as in beer” — je draait exact dezelfde code op je eigen server, zonder ooit te betalen aan Plausible. Het project wordt gefinancierd door de cloud-abonnees, niet door investeerders of advertentiedata.

Waarom self-hosting?

Voor eddydevink.nl koos ik voor de Community Edition op mijn eigen server. Mijn overwegingen:

  1. Privacy-first: Alle bezoekersdata blijft op mijn eigen hardware. Geen derde partij die meekijkt.
  2. Geen maandelijkse kosten: Mijn NUC server draait toch al 24/7 voor Nextcloud, Forgejo en CouchDB. Plausible erbij kost niets extra.
  3. Controle: Ik bepaal zelf hoe lang data wordt bewaard, of er überhaupt data wordt gelogd, en wie erbij kan.
  4. Leerzaam: Het opzetten van een Docker Compose stack met PostgreSQL, ClickHouse, Elixir/Phoenix en nginx is een mooie technische uitdaging.
  5. Geen cookiemuur: Plausible gebruikt geen cookies, dus je hoeft geen irritante cookiebanner te tonen. Dit is zowel netter voor bezoekers als AVG-technisch juist.

De architectuur

Plausible CE draait als een Docker Compose stack met drie containers:

┌─────────────────────────────────────────────┐
│                 nginx                         │
│           (reverse proxy + HTTPS)             │
│  plausible.eddydevink.nl → 127.0.0.1:8000    │
└─────────────────┬───────────────────────────┘
                  │
    ┌─────────────┴─────────────┐
    │     Plausible (Elixir)     │
    │      v3.2.1 CE             │
    │      poort 8000 (intern)   │
    └─────┬──────────────┬───────┘
          │              │
    ┌─────┴──────┐  ┌────┴──────────┐
    │ PostgreSQL  │  │  ClickHouse    │
    │  (config)   │  │  (analytics)   │
    │  poort 5432 │  │  poort 8123    │
    └────────────┘  └───────────────┘
  • PostgreSQL — gebruikersaccounts, site-configuratie, instellingen
  • ClickHouse — column-store database voor de analytics-data. Extreem snel voor time-series queries
  • Plausible — de Elixir/Phoenix webapplicatie die het dashboard serveert en de tracking API afhandelt

De tracking integratie met Hugo is verrassend simpel. Een partial template in het theme voegt het script alleen toe in productie-builds:

{{ if hugo.IsProduction }}
<script defer data-domain="{{ .Site.BaseURL | replaceRE "^https?://(www\\.)?" "" | replaceRE "/$" "" }}"
        src="https://{{ .Site.Params.plausibleServerDomain }}/js/script.js"></script>
{{ end }}

Tijdens hugo server (lokaal ontwikkelen) wordt het script niet geladen. Alleen na hugo --minify en deployen naar productie verschijnt het.

Beveiliging

Self-hosting betekent dat je zelf verantwoordelijk bent voor de beveiliging. Dit zijn de maatregelen die ik heb genomen:

1. Alleen bereikbaar via nginx

Plausible draait op 127.0.0.1:8000 — uitsluitend bereikbaar vanaf localhost. nginx fungeert als enige ingang op poort 443 met Let’s Encrypt, security headers, en user-agent blocking.

2. Registratie uit

Na het aanmaken van het admin-account heb ik DISABLE_REGISTRATION=true gezet. Niemand kan een account registeren — zelfs niet als ze de URL weten.

3. Fail2Ban op login

Een custom jail beschermt tegen brute-force aanvallen op het login-formulier:

[plausible]
enabled = true
filter = plausible-login
maxretry = 3
bantime = 86400   # 24 uur
findtime = 43200   # 12 uur

Bij 3 foutieve inlogpogingen binnen 12 uur wordt het IP geblokkeerd in firewalld en gerapporteerd aan AbuseIPDB. Eigen IP-adressen (LAN, Tailscale, publiek) staan op de whitelist.

4. Database credentials

Het standaard PostgreSQL-wachtwoord (postgres) is vervangen. ClickHouse draait zonder authenticatie, maar is alleen bereikbaar binnen het Docker-netwerk.

5. Security headers in nginx

X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()

De installatie

Stap 1: DNS en nginx

Een nieuw A-record voor plausible.eddydevink.nl wijst naar het publieke IP van de Nextcloud server. nginx vangt het verkeer op poort 443 af en proxyt naar 127.0.0.1:8000:

server {
    server_name plausible.eddydevink.nl;
    
    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
    
    location /live/websocket {
        proxy_pass http://127.0.0.1:8000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
    }
}

De /live/websocket endpoint is nodig voor realtime updates in het dashboard — zonder deze locatie kon het dashboard niet live tonen of er nieuwe bezoekers binnenkomen.

Stap 2: Docker Compose

Plausible CE draait via een standaard Docker Compose setup. De belangrijkste aanpassing was het compose.override.yml bestand:

services:
  plausible:
    ports:
      - 127.0.0.1:8000:8000
    environment:
      - CLICKHOUSE_DATABASE_URL=http://plausible_events_db:8123/plausible_events_db

Stap 3: Hugo integratie

De tracking partial in het Hugo theme zorgt voor de juiste data-domain (zonder www. — zie probleem 1 hieronder) en laadt het script alleen in productie.

De problemen — en hoe ik ze oploste

De installatie verliep niet vlekkeloos. Drie problemen hielden me een uur bezig.

Probleem 1: www. vs naakt domein

Symptoom: Na de installatie bleef het dashboard “Awaiting your first pageview” tonen, ondanks dat de site bezocht werd met het tracking script.

Analyse: Plausible slaat domeinen op zonder www.-prefix. Wanneer je in de setup wizard www.eddydevink.nl invoert, wordt dit automatisch opgeslagen als eddydevink.nl. Maar de data-domain in het tracking script bevatte nog steeds www.eddydevink.nl. De domain lookup faalde — het event werd gedropt met de status dropped_not_found.

De ingest_counters tabel in ClickHouse onthulde dit:

eddydevink.nl     dropped_not_found    (events met 'eddydevink.nl')
plausible.eddydevink.nl  dropped_not_found  (events met verkeerde data-domain)

Geen enkel event met www.eddydevink.nl werd geteld — niet als success, niet als drop.

Oplossing: De Hugo partial stript nu de www. prefix uit baseURL met replaceRE "^https?://(www\\.)?" "". De data-domain wordt eddydevink.nl, wat exact matcht met Plausible’s database.

Probleem 2: CLICKHOUSE_DATABASE_URL ontbrak

Symptoom: API calls naar /api/event retourneerden netjes HTTP 202 Accepted, maar geen enkel event verscheen in de ClickHouse events_v2 tabel. De Elixir.Plausible.Event.WriteBuffer in het Elixir-proces was leeg — nergens in het logs verscheen een event-processing melding.

Analyse: De compose.yml van Plausible CE declareert CLICKHOUSE_DATABASE_URL als een optionele omgevingsvariabele die uit de host wordt doorgegeven. Docker Compose leest .env echter alleen voor variabele interpolatie in de YAML — niet voor het doorgeven aan containers. De environment variable CLICKHOUSE_DATABASE_URL moest expliciet in compose.override.yml onder environment: gezet worden.

Zonder deze variabele wist Plausible niet waar ClickHouse te vinden was. Events werden geaccepteerd door de HTTP handler maar de ingest pipeline kon ze nergens kwijt — een stille fout.

Oplossing: CLICKHOUSE_DATABASE_URL=http://plausible_events_db:8123/plausible_events_db toegevoegd aan compose.override.yml. Na een docker compose up -d stroomden de events direct door naar ClickHouse.

Probleem 3: Volume reset bij debuggen

Symptoom: Tijdens het debuggen draaide ik docker compose down -v (volumes verwijderen) — een standaard reflex bij Docker-problemen. Alle data was weg: gebruiker, site-configuratie, 2FA sleutels, analytics historie.

Oplossing: DISABLE_REGISTRATION tijdelijk uitgecommentarieerd, account opnieuw geregistreerd via de web-UI, site opnieuw toegevoegd, en DISABLE_REGISTRATION weer aangezet. De 2FA moest opnieuw ingesteld worden. Les geleerd: -v is destructief — next time eerst docker compose down (zonder -v) proberen.

Conclusie

Plausible Analytics is nu live op https://plausible.eddydevink.nl. De Hugo site op www.eddydevink.nl stuurt bezoekersgegevens naar de self-hosted instantie. Het dashboard toont real-time stats: unieke bezoekers, paginaweergaven, verkeersbronnen, landen, browsers — allemaal zonder cookies, zonder privacy-schendingen, en zonder maandelijkse kosten.

De drie technische hindernissen — domein-stripping, ontbrekende ClickHouse URL, en volume-reset — lijken achteraf eenvoudig, maar het ontbreken van duidelijke foutmeldingen maakte de diagnose lastig. Events verdwenen spoorloos; nergens in de logs was een error te vinden. De oplossing lag in het methodisch doorlopen van de pipeline: nginx access log → API response → in-memory WriteBuffer → ClickHouse ingest_countersevents_v2 tabel.

Wat ik heb geleerd:

  • Plausible stript www. van domeinen — altjijd controleren of data-domain matcht met de database
  • Docker Compose .env bestanden zijn voor YAML-interpolatie, niet voor container environment
  • docker compose down -v is een kernwapen — gebruik het met beleid
  • Fail2Ban voor Plausible vereist een custom filter (plausible-login.conf) omdat Plausible foute logins retourneert met HTTP 200 (form re-render), niet met 401/422

Benieuwd naar Plausible? De Community Edition is gratis en relatief eenvoudig te installeren op elke server met Docker. De source code staat op GitHub.