Direct naar de inhoud

Cookie-instellingen

Kies per soort welke cookies je toestaat. Je kunt je keuze altijd wijzigen via Cookie-instellingen onderaan elke pagina. Privacyverklaring

Noodzakelijk

Altijd aan

Nodig om in te loggen, je winkelwagen te bewaren en je keuzes te onthouden. Deze staan altijd aan.

Welke cookies?
  • weboke_sessie, weboke_klant, weboke_remember: je sessie en ingelogd blijven
  • weboke_basket: je winkelwagen
  • wkBtwIncl: prijzen met of zonder btw tonen
  • wok_cookies: je cookiekeuze, 12 maanden

Meet welke advertentie tot een bezoek of bestelling leidt, zodat we niet betalen voor advertenties die niets doen.

Welke cookies?
  • Google Ads via Google Tag Manager (_gcl_au, _gcl_aw), tot 90 dagen
  • OpenAI Ads, advertenties in ChatGPT
  • wok_aff: via welke partner je bij ons kwam (affiliateprogramma), 60 dagen

4,6

Land en taal

Elk land heeft zijn eigen site, met prijzen en btw van dat land.

Bekijken

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 caddy

Gebruik ó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 -f

In 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=31536000

De 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:

  1. Zet HSTS pas aan als https overal werkt en vernieuwen automatisch gaat (les 7.3).
  2. Begin met een korte max-age, bijvoorbeeld een dag (86400), en verhoog in stappen naar een jaar.
  3. Wees voorzichtig met includeSubDomains: dat geldt voor elk subdomein, ook een oud subdomein met een apparaat of dienst zonder geldig certificaat.
  4. preload vraagt om opname in een lijst die in browsers is ingebouwd (hstspreload.org). Daarvoor eisen ze onder meer een max-age van 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:

KopregelWaartegenWaarde om mee te beginnen
X-Content-Type-Optionsde browser gaat raden wat een bestand is, zodat een geüpload "plaatje" als script kan draaiennosniff
Content-Security-Policy: frame-ancestors (of het oudere X-Frame-Options)clickjacking: een andere site laadt jouw pagina onzichtbaar in een frameframe-ancestors 'self' (of X-Frame-Options: SAMEORIGIN)
Referrer-Policyhet adres van je pagina (met zoektermen of ordernummers) lekt naar andere sitesstrict-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-origin

security.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.txt

De regels:

  • Contact is verplicht: een mailto:, https://- of tel:-adres waar meldingen terechtkomen. Gebruik een adres dat iemand ook echt leest.
  • Expires is 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, Canonical en Policy (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.txt mag 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-8

Gebruik 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_proxy geeft de juiste kopregels vanzelf door.
  • HSTS laat browsers onthouden dat je site alleen https is; bouw max-age op, wees voorzichtig met includeSubDomains en heel voorzichtig met preload.
  • Zet daarnaast X-Content-Type-Options: nosniff, frame-ancestors 'self' en een Referrer-Policy; in Caddy met header, in nginx met add_header ... always, en controleer met curl -I.
  • security.txt staat op /.well-known/security.txt, via https, als platte tekst, met verplicht Contact en één Expires binnen een jaar.

Quiz

1. Waarom helpt HSTS, terwijl je http al doorstuurt naar https?



2. Je zet HSTS met max-age=31536000; includeSubDomains. Een week later blijkt camera.example.com geen geldig certificaat te hebben. Wat merken bezoekers die je site eerder bezochten?



3. Welke velden zijn verplicht in een security.txt?



4. Je hebt in nginx de beveiligingsheaders in het server-blok gezet, maar voor /api/ komen ze niet mee. In het blok location /api/ staat één eigen add_header. Wat is er aan de hand?



5. Wat moet je doen voordat je preload aan je HSTS-kop toevoegt?



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.

Opdracht Z7.4: HSTS, headers en security.txt

Alleen voor aangemelde cursisten. We halen https://<naam>/ en /.well-known/security.txt op (alleen GET).




Wat je invult en wat we vinden, slaan we niet op. Je browser onthoudt je antwoorden en of je de opdracht hebt gehaald. Ben je aangemeld, dan bewaren we alleen dat je de opdracht (bijna) goed had in Mijn cursussen; daar kun je het ook verwijderen.

Verder lezen

Even geduld…

Stel je domeinen in

Regel per domein de nameservers, de koppeling aan je hosting en wie de eigenaar wordt.

Je domeinen laden…