Een reverse proxy ontvangt al het webverkeer op poort 80 en 443 en stuurt elk verzoek door naar de juiste app op de server. Caddy is een goede keuze omdat hij zelf HTTPS-certificaten ophaalt en vernieuwt: één regel zoals reverse_proxy localhost:3000 per site is genoeg.
Deze gids installeert Caddy op Ubuntu 24.04 of Debian 12 en bouwt de configuratie stap voor stap op.
Waarom een proxy ervoor zetten?
- Eén toegangspunt. Meerdere apps op één server, elk op een eigen domein of pad.
- HTTPS op één plek. Apps praten gewoon HTTP op localhost; Caddy regelt TLS.
- Beveiliging. Apps luisteren nooit op publieke poorten. In de proxy kun je headers, rate limits of een wachtwoord toevoegen.
- Flexibiliteit. Verhuis een app naar een andere poort of server en pas één regel aan.
1. Vereisten
- Een server waarop de eerste stappen zijn gedaan.
- DNS-records voor elk domein die naar de server verwijzen (A voor IPv4, AAAA voor IPv6).
- Poort 80 en 443 open:
sudo ufw allow 80,443/tcp
sudo ufw allow 443/udp # HTTP/3
Open dezelfde poorten in je cloudfirewall als je die gebruikt.
2. Caddy installeren
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | \
sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | \
sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install -y caddy
systemctl status caddy
Het pakket draait Caddy als systemd-service, met de configuratie in /etc/caddy/Caddyfile.
3. De kleinste nuttige Caddyfile
Stel dat een app luistert op localhost:3000. Vervang het bestand door:
app.example.com {
reverse_proxy localhost:3000
}
Valideer en herlaad:
sudo caddy fmt --overwrite /etc/caddy/Caddyfile
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
Bij het eerste verzoek haalt Caddy een certificaat op, stuurt HTTP door naar HTTPS en serveert de app. Meer is het niet.
4. Meerdere apps op één server
Voeg per site een blok toe:
app.example.com {
reverse_proxy localhost:3000
}
api.example.com {
reverse_proxy localhost:8080
}
status.example.com {
reverse_proxy localhost:3001
}
Of verdeel één domein op pad:
example.com {
handle_path /api/* {
reverse_proxy localhost:8080
}
handle {
root * /var/www/site
file_server
}
}
handle_path haalt /api weg voordat het verzoek wordt doorgestuurd; gebruik handle als de backend het prefix verwacht.
5. Handige standaardinstellingen met een snippet
Snippets houden herhaalde instellingen op één plek:
(common) {
encode zstd gzip
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
-Server
}
log {
output file /var/log/caddy/access.log {
roll_size 50MiB
roll_keep 5
}
}
}
app.example.com {
import common
reverse_proxy localhost:3000
}
Maak de logmap één keer aan: sudo mkdir -p /var/log/caddy && sudo chown caddy:caddy /var/log/caddy.
6. Een beheertool beveiligen met een wachtwoord
Genereer een wachtwoordhash:
caddy hash-password
Gebruik die in het siteblok:
admin.example.com {
basic_auth {
alice <paste-hash-here>
}
reverse_proxy localhost:9000
}
In oudere Caddy-versies heet de directive basicauth. Beperk voor alles wat gevoelig is ook op adres:
admin.example.com {
@outside not remote_ip 198.51.100.0/24
respond @outside 403
reverse_proxy localhost:9000
}
Het bereik is een voorbeeld. Vul het bereik van je kantoor of VPN in; met de WireGuard-gids kan dat je VPN-subnet zijn.
7. Doorsturen naar Docker-containers
Draait Caddy op de host, bind containers dan aan localhost zodat ze niet publiek zijn:
services:
app:
image: ghcr.io/example/app:1.0
ports:
- "127.0.0.1:3000:3000"
Draait Caddy in Docker, zet hem dan in hetzelfde Compose-project en stuur door naar servicenamen (reverse_proxy app:3000). De Docker Compose-gids laat een complete stack zien.
8. Load balancing over meerdere backends
Caddy kan verzoeken verdelen over meerdere app-instanties, op dezelfde server of op andere servers via een privénetwerk:
app.example.com {
reverse_proxy 10.0.0.11:3000 10.0.0.12:3000 {
lb_policy least_conn
health_uri /healthz
health_interval 10s
}
}
De adressen zijn voorbeelden uit een privébereik. Wil je de verdeling buiten je servers houden, dan doet een beheerde load balancer hetzelfde werk met health checks, en die kan ook servers zonder publiek IP bereiken.
9. WebSockets en echte client-IP's
WebSockets werken via reverse_proxy zonder extra instellingen. Caddy stuurt X-Forwarded-For, X-Forwarded-Proto en X-Forwarded-Host naar de backend; stel je app zo in dat hij die alleen vanaf 127.0.0.1 vertrouwt.
Staat Caddy zelf achter een andere proxy of load balancer, vertel hem dan welke adressen hij moet vertrouwen:
{
servers {
trusted_proxies static 10.0.0.0/24
}
}
Dit globale blok hoort helemaal bovenaan de Caddyfile.
10. Logs en probleemoplossing
journalctl -u caddy --since "10 min ago"
sudo tail -f /var/log/caddy/access.log
curl -I https://app.example.com
Veelvoorkomende problemen:
| Probleem | Oplossing |
|---|---|
| Certificaat niet uitgegeven | DNS moet naar deze server wijzen; poort 80/443 open in UFW en de cloudfirewall |
502 Bad Gateway |
De backend draait niet of luistert op een andere poort: ss -tlnp |
| Redirect-lus | De app stuurt zelf ook door naar HTTPS; laat Caddy dat doen en vertrouw X-Forwarded-Proto |
| Wijzigingen genegeerd | Voer systemctl reload caddy uit en controleer de uitvoer van caddy validate |
Uitgevers van certificaten hanteren rate limits bij herhaalde mislukkingen, dus los DNS op voordat je het vaak opnieuw probeert.
Redirects en canonieke domeinen
De meeste sites horen op één naam te antwoorden en de andere door te sturen. Stuur www naar het kale domein:
www.example.com {
redir https://example.com{uri} permanent
}
example.com {
reverse_proxy localhost:3000
}
Caddy haalt voor beide namen certificaten op. Draai ze om als je liever www als hoofdadres gebruikt.
Rate limiting en verzoekgrootte
Caddy houdt de kern klein, dus voor rate limiting heb je een plug-in nodig. Voor de meeste kleine sites is Fail2ban op het toegangslog of een limiet in de applicatie genoeg. Het beperken van de grootte van de request body is wel ingebouwd:
app.example.com {
request_body {
max_size 20MB
}
reverse_proxy localhost:3000
}
Zo houd je te grote uploads tegen voordat ze je app bereiken.
Statische bestanden naast een app
Assets rechtstreeks van schijf serveren is sneller dan ze via de app te laten lopen:
app.example.com {
handle /static/* {
root * /var/www/app
file_server
header Cache-Control "public, max-age=604800"
}
handle {
reverse_proxy localhost:3000
}
}
11. Waar certificaten staan
Caddy bewaart certificaten en sleutels onder /var/lib/caddy/.local/share/caddy. Neem dat pad mee in je back-ups, zodat je na een herstel niet alles opnieuw hoeft uit te geven. Ontbreekt het, dan haalt Caddy gewoon nieuwe certificaten op.
Volgende stappen
Met Caddy ervoor kun je veel apps op één server hosten. Logische volgende stappen zijn WordPress, een Docker-stack of een zelfgehoste tool uit self-hosting. Vergeet de basisbeveiliging en back-ups niet.
Veelgestelde vragen
Haalt Caddy echt automatisch HTTPS-certificaten op?
Ja. Als een siteadres in de Caddyfile een publiek domein is dat naar de server verwijst, en poort 80 en 443 bereikbaar zijn, haalt Caddy zelf certificaten op bij een publieke ACME-autoriteit en vernieuwt ze ook.
Is Caddy snel genoeg voor productie?
Ja. Bij gangbare webapps is de reverse proxy zelden de bottleneck, en Caddy ondersteunt HTTP/2 en HTTP/3 direct uit de doos.
Kan Caddy doorsturen naar apps in Docker?
Ja. Draai Caddy in hetzelfde Compose-netwerk en gebruik servicenamen, of bind containers aan 127.0.0.1 en stuur door naar poorten op localhost.
Caddy of Nginx?
Caddy vraagt minder configuratie en regelt certificaten zelf. Nginx heeft een groter ecosysteem van voorbeelden en modules. Beide zijn solide keuzes.
Waarom krijgt Caddy geen certificaat?
Meestal wijst DNS nog niet naar de server, of wordt poort 80 of 443 door een firewall geblokkeerd. Controleer beide en kijk daarna in journalctl -u caddy.