Een publieke statuspagina maken zonder Statuspage.io: self-hosted, in 30 minuten, op een andere server dan je product
Een statuspagina op dezelfde server als de dienst die ze beschrijft, is down wanneer je haar nodig hebt. Dit is de opzet die wél werkt: Uptime Kuma op een aparte, goedkope VPS, checks van buitenaf op HTTP, TLS en TCP, een publieke pagina op status.jouwdomein.be met een eigen certificaat, incident-updates die je vanaf je telefoon plaatst, en de DNS- en cache-details die bepalen of de pagina bereikbaar blijft als de rest brandt.
Inhoud
- De ene regel die alles bepaalt
- Stap 1: Uptime Kuma installeren
- Stap 2: publiek maken met eigen domein en TLS
- Stap 3: de checks — van buitenaf, op wat de klant ziet
- Stap 4: de publieke pagina
- Stap 5: incidenten — vanaf je telefoon
- Stap 6: notificaties en de heartbeat van de statuspagina zelf
- Onderhoud
- Valkuilen
- Wat je hiermee nog niet hebt
- Zo doet monsys het
- FAQ
Tijdens een storing doen klanten drie dingen: ze herladen de pagina, ze mailen support, en ze zoeken naar een statuspagina. Als die er niet is, of als ze mee down is met de rest, verdubbelt je supportbelasting op het moment dat je handen nodig hebt om te fixen. Statuspage.io en consorten lossen dat op voor 29 tot 99 dollar per maand en met je klantdata in de VS. Dit is de versie op je eigen VPS, met dezelfde functies, voor de prijs van de kleinste server bij je hoster.
De ene regel die alles bepaalt
De statuspagina draait niet op de infrastructuur die ze bewaakt. Andere hoster, of minstens ander datacenter, andere DNS-provider als het even kan, eigen certificaat. Een statuspagina die "alles OK" toont omdat ze zelf ook down is, is erger dan geen statuspagina.
Praktisch: een 1-vCPU-VPS met 1 GB RAM bij een tweede hoster (Hetzner als je bij OVH zit, of omgekeerd) kost 4 tot 5 euro per maand en draait dit met gemak.
Stap 1: Uptime Kuma installeren
Uptime Kuma is open source (MIT), heeft HTTP/TCP/DNS/ping/keyword-checks, publieke statuspagina's, incident-berichten en notificaties naar ntfy, e-mail en zo'n negentig andere kanalen. Eén container:
# Op de status-VPS
sudo apt install -y docker.io docker-compose-v2 caddy
sudo install -d /opt/status && cd /opt/status
sudo tee docker-compose.yml >/dev/null <<'EOF'
services:
kuma:
image: louislam/uptime-kuma:1
container_name: uptime-kuma
restart: unless-stopped
ports: ["127.0.0.1:3001:3001"]
volumes:
- ./data:/app/data
logging:
driver: json-file
options: { max-size: "10m", max-file: "3" }
EOF
sudo docker compose up -d
Open http://<vps-ip>:3001 via een SSH-tunnel (ssh -L 3001:127.0.0.1:3001 status-vps) en maak het admin-account aan. Doe dat nu: de eerste bezoeker van een verse installatie mag het admin-account kiezen.
Stap 2: publiek maken met eigen domein en TLS
# DNS: status.example.be → A-record naar de status-VPS, TTL 300 (niet 86400!)
sudo tee /etc/caddy/Caddyfile >/dev/null <<'EOF'
status.example.be {
reverse_proxy 127.0.0.1:3001
header {
# Geen caching van de statuspagina door tussenliggende proxies/CDN's
Cache-Control "no-store"
X-Frame-Options "SAMEORIGIN"
}
encode gzip
}
EOF
sudo systemctl reload caddy
curl -sI https://status.example.be | head -3 # 200, cert van Let's Encrypt
Caddy regelt het certificaat. Twee details die er tijdens een storing toe doen:
- DNS-TTL van 300 seconden. Als je de status-VPS ooit moet vervangen, wil je binnen vijf minuten het A-record kunnen omzetten, niet binnen een dag.
Cache-Control: no-store. Een CDN of bedrijfsproxy die de pagina van vanmorgen cachet, toont "operational" terwijl je incident al twee uur loopt.
Stap 3: de checks — van buitenaf, op wat de klant ziet
In de Kuma-UI, Add New Monitor. Per dienst één check die doet wat een bezoeker doet:
| Dienst | Type | Instelling |
|---|---|---|
| Website / app | HTTP(s) – Keyword | URL van de loginpagina, keyword Inloggen (niet alleen status 200: een foutpagina geeft ook 200) |
| API | HTTP(s) – Json Query | https://api.example.be/healthz, query $.status == "ok" |
| TCP Port | mail.example.be:587 en :993 | |
| DNS | DNS | je eigen domein tegen je eigen nameserver, verwacht record |
| TLS | (zit in HTTP-check) | Certificate Expiry Notification aan, 14 dagen |
Interval 60 s, retries 2 (dus down na 3 mislukte pogingen = ~3 minuten; flap-bescherming), Upside Down Mode uit. Zet in de check de Accepted Status Codes op 200-299 — niet 200-399, anders telt een redirect naar een foutpagina als up.
Voor diensten achter een firewall die alleen intern bereikbaar zijn, gebruik je een Push-monitor: de interne host meldt zich elke minuut met een curl naar Kuma, en Kuma alarmeert als de push uitblijft. Zo blijft Kuma buiten je netwerk en zie je toch interne diensten.
# Op de interne host, cron elke minuut:
* * * * * curl -fsS -m 10 "https://status.example.be/api/push/AbCdEf1234?status=up&msg=OK" >/dev/null
Stap 4: de publieke pagina
Status Pages → New Status Page: slug main, titel, en sleep de monitors in groepen ("Website", "API", "E-mail"). Wat je niet op de publieke pagina zet:
- De URL's en poorten van je checks (Kuma toont ze niet, tenzij je de monitor-naam een URL geeft — noem hem "Klantportaal", niet "https://app.example.be/login").
- Interne diensten (database, Redis, back-up). De klant heeft er niets aan en het is informatie voor een aanvaller.
- Response-tijden-grafieken als je die niet wilt uitleggen; "Show Certificate Expiry" mag wel — het is een signaal van zorgvuldigheid.
Zet Custom domain op status.example.be en een korte Description met wat de klant moet doen bij een storing ("Voor dringende vragen: support@example.be — gebruik geen e-mail via het platform tijdens een storing").
Stap 5: incidenten — vanaf je telefoon
Een statuspagina zonder incident-berichten toont alleen rood en groen. Wat een klant wil lezen: wat er is, wie eraan werkt en wanneer de volgende update komt. In Kuma: op de statuspagina Create Incident, titel, tekst, stijl (info/warning/danger). Dat werkt vanaf de mobiele browser.
Hou het formaat vast, dan kost een update 30 seconden:
[14:05] Onderzoek — De API antwoordt traag sinds 13:50. We onderzoeken de oorzaak. Volgende update om 14:30.
[14:28] Geïdentificeerd — Oorzaak: volle schijf op de databaseserver. Opruimen loopt. Volgende update om 15:00.
[14:52] Opgelost — Schijfruimte hersteld om 14:45; API-responstijden normaal sinds 14:47. Post-mortem volgt binnen 2 werkdagen.
Sla incidenten niet alleen op de pagina op. Kopieer de tijdlijn na afloop naar je incidentregister — dat is NIS2-bewijsstuk 12, en de "early warning binnen 24 uur"-klok begint bij de eerste regel.
Stap 6: notificaties en de heartbeat van de statuspagina zelf
Settings → Notifications → ntfy: topic status, en koppel hem aan alle monitors (Apply on all existing monitors). Zo krijg je het bericht vóór de klant het ziet. Zie on-call zonder PagerDuty voor de escalatie.
En bewaak de bewaker: een monitor op een andere plek (je hoofdserver, of de gratis tier van een externe dienst) die https://status.example.be elke 5 minuten controleert. Kuma kan zichzelf niet melden als hij down is.
# Op je hoofdserver (die géén status-VPS is): cron elke 5 min
*/5 * * * * curl -fsS -m 10 -o /dev/null https://status.example.be || curl -s -H "Title: STATUS PAGE DOWN" -H "Priority: urgent" -d "status.example.be unreachable" https://ntfy.example.be/oncall
Onderhoud
# Back-up van de Kuma-data (SQLite + config): dagelijks, naar een andere plek
0 4 * * * root tar -C /opt/status -czf /var/backups/kuma-$(date +\%F).tgz data && find /var/backups -name 'kuma-*.tgz' -mtime +14 -delete
# Updates: Kuma via de image-tag; de VPS via unattended-upgrades
cd /opt/status && sudo docker compose pull && sudo docker compose up -d
Valkuilen
- Statuspagina op dezelfde server. Nogmaals, want het is de fout die iedereen één keer maakt. Ook "een andere VM bij dezelfde hoster in hetzelfde rack" telt als dezelfde server wanneer de hoster een netwerkstoring heeft.
- Alleen HTTP 200 checken. Een maintenance-pagina, een WAF-blokkade en een lege app geven alle drie 200. Keyword- of JSON-checks kijken naar de inhoud.
- Te agressief interval. 20 s met 0 retries geeft een pagina die bij elke netwerkhik rood knippert. 60 s met 2 retries is de sweet spot voor een publieke pagina; je interne monitoring mag scherper.
- Vergeten dat de pagina publiek is. Monitor-namen, incident-teksten en beschrijvingen zijn leesbaar voor iedereen, inclusief concurrenten en aanvallers. Geen hostnamen, geen "database-server 3 is vol".
- Geen eigenaar voor updates. Een incident zonder update in 45 minuten leest als "ze weten het niet". Spreek af wie tijdens een incident de pagina bijhoudt — niet degene die aan het fixen is.
Wat je hiermee nog niet hebt
- De koppeling met de oorzaak. Kuma ziet dat de API traag is; waarom (schijf vol, CPU, een deploy) staat in je servermonitoring. Twee systemen, twee logins, midden in een incident.
- Toegang per klant. Eén publieke pagina voor iedereen. Een MSP wil per klant een eigen pagina met alleen hún diensten, eventueel achter een wachtwoord of token.
- Bewijs. De uptime-percentages op de pagina zijn wat Kuma heeft gemeten; voor een SLA-rapport of een NIS2-dossier wil je ze ondertekend, met de ruwe metingen erbij.
Zo doet monsys het
De uptime-checks in monsys draaien vanaf de hub — een andere locatie dan je servers — met HTTP-, TLS- en TCP-checks, twee opeenvolgende fouten voordat iets down heet, en certificaatverloop-alerts op 14 dagen. Elke tenant publiceert statuspagina's onder een eigen slug, publiek, via een geheime link of met wachtwoord — zodat een MSP per klant een pagina heeft. Een down-check is een gewone alert in dezelfde pijplijn als de agent-metrics, dus de statuspagina en de oorzaak (schijf vol op de databasehost) staan in hetzelfde scherm. De metingen landen in de SLA-engine en het ondertekende maandrapport.
FAQ
Welke self-hosted statuspagina is het makkelijkst?
Uptime Kuma: één container, checks en statuspagina in één, incident-berichten ingebouwd, actief onderhouden. Alternatieven: Gatus (configuratie als YAML, geen UI voor incidenten), Cachet (alleen statuspagina, checks apart), Statping-ng.
Moet de statuspagina echt op een andere hoster?
Ja, als je wilt dat ze werkt tijdens een storing bij je hoster — en dat is precies wanneer je haar nodig hebt. Minimaal een ander datacenter; idealiter een andere hoster én een andere DNS-provider voor het status-subdomein.
Hoe krijg ik de statuspagina op mijn eigen domein?
Een A-record status.example.be naar de status-VPS (TTL 300), Caddy of nginx als reverse proxy met automatisch Let's Encrypt-certificaat, en in Kuma het custom domain instellen op de statuspagina. Geen wildcard-certificaat van je hoofddomein hergebruiken: als dat verloopt of gecompromitteerd is, wil je dat de statuspagina er los van staat.
Geschreven door het monsys-team — sysadmins die dit dagelijks doen.
Zelf gedaan? Laat monsys het bijhouden.
Alles uit deze how-to draait in monsys als doorlopende check, met historie, alerts en audit-bewijs. 5 servers gratis, EU-gehost in België, geïnstalleerd in 60 seconden.