Beveiligingsheaders in gewone taal: HSTS, CSP en de rest
Beveiligingsheaders zijn korte regels die je server met elke pagina meestuurt en die de browser vertellen wat wel en niet mag. HSTS dwingt een beveiligde verbinding af, X-Frame-Options houdt je pagina uit andermans kader en een Content-Security-Policy bepaalt waar scripts vandaan mogen komen. Met curl -sI of een testsite zie je welke jouw site stuurt.
Elke keer dat een browser een pagina van je site ophaalt, krijgt hij er een paar regels bij die de bezoeker nooit ziet. Die regels heten headers. Een deel ervan is praktisch, zoals het soort bestand of de datum. Een ander deel gaat over veiligheid en vertelt de browser wat hij wel en niet mag doen met je pagina. Die beveiligingsheaders kosten niets, en je controleert ze in een paar minuten.
Zo zie je de headers van je site
Op je server vraag je alleen de headers van een pagina op met:
curl -sI https://mijndomein.nl
curl haalt een adres op, -I vraagt alleen de headers en -s houdt het programma verder stil. Je krijgt een rijtje regels, elk met een naam, een dubbele punt en een waarde. Op een site die met publish op een server van retrAIt staat, zitten deze vier ertussen:
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Referrer-Policy: strict-origin-when-cross-origin
Strict-Transport-Security: max-age=31536000
Hoofdletters kunnen bij jou anders zijn; dat maakt niet uit. De server stuurt ze ook mee met een foutpagina, zoals de pagina die je krijgt bij een adres dat niet bestaat. Doe dit op je server of op een Mac. In PowerShell op Windows is curl soms een ander commando met andere uitvoer.
Wat de eerste drie doen
X-Content-Type-Options: nosniff zegt tegen de browser dat hij het soort bestand moet aannemen dat de server opgeeft, en niet zelf moet gaan raden. Zonder deze regel kan een browser een bestand dat als platte tekst bedoeld is, toch openen als webpagina, met de scripts die erin staan. Dat telt vooral als bezoekers bestanden kunnen uploaden.
X-Frame-Options: SAMEORIGIN zegt dat alleen je eigen site je pagina’s in een kader mag tonen. Zo kan niemand jouw pagina onzichtbaar onder een nepknop op zijn eigen site leggen, zodat je bezoekers ongemerkt op jouw knoppen klikken. Die truc heet clickjacking. Een nieuwere manier om hetzelfde te regelen is de regel frame-ancestors in een Content-Security-Policy, waarover verderop meer.
Een voorbeeld. Fatima is fysiotherapeut en heeft een pagina waar cliënten inloggen en een afspraak verzetten. Zonder deze header kan een andere site die pagina in een onzichtbaar kader zetten, met een eigen knop erboven, en een bezoeker iets laten aanklikken wat hij niet van plan was. Met de header laat de browser dat kader gewoon leeg.
Referrer-Policy: strict-origin-when-cross-origin bepaalt wat een andere site te horen krijgt als een bezoeker vanaf jouw site doorklikt. Met deze waarde hoort die site alleen je domeinnaam, niet het volledige adres van de pagina. Zo’n adres kan meer verraden dan je denkt, zoals een zoekterm of een code uit een inloglink. Moderne browsers doen dit inmiddels ook zonder de regel; je server zegt het voor de zekerheid.
HSTS: alleen nog via https
De vierde, Strict-Transport-Security, kortweg HSTS, vraagt wat meer uitleg. Met HSTS zegt je server tegen de browser: kom voor deze naam voortaan alleen via een beveiligde verbinding, en onthoud dat een jaar lang. Dat jaar staat in de header als max-age=31536000, het aantal seconden in een jaar. Een browser die de header eenmaal heeft gezien, maakt van een link die met http:// begint zelf een https://-link, nog voordat er iets over het netwerk gaat.
De uitleg van MDN over HSTS, de naslag van Mozilla voor webontwikkelaars, noemt twee kanten die je moet kennen. HSTS werkt pas na het eerste bezoek via https, want een browser negeert de header als die via een onbeveiligde verbinding binnenkomt. En een browser die HSTS kent voor jouw site, laat bezoekers niet meer doorklikken bij een probleem met je certificaat. Verloopt dat certificaat ooit, dan is je site voor hen onbereikbaar.
Dat maakt het vanzelf vernieuwen van je certificaat extra waardevol. Let’s Encrypt, waar de gratis certificaten vandaan komen, stuurt sinds juni 2025 geen herinneringen meer per mail als een certificaat bijna verloopt. Kijk daarom af en toe zelf met sudo certbot certificates hoeveel dagen je certificaat nog geldig is. Meer daarover lees je in Het slotje in je adresbalk.
Er bestaan twee uitbreidingen: includeSubDomains, voor alle subdomeinen tegelijk, en preload, waarmee je je domein op een lijst zet die in browsers zelf is ingebouwd. Een server van retrAIt zet ze bewust niet. Met de eerste wordt elk subdomein zonder certificaat onbereikbaar, en van de lijst kom je volgens de beheerders pas na maanden weer af.
Een Content-Security-Policy
Eén belangrijke header zet de server niet standaard: de Content-Security-Policy, kortweg CSP. Daarmee geef je de browser een lijst van plekken waar je pagina scripts, stijlen, lettertypes en afbeeldingen vandaan mag halen. Al het andere weigert de browser. Volgens de uitleg van MDN over CSP is dat vooral een verdediging tegen cross-site scripting: een aanval waarbij iemand een eigen stukje script in je pagina weet te krijgen, bijvoorbeeld via een formulier. Lukt dat toch, dan houdt een goede CSP het script vaak nog tegen.
Hij staat niet standaard aan omdat hij per site anders is. Gebruikt je site bijvoorbeeld lettertypes van Google of een video van YouTube, dan moeten die op de lijst. Een verkeerde CSP maakt je site stuk. Begin daarom altijd met de proefversie, Content-Security-Policy-Report-Only. Die blokkeert niets, maar meldt in de ontwikkelhulpmiddelen van je browser wat hij zou blokkeren. Pas als daar niets meer staat wat je wilt houden, zet je hem echt aan.
Een CSP is vooral de moeite waard zodra bezoekers op je site iets invullen of inloggen. Laat Claude hem opstellen, in die proefversie. Op een server van retrAIt horen eigen regels voor één site in de map /etc/nginx/extra/mijndomein.nl/, omdat publish de rest van de instellingen voor je domein elke keer opnieuw schrijft.
Daar zit een valkuil in. De webserver nginx neemt de headers van een hoger niveau alleen over als er op het eigen niveau geen enkele header wordt toegevoegd, zo staat in de documentatie van nginx. Voegt een regel voor één adres zijn eigen header toe, dan vallen op dat adres de andere weg. Vraag Claude daarom na elke wijziging om met curl -sI te laten zien dat alle headers er nog zijn, ook op dat ene adres.
Laat een testsite meekijken
Een tweede mening krijg je bij de HTTP Observatory van Mozilla, de makers van Firefox, en bij internet.nl, een initiatief van de internetgemeenschap en de Nederlandse overheid. Allebei kijken ze naar deze headers en geven ze uitleg per onderdeel. Bij de Observatory is de geschiedenis van scans per domein voor iedereen te zien.
Neem zo’n uitslag als hulpmiddel, niet als keurmerk. Mozilla zegt er zelf bij dat een hoog cijfer niet betekent dat je site veilig is: verouderde software of een fout in je eigen programma vindt de test niet. Daarover gaat Waarom apps die met AI gebouwd zijn zo vaak lekken. En voeg geen header toe alleen omdat een test erom vraagt. De oude header X-XSS-Protection hoort er bijvoorbeeld niet meer bij, en kan volgens MDN in sommige gevallen juist een lek veroorzaken.
Typ vandaag curl -sI https://mijndomein.nl met je eigen domein en zoek de vier headers op. Doe hetzelfde voor een adres dat niet bestaat, en voor de versie die met http:// begint: die hoort te antwoorden met 301 en een regel Location die naar het https-adres wijst. Mist er iets, of wil je een CSP, vraag Claude dan om het in te richten, eerst als proefversie. En kijk in Wat staat er per ongeluk openbaar op je website meteen of er geen bestanden online staan die er niet horen.
Foto bovenaan: Travis Saylor via Pexels