Module 7 · 7.4
Caddy, HSTS en security.txt
Module 7: Domein, webserver en https · Leestijd: 12 minuten
Leerdoel: Na deze les kun je Caddy gebruiken als eenvoudige webserver met automatische https, HSTS en de belangrijkste beveiligingsheaders zetten (in Caddy en nginx) zonder je site onbereikbaar te maken, en een geldige security.txt publiceren.
Kernbegrippen: Caddy, Caddyfile, automatische https, reverse_proxy, file_server, header, HSTS (Strict-Transport-Security), max-age, includeSubDomains, preload, X-Content-Type-Options, frame-ancestors, Referrer-Policy, security.txt, Contact, Expires, /.well-known/
Caddy: https zonder omkijken
In les 7.2 en 7.3 bouwde je met nginx en certbot een site met https. Dat zijn twee programma's, een paar bestanden en een timer. Caddy doet het in één: het is een webserver met een ingebouwde ACME-client. Staat een naam in de configuratie, wijst die naar de server en zijn poort 80 en 443 bereikbaar, dan haalt Caddy zelf een certificaat op, vernieuwt het op tijd en stuurt http door naar https.
Caddy staat in de pakketbron van Debian en Ubuntu (een wat oudere versie); de makers bieden ook een eigen apt-bron met de nieuwste versie, met een eigen sleutel en Signed-By zoals in les 4.1. In module 9 draait Caddy als container voor je Docker-diensten (les 9.4). Hier gebruik je hem als gewone dienst.
anna@web1:~$ sudo apt install caddy
anna@web1:~$ systemctl status caddyGebruik óf nginx óf Caddy op poort 80 en 443, niet allebei: maar één programma kan op een poort luisteren.
De Caddyfile
De configuratie staat in /etc/caddy/Caddyfile. Een statische site en een applicatie achter een reverse proxy:
example.com, www.example.com {
root * /var/www/example.com
file_server
encode zstd gzip
}
app.example.com {
reverse_proxy 127.0.0.1:3000
}Dat is alles. Caddy luistert vanzelf op IPv4 en IPv6, vraagt voor de drie namen certificaten aan, stuurt http door naar https, en geeft bij reverse_proxy de kopregels X-Forwarded-For en X-Forwarded-Proto vanzelf door (wat je in nginx met de hand deed, les 7.2).
Testen en herladen gaat zoals bij nginx:
anna@web1:~$ caddy validate --config /etc/caddy/Caddyfile
Valid configuration
anna@web1:~$ sudo systemctl reload caddy
anna@web1:~$ sudo journalctl -u caddy -fIn de journal zie je het ophalen van de certificaten. Gaat het mis, dan staat de reden er: meestal dezelfde als bij certbot (poort 80 dicht, naam wijst ergens anders heen, IPv6 kapot; les 7.3).
HSTS: de browser laten onthouden dat het https is
Doorsturen van http naar https heeft één zwak moment: het allereerste verzoek via http gaat nog onversleuteld. Iemand op hetzelfde netwerk kan dat onderscheppen en de bezoeker op http houden. HSTS (HTTP Strict Transport Security, RFC 6797) lost dat op. Je site stuurt via https deze kopregel mee:
Strict-Transport-Security: max-age=31536000De browser onthoudt dan een jaar (31.536.000 seconden) dat deze naam alleen via https mag. Typt de bezoeker later http://, dan maakt de browser er zelf https van, nog voordat er iets over het netwerk gaat. Een HSTS-kop die via http binnenkomt, negeert de browser; hij telt alleen via https.
Er is een keerzijde. Heeft een browser HSTS onthouden en ziet hij een certificaatfout (verlopen, verkeerde naam), dan krijgt de bezoeker geen knop om toch door te gaan. Je site is dan voor die bezoekers onbereikbaar tot het certificaat klopt. Daarom:
- Zet HSTS pas aan als https overal werkt en vernieuwen automatisch gaat (les 7.3).
- Begin met een korte
max-age, bijvoorbeeld een dag (86400), en verhoog in stappen naar een jaar. - Wees voorzichtig met
includeSubDomains: dat geldt voor elk subdomein, ook een oud subdomein met een apparaat of dienst zonder geldig certificaat. preloadvraagt om opname in een lijst die in browsers is ingebouwd (hstspreload.org). Daarvoor eisen ze onder meer eenmax-agevan minstens een jaar,includeSubDomains, en https op alle subdomeinen. Eraf komen duurt maanden. Doe het alleen als je zeker weet dat het hele domein voorgoed https is.
De andere beveiligingsheaders
Naast HSTS zijn er een paar kopregels die met weinig moeite veel helpen:
| Kopregel | Waartegen | Waarde om mee te beginnen |
|---|---|---|
X-Content-Type-Options | de browser gaat raden wat een bestand is, zodat een geüpload "plaatje" als script kan draaien | nosniff |
Content-Security-Policy: frame-ancestors (of het oudere X-Frame-Options) | clickjacking: een andere site laadt jouw pagina onzichtbaar in een frame | frame-ancestors 'self' (of X-Frame-Options: SAMEORIGIN) |
Referrer-Policy | het adres van je pagina (met zoektermen of ordernummers) lekt naar andere sites | strict-origin-when-cross-origin |
Een volledig Content-Security-Policy (welke scripts en stijlen mogen laden) is maatwerk per site en valt buiten deze cursus; begin met alleen frame-ancestors.
In Caddy zet je ze met de header-directive in het blok van een site:
example.com, www.example.com {
root * /var/www/example.com
file_server
header {
Strict-Transport-Security "max-age=31536000"
X-Content-Type-Options "nosniff"
Content-Security-Policy "frame-ancestors 'self'"
Referrer-Policy "strict-origin-when-cross-origin"
-Server
}
}Caddy zet HSTS en deze kopregels niet zelf; je moet ze er dus echt in zetten. -Server haalt de kopregel Server weg. In les 9.4 zet je in hetzelfde soort blok je eigen kopregel X-Weboke-Cursus voor de opdracht.
In nginx zet je dezelfde regels in het server-blok voor poort 443:
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Content-Security-Policy "frame-ancestors 'self'" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;always zorgt dat ze ook bij foutpagina's worden meegestuurd. Let op een bekende valkuil van nginx: staat er in een location-blok zelf één add_header, dan gelden de add_header-regels van het server-blok niet meer voor die location. Controleer daarom altijd het resultaat:
anna@laptop:~$ curl -sI https://example.com | grep -iE 'strict|nosniff|frame|referrer'
strict-transport-security: max-age=31536000
x-content-type-options: nosniff
content-security-policy: frame-ancestors 'self'
referrer-policy: strict-origin-when-cross-originsecurity.txt: zo kan iemand je een lek melden
Stel dat iemand een lek op je server vindt, zoals een open map of een verouderde dienst. Hoe weet die persoon wie hij moet waarschuwen? Daarvoor is security.txt (RFC 9116): een klein tekstbestand op een vaste plek, https://<jouwdomein>/.well-known/security.txt.
Contact: mailto:security@example.com
Expires: 2027-10-31T23:00:00Z
Preferred-Languages: nl, en
Canonical: https://example.com/.well-known/security.txtDe regels:
Contactis verplicht: eenmailto:,https://- oftel:-adres waar meldingen terechtkomen. Gebruik een adres dat iemand ook echt leest.Expiresis verplicht en mag maar één keer voorkomen: de datum waarna het bestand als verouderd geldt. De RFC raadt een datum binnen een jaar aan. Zo blijft er geen bestand met een allang vergeten adres staan. Zet in je agenda wanneer je hem moet verlengen.Preferred-Languages,CanonicalenPolicy(een link naar je beleid voor meldingen) zijn optioneel.- Het bestand moet via https komen, als platte tekst (
text/plain, UTF-8). De oude plek/security.txtmag als extra, maar/.well-known/gaat voor.
Neerzetten bij een statische site:
anna@web1:~$ sudo mkdir -p /var/www/example.com/.well-known
anna@web1:~$ sudoedit /var/www/example.com/.well-known/security.txt
anna@laptop:~$ curl -sI https://example.com/.well-known/security.txt | grep -i content-type
content-type: text/plain; charset=utf-8Gebruik je in nginx het vangnet location ~ /\. { deny all; } uit les 7.2, zet er dan een uitzondering vóór: location ^~ /.well-known/ { }. Voor een applicatie achter een reverse proxy kun je in Caddy het pad /.well-known/security.txt naar een bestand laten wijzen met een handle-blok en file_server.
Een veelgemaakte fout: een site of applicatie die elk onbekend pad beantwoordt met de voorpagina en status 200. Dan "bestaat" security.txt, maar is het HTML. Controleer dus ook de inhoud.
Op AlmaLinux en Rocky Linux
Caddy komt daar uit de bron van de makers of uit EPEL/COPR. Zoals bij nginx moet SELinux een reverse proxy toestaan (setsebool -P httpd_can_network_connect 1). De Caddyfile en de headers zijn hetzelfde.
Samenvatting
- Caddy regelt certificaten, vernieuwen en doorsturen naar https zelf;
reverse_proxygeeft de juiste kopregels vanzelf door. - HSTS laat browsers onthouden dat je site alleen https is; bouw
max-ageop, wees voorzichtig metincludeSubDomainsen heel voorzichtig metpreload. - Zet daarnaast
X-Content-Type-Options: nosniff,frame-ancestors 'self'en eenReferrer-Policy; in Caddy metheader, in nginx metadd_header ... always, en controleer metcurl -I. - security.txt staat op
/.well-known/security.txt, via https, als platte tekst, met verplichtContacten éénExpiresbinnen een jaar.
Quiz
Opdracht
Opdracht Z7.4: HSTS, headers en security.txt. Zet voor je site HSTS (minstens 180 dagen, nadat https een tijd goed werkt), X-Content-Type-Options: nosniff en frame-ancestors 'self' (of X-Frame-Options), en publiceer een security.txt met Contact en een Expires binnen een jaar. Vul je hostnaam in. Het programma haalt https://<hostnaam>/ op en controleert de kopregels, en haalt https://<hostnaam>/.well-known/security.txt op en controleert de velden. Het slaat niets op behalve de uitslag en de Expires-datum.
Verder lezen
- HSTS en beveiligingsheaders voor je website
- Caddy, Automatic HTTPS: https://caddyserver.com/docs/automatic-https
- Caddy, header-directive: https://caddyserver.com/docs/caddyfile/directives/header
- RFC 6797, HTTP Strict Transport Security: https://www.rfc-editor.org/rfc/rfc6797.html
- HSTS-preloadlijst en de eisen: https://hstspreload.org/
- RFC 9116, security.txt: https://www.rfc-editor.org/rfc/rfc9116.html
- securitytxt.org (maken en controleren): https://securitytxt.org/
