Als je een app lanceert met Cursor, Lovable of v0, antwoordt de server op elke aanvraag met een reeks HTTP-headers: onzichtbare instructies die de browser opvolgt voordat hij iets tekent. Een handvol ervan bepaalt een groot deel van de beveiliging van je site, en vrijwel geen enkel template levert ze ingeschakeld mee. Deze gids legt uit wat elke header doet, welke waarde je instelt, welke fouten dingen kapotmaken en hoe je ze configureert op Vercel, Next.js, Nuxt, Netlify, Cloudflare of nginx.
Wat een header is en hoe je de jouwe bekijkt
Een header is een regel tekst die met het antwoord meereist, in de vorm Naam: waarde. De browser leest hem en past zijn gedrag aan: een script wel of niet laden, HTTPS afdwingen, je pagina wel of niet in een iframe toestaan. Je ziet ze in de ontwikkelaarstools (F12, tabblad Network, klik op het hoofddocument, sectie Response Headers) of vanuit de terminal:
curl -I https://jouwdomein.nl
Staan de headers hieronder niet in die lijst, dan zijn ze niet ingesteld. Er zijn geen standaardwaarden die je redden.
De zeven die ertoe doen
Content-Security-Policy (CSP)
De koningin. Hij vertelt de browser van welke origins hij scripts, stijlen, afbeeldingen, lettertypen en verbindingen mag laden. Lukt het een aanvaller een <script> in je pagina te injecteren, dan voorkomt een goede CSP dat het wordt uitgevoerd. Een eerlijk startpunt voor een moderne app:
Content-Security-Policy: default-src 'self'; script-src 'self' https://js.stripe.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https://*.supabase.co; frame-ancestors 'none'; base-uri 'self'; form-action 'self'
De klassieke fout is 'unsafe-inline' in script-src zetten omdat "het anders niet werkt": dat gooit de belangrijkste bescherming weg. Moderne frameworks lossen inline scripts op met een nonce (een willekeurige waarde per aanvraag) of met hashes. En de veilige manier om te beginnen is de rapportmodus: de header Content-Security-Policy-Report-Only blokkeert niets, hij vertelt alleen wat hij zou blokkeren. Laat hem een paar dagen zo staan, bekijk de rapporten en zet hem om naar de echte header zodra hij niets meer breekt.
Strict-Transport-Security (HSTS)
Dwingt de browser om gedurende de opgegeven tijd HTTPS te gebruiken, ook als de gebruiker http:// typt of op een oude link klikt. Voorkomt dat iemand op hetzelfde netwerk de verbinding naar beneden haalt. De gebruikelijke waarde:
Strict-Transport-Security: max-age=31536000; includeSubDomains
Schakel hem pas in als je hele domein en alle subdomeinen over HTTPS lopen: zet je hem met een subdomein nog op HTTP, dan sluit je dat een jaar lang buiten in elke browser die de header heeft gezien. Begin bij twijfel met een korte max-age.
X-Frame-Options en frame-ancestors
Ze voorkomen dat je site in een <iframe> van een andere pagina wordt geladen. Zonder kan iemand je app in een nepsite inbedden en gebruikers verleiden te klikken waar ze dat niet moeten: clickjacking. X-Frame-Options: DENY of SAMEORIGIN lost het op in oude browsers; de moderne vorm is de directive frame-ancestors in de CSP. Zet ze allebei.
X-Content-Type-Options
Eén waarde: nosniff. Voorkomt dat de browser het type van een bestand raadt en iets als script uitvoert wat je als tekst of afbeelding hebt geserveerd. Het is één regel; er is geen excuus om hem over te slaan.
Referrer-Policy
Bepaalt hoeveel van de oorspronkelijke URL wordt meegestuurd bij het navigeren naar een andere site of het laden van een externe bron. Zonder controle kun je privépaden of tokens lekken die in de URL reizen. strict-origin-when-cross-origin is de verstandige balans: je domein wel, het volledige pad niet.
Permissions-Policy
Verklaart welke browserfuncties je pagina en zijn iframes mogen gebruiken: camera, microfoon, locatie, betalingen. Gebruik je ze niet, zet ze dan uit; dan kan een geïnjecteerd script of een iframe van derden ze ook niet gebruiken. Bijvoorbeeld: camera=(), microphone=(), geolocation=().
Cross-Origin-Opener-Policy
Minder bekend: isoleert je venster van de vensters die het openden of die jij opende met window.open, wat een familie van aanvallen tussen tabbladen afsnijdt. same-origin is de gebruikelijke waarde, tenzij je afhankelijk bent van pop-ups van derden (sommige OAuth-logins), dan is same-origin-allow-popups beter.
Welke je beter kunt verwijderen
X-Powered-By en een Server-header met versie verraden je exacte stack aan wie naar bekende fouten zoekt. In Next.js haal je hem weg met poweredByHeader: false; in Express met app.disable('x-powered-by'). En X-XSS-Protection is verouderd: moderne browsers negeren hem en in oude veroorzaakte hij zelf fouten. Zet hem niet, of zet hem op 0.
Hoe je ze instelt per host
Vercel, voor elk framework, in vercel.json:
{
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "Strict-Transport-Security", "value": "max-age=31536000; includeSubDomains" },
{ "key": "X-Frame-Options", "value": "SAMEORIGIN" },
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" },
{ "key": "Permissions-Policy", "value": "camera=(), microphone=(), geolocation=()" }
]
}
]
}
Next.js: de functie headers() in next.config.js, met dezelfde lijst; de CSP met nonce wordt in de middleware gegenereerd. Nuxt: routeRules in nuxt.config.ts met een headers-object, of een serverplugin als je een nonce nodig hebt. Netlify: een bestand _headers in de gepubliceerde map:
/*
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Cloudflare: transformatieregels voor antwoordheaders in het dashboard, zonder code aan te raken. nginx op een VPS:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
De CSP laat ik met opzet buiten deze blokken: het is de enige die afhangt van welke scripts en diensten je app gebruikt, en je bouwt hem het best in rapportmodus op voordat je hem vastzet.
Fouten die dingen kapotmaken
- HSTS voordat alles op HTTPS staat. Sluit iedereen buiten die nog een subdomein op HTTP heeft. Eerst controleren, dan inschakelen.
- CSP zonder nonce in Next.js of Nuxt. Beide injecteren inline scripts om de pagina te hydrateren; zonder nonce blokkeert een strikte CSP ze en blijft de app wit. Gebruik het nonce-mechanisme van het framework.
- Headers alleen op de HTML. API-routes en statische bestanden hebben ze ook nodig, vooral
nosniff. Pas de regel toe op/(.*). - Dubbele headers. Voegen het CDN en de app dezelfde toe, dan houdt de browser mogelijk de strengste of negeert hij allebei. Kies één plek.
- Subdomeinen vergeten (app., api., docs.): elk antwoordt met zijn eigen headers.
Controleer ze van buitenaf
Deze headers zijn openbare informatie: iedereen kan ze lezen, jij ook. De gratis security headers checker somt in seconden op welke ontbreken en welke verkeerd staan. En Scanaris doet de volledige read-only controle, met zo'n 45 andere dingen, en geeft je voor elke ontbrekende header een fixprompt die je zo in je AI plakt met de exacte regel voor jouw host. Het buit niets uit en raakt je server niet aan: het leest en vertelt je wat je moet inschakelen. Voor de volledige context lees je de beveiligingsgids voor AI-gebouwde apps en de lanceerchecklist.
Veelgestelde vragen
Maken headers de site trager?
Nee. Het zijn een paar honderd bytes per antwoord en de browser verwerkt ze in microseconden. HSTS maakt hem zelfs sneller, omdat hij bij latere bezoeken de omleiding van HTTP naar HTTPS overslaat.
Wat als een header iets kapotmaakt in productie?
Haal hem weg of versoepel hem en deploy opnieuw: het effect is onmiddellijk, behalve bij HSTS, die browsers onthouden gedurende de max-age. Daarom test je de CSP in rapportmodus en schakel je HSTS als laatste in.
Heb ik een CSP nodig als mijn app klein is?
Heeft je app een formulier of toont hij gebruikersinhoud, dan ja: het is de verdediging die een XSS tot een klein incident maakt. Begin met de andere headers, die triviaal zijn, en voeg de CSP in rapportmodus toe zodra je er een middag voor hebt.