Nginx beveiligingshardening op Ubuntu en Debian
Versterk Nginx verder dan de standaardconfiguratie met beveiligingsheaders, TLS 1.3, HSTS, methodebeperking en toegangscontroles. Elke directive is gekoppeld aan de specifieke aanval die het voorkomt.
Een standaard Nginx-installatie serveert verkeer. Het beschermt het niet. De standaardconfiguratie lekt versie-informatie, accepteert elke HTTP-methode, stuurt geen beveiligingsheaders en gebruikt de TLS-instellingen die OpenSSL biedt.
Deze gids versterkt Nginx op Debian 12 en Ubuntu 24.04. Elke sectie noemt eerst de dreiging en toont dan de directive die deze blokkeert. Je kunt deze wijzigingen toepassen op een draaiende server zonder downtime.
Vereisten:
- Nginx geïnstalleerd en minstens een site beschikbaar via HTTPS (Let's Encrypt SSL/TLS instellen voor Nginx op Debian 12 en Ubuntu 24.04)
- Root- of sudo-toegang
- Basiskennis van de Nginx-configuratiestructuur (Nginx configuratiebestandsstructuur uitgelegd)
We slaan alle hardeningdirectieven op in een enkel include-bestand. Dit houdt je server-blokken schoon en maakt auditing eenvoudig:
sudo touch /etc/nginx/snippets/security-hardening.conf
Elk server-blok dat hardening nodig heeft krijgt een enkele regel:
include /etc/nginx/snippets/security-hardening.conf;
Na elke wijziging in deze gids, test en herlaad:
sudo nginx -t && sudo systemctl reload nginx
Hoe verberg ik de Nginx-serverversie?
Voeg server_tokens off; toe aan het http-blok in /etc/nginx/nginx.conf. Dit verwijdert het versienummer uit de Server-responsheader en de standaard foutpagina's. Aanvallers scannen op specifieke versies met bekende CVE's. De versie verbergen fixt geen kwetsbaarheden, maar verhoogt de kosten van gerichte aanvallen.
Voeg toe in /etc/nginx/nginx.conf binnen het http-blok:
server_tokens off;
Dit hoort in de hoofdconfiguratie, niet in het snippet, omdat het globaal geldt.
Na het herladen, controleer de responsheader:
curl -sI https://your-domain.com | grep -i server
Server: nginx
Geen versienummer. Voor deze wijziging stond er iets als Server: nginx/1.24.0.
Hoe blokkeer ik verzoeken naar onbekende hostnamen?
Wanneer iemand het IP-adres van je server direct benadert of een onherkende hostnaam gebruikt, serveert Nginx het eerste server-blok dat het vindt. Dit opent de deur voor DNS-rebinding-aanvallen en laat scanners je diensten fingerprinting.
Een standaard server-blok dat alle ongeassocieerde verzoeken afwijst, stopt dit. Voeg het toe als eerste server-blok dat Nginx laadt:
sudo nano /etc/nginx/sites-available/00-default-deny.conf
server {
listen 80 default_server;
listen [::]:80 default_server;
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
server_name _;
# Self-signed or snakeoil cert just to complete the TLS handshake
ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
return 444;
}
Statuscode 444 is Nginx-specifiek. Het sluit de verbinding onmiddellijk zonder een antwoord te sturen.
Activeer het:
sudo ln -s /etc/nginx/sites-available/00-default-deny.conf /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Op Debian 12 en Ubuntu 24.04, installeer het ssl-cert-pakket als het snakeoil-certificaat ontbreekt:
sudo apt install ssl-cert
Test het door het IP direct te benaderen:
curl -sI -k https://YOUR_SERVER_IP
De verbinding sluit met een leeg antwoord of een curl-fout (exitcode 52). Geen content geserveerd.
Welke TLS-configuratie moet ik in 2026 gebruiken voor Nginx?
Gebruik het Intermediate-profiel van Mozilla: TLS 1.2 en 1.3 met forward-secret cipher suites. Dit dekt clients tot Firefox 27 en Android 4.4.2 terwijl onveilige protocollen worden uitgesloten. TLS 1.0 en 1.1 zijn kwetsbaar voor POODLE en BEAST en zijn verouderd sinds RFC 8996 (2021). Als al je clients TLS 1.3 ondersteunen, gebruik dan het Modern-profiel (ssl_protocols TLSv1.3;).
TLS-hardening werd urgenter begin 2026. CVE-2026-1642 (CVSS 5.9) toonde aan dat de TLS upstream-afhandeling van Nginx een race condition had die MitM-injectie mogelijk maakte voor de handshake voltooid was. De fix verscheen in Nginx 1.28.2 en 1.29.5. Controleer je versie met nginx -v en update indien nodig.
| Profiel | Protocollen | Oudste client | Gebruikssituatie |
|---|---|---|---|
| Modern | Alleen TLS 1.3 | Firefox 63, Chrome 70 | API's, moderne webapps |
| Intermediate | TLS 1.2 + 1.3 | Firefox 27, Android 4.4 | Algemene servers |
| Old | TLS 1.0 + 1.1 + 1.2 + 1.3 | IE 8 op XP | Alleen legacy-compatibiliteit |
Genereer DH-parameters voor de DHE cipher suites in het Intermediate-profiel. Dit duurt een paar minuten:
sudo openssl dhparam -out /etc/nginx/dhparam.pem 2048
sudo chmod 644 /etc/nginx/dhparam.pem
Voeg TLS-hardening toe aan /etc/nginx/snippets/security-hardening.conf:
# TLS protocols - Mozilla Intermediate profile
ssl_protocols TLSv1.2 TLSv1.3;
# Cipher suites - Mozilla Intermediate profile (version 5.7)
# TLS 1.3 suites are configured automatically by OpenSSL
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305';
ssl_prefer_server_ciphers off;
ssl_dhparam /etc/nginx/dhparam.pem;
# Session settings
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
Waarom ssl_prefer_server_ciphers off? Met alleen sterke ciphers in de lijst geeft clientvoorkeur betere prestaties. Clients kiezen de cipher die hun hardware het beste versnelt.
Waarom ssl_session_tickets off? Sessietickets gebruiken een serverbrede sleutel. Als die sleutel lekt, kan een aanvaller alle opgenomen sessies ontsleutelen. Zonder tickets biedt de gedeelde sessiecache hervatting alleen op dezelfde server, wat prima is voor single-server setups.
OCSP stapling opmerking: Let's Encrypt heeft zijn OCSP-dienst in augustus 2025 beëindigd. Als je Let's Encrypt-certificaten gebruikt, verwijder dan alle ssl_stapling-directieven. Ze veroorzaken waarschuwingen in je logs. Let's Encrypt publiceert intrekkingsinformatie nu uitsluitend via CRL's.
Herlaad en test vanaf je lokale machine:
openssl s_client -connect your-domain.com:443 -tls1_2 </dev/null 2>/dev/null | grep "Protocol\|Cipher"
Protocol : TLSv1.2
Cipher : ECDHE-RSA-AES256-GCM-SHA384
Bevestig dat TLS 1.1 wordt geweigerd:
openssl s_client -connect your-domain.com:443 -tls1_1 </dev/null 2>&1 | head -5
De handshake mislukt. Voor een volledige audit, dien je domein in bij SSL Labs. Mik op een A of A+ beoordeling.
Hoe activeer ik HSTS in Nginx?
HSTS (HTTP Strict Transport Security) vertelt browsers om alleen via HTTPS te verbinden voor een ingestelde periode. Zonder HSTS kan een MitM-aanvaller het eerste HTTP-verzoek onderscheppen en de verbinding degraderen. Zodra een browser de HSTS-header heeft gezien, weigert het plain HTTP-verbindingen naar je domein tot max-age verloopt.
Voeg toe aan /etc/nginx/snippets/security-hardening.conf:
# HSTS - 2 years, include subdomains
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
De always-vlag zorgt ervoor dat Nginx deze header ook bij foutresponsen stuurt (4xx, 5xx). Zonder always zou een 403- of 500-respons de header niet bevatten, waardoor er een venster voor downgrade-aanvallen ontstaat.
Voordat je includeSubDomains toevoegt, bevestig dat al je subdomeinen HTTPS ondersteunen. Deze directive geldt voor elk subdomein. Een subdomein zonder geldig certificaat wordt onbereikbaar.
HSTS preloading: Het toevoegen van preload aan de header en het indienen van je domein bij hstspreload.org codeert HTTPS-handhaving permanent in browsers. Verwijdering duurt maanden. Voeg preload pas toe als je zeker bent dat elk subdomein altijd HTTPS zal serveren.
Na het herladen:
curl -sI https://your-domain.com | grep -i strict
strict-transport-security: max-age=63072000; includeSubDomains
Welke beveiligingsheaders moet elke Nginx-server hebben?
Vijf responsheaders beschermen tegen veelvoorkomende browseraanvallen: MIME-sniffing, clickjacking, cross-site scripting, referrer-lekkage en misbruik van browser-API's. Voeg alle vijf toe met de always-vlag zodat ze ook bij foutresponsen gelden. De verouderde X-XSS-Protection-header moet niet worden opgenomen. Moderne browsers hebben hun XSS-auditors verwijderd, en de header zelf introduceerde kwetsbaarheden in sommige gevallen.
Voeg toe aan /etc/nginx/snippets/security-hardening.conf:
# Prevent MIME type sniffing - stops browsers from interpreting files as a
# different content type than declared, blocking drive-by download attacks
add_header X-Content-Type-Options "nosniff" always;
# Clickjacking protection - prevents your pages from being embedded in
# iframes on other sites
add_header X-Frame-Options "SAMEORIGIN" always;
# Content Security Policy - controls which sources can load scripts, styles,
# images, and other resources. Start restrictive, loosen as needed.
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'self'; base-uri 'self'; form-action 'self';" always;
# Referrer Policy - controls how much URL information the browser sends
# when navigating away from your site
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Permissions Policy - disables browser features you don't use, preventing
# compromised scripts from accessing camera, microphone, etc.
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=()" always;
interest-cohort=() verscheen in oudere gidsen om Google FLoC te blokkeren. FLoC werd in 2023 stopgezet. Moderne browsers herkennen deze directive niet en geven consolewaarschuwingen als je het opneemt.
| Header | Waarde | Afgewende dreiging |
|---|---|---|
| X-Content-Type-Options | nosniff |
MIME-verwarringsaanvallen, drive-by downloads |
| X-Frame-Options | SAMEORIGIN |
Clickjacking via iframe-embedding |
| Content-Security-Policy | default-src 'self' |
XSS, data-injectie, ongeautoriseerd laden van bronnen |
| Referrer-Policy | strict-origin-when-cross-origin |
URL-lekkage naar derden |
| Permissions-Policy | camera=(), microphone=()... |
Misbruik van browser-API's door geïnjecteerde scripts |
CSP-afstemming: Het bovenstaande voorbeeld is strikt. De meeste applicaties hebben aanpassingen nodig. Als je site scripts laadt van een CDN, voeg het toe: script-src 'self' https://cdn.example.com;. Gebruik de ontwikkelaarsconsole van je browser om CSP-schendingen te vinden en versoepel het beleid stapsgewijs. Gebruik nooit unsafe-eval tenzij je het risico begrijpt.
De add_header overervingsval
Nginx verwijdert alle add_header-directieven van bovenliggende blokken wanneer een kind-location-blok zijn eigen add_header definieert. Als je een header toevoegt in een location-blok, verdwijnen alle beveiligingsheaders van het server- of http-niveau uit de responses van die location.
Twee manieren om dit aan te pakken:
- Nginx 1.29.3+: Gebruik
add_header_inherit merge;in het kindblok om bovenliggende headers te behouden. - Oudere versies: Herhaal de regel
include /etc/nginx/snippets/security-hardening.conf;in elk location-blok dat eigen headers toevoegt.
Herlaad en controleer:
sudo nginx -t && sudo systemctl reload nginx
curl -sI https://your-domain.com | grep -iE "x-content-type|x-frame|content-security|referrer-policy|permissions-policy"
Alle vijf headers verschijnen in de output. Controleer ook een specifiek location-pad, niet alleen de root, om overervingsproblemen te detecteren.
Hoe schakel ik onveilige HTTP-methoden uit in Nginx?
De meeste sites hebben alleen GET, HEAD en POST nodig. Methoden als DELETE, PUT, TRACE en OPTIONS (wanneer ongebruikt) vergroten je aanvalsoppervlak. TRACE kan met name cookies en auth-tokens lekken via cross-site tracing (XST) aanvallen.
De schoonste aanpak in standaard Nginx is limit_except, speciaal ontworpen voor methodebeperkingen. Plaats het in je location-blokken:
location / {
limit_except GET POST {
deny all;
}
# ... your existing config
}
limit_except GET staat impliciet HEAD toe (HEAD is een GET zonder body volgens de HTTP-specificatie). Elke niet-genoemde methode retourneert 403 Forbidden.
Voor een globale aanpak met map (voeg toe aan nginx.conf in het http-blok):
map $request_method $method_not_allowed {
default 1;
GET 0;
HEAD 0;
POST 0;
}
Dan in je server-blok:
if ($method_not_allowed) {
return 405;
}
De limit_except-aanpak heeft de voorkeur wanneer je controle per location nodig hebt. De map-aanpak werkt wanneer je een dekkend beleid over de hele server wilt.
Test met een geblokkeerde methode:
curl -sI -X DELETE https://your-domain.com
Retourneert HTTP/1.1 403 Forbidden of HTTP/1.1 405 Not Allowed.
curl -sI -X GET https://your-domain.com
Retourneert HTTP/1.1 200 OK (of je normale responscode).
Hoe beperk ik toegang tot admin-paden op basis van IP-adres?
Beheerpanelen, monitoringdashboards en status-endpoints moeten niet bereikbaar zijn vanaf het publieke internet. De allow- en deny-directieven van Nginx beperken toegang per IP-adres op location-niveau.
location /admin {
allow 203.0.113.10; # Your office IP
allow 198.51.100.0/24; # Your VPN range
deny all;
# ... proxy_pass or other directives
}
location /nginx-status {
stub_status;
allow 127.0.0.1;
allow ::1;
deny all;
}
Volgorde is belangrijk. Nginx evalueert allow en deny van boven naar beneden en stopt bij de eerste match. Plaats deny all als laatste.
Als je IP vaak verandert, overweeg dan toegang te beperken via je firewall of gebruik een VPN. IP-gebaseerde ACL's in Nginx zijn een tweede beveiligingslaag, geen vervanging voor authenticatie.
Vanaf een toegestaan IP:
curl -sI https://your-domain.com/admin
Retourneert je normale respons (200, 302, etc.).
Vanaf een ander IP (of via een proxy) retourneert hetzelfde verzoek HTTP/1.1 403 Forbidden.
Controleer het toegangslog voor geweigerde verzoeken:
sudo tail -5 /var/log/nginx/access.log | grep admin
Welke buffer- en timeoutlimieten voorkomen DoS-aanvallen in Nginx?
Standaard buffergroottes en timeouts zijn ruim. Een aanvaller kan dit misbruiken door grote headers, trage verzoeken of te grote bodies te sturen om worker-verbindingen te bezetten. Het aanscherpen van deze waarden beperkt de schade die een enkele verbinding kan veroorzaken.
Voeg toe aan /etc/nginx/snippets/security-hardening.conf:
# Maximum allowed request body size - reject uploads larger than 10MB
client_max_body_size 10m;
# Buffer for reading client request body
client_body_buffer_size 16k;
# Buffer for reading large client headers
large_client_header_buffers 4 16k;
# Timeouts - how long Nginx waits for client data
client_body_timeout 30s;
client_header_timeout 30s;
send_timeout 30s;
keepalive_timeout 65s;
| Directive | Waarde | Te hoog | Te laag |
|---|---|---|---|
client_max_body_size |
10m |
Staat enorme uploads toe die de schijf vullen | Breekt upload-formulieren |
client_body_buffer_size |
16k |
Geheugenverspilling per verbinding | Forceert temp-bestanden voor kleine POST's |
large_client_header_buffers |
4 16k |
Geheugenverspilling bij header-aanvallen | Breekt apps met grote cookies/URL's |
client_body_timeout |
30s |
Slow loris-verbindingen blijven open | Verbreekt gebruikers op trage netwerken |
client_header_timeout |
30s |
Hetzelfde als hierboven | Hetzelfde als hierboven |
send_timeout |
30s |
Bezet verbindingen voor trage clients | Breekt grote downloads af |
keepalive_timeout |
65s |
Uitputting van de verbindingspool | Extra TLS-handshakes |
Pas client_max_body_size aan op je applicatie. Als je bestandsuploads verwerkt, stel het in op wat je app verwacht. Een standaard van 10 MB is redelijk voor de meeste webapplicaties.
Herlaad en test de body-groottelimiet:
dd if=/dev/zero bs=1M count=11 2>/dev/null | curl -s -X POST --data-binary @- -o /dev/null -w "%{http_code}" https://your-domain.com/
Verwacht: 413 (Request Entity Too Large).
Voor rate limiting en diepgaandere DDoS-bescherming, zie Nginx Rate Limiting en DDoS-bescherming.
Hoe test ik de volledige hardeningconfiguratie?
Zodra alle wijzigingen zijn doorgevoerd, herlaad en doorloop de belangrijkste controles. Begin met een configuratietest en herlaad:
sudo nginx -t && sudo systemctl reload nginx
Controleer headers en versie-informatie met een enkel verzoek:
curl -sI https://your-domain.com
De Server-header moet nginx tonen zonder versienummer. Alle zes beveiligingsheaders (HSTS, X-Content-Type-Options, X-Frame-Options, Content-Security-Policy, Referrer-Policy, Permissions-Policy) moeten in de respons verschijnen.
Voor TLS, bevestig het protocol en cipher vanaf je lokale machine:
openssl s_client -connect your-domain.com:443 </dev/null 2>/dev/null | grep -E "Protocol|Cipher"
Je wilt TLSv1.2 of TLSv1.3 met een GCM- of CHACHA20-cipher.
Benader het server-IP direct om te bevestigen dat het standaard weiger-blok werkt:
curl -sk https://YOUR_SERVER_IP -o /dev/null -w "%{http_code}"
Een responscode van 000 betekent dat de verbinding is gesloten zonder respons, wat correct is.
Probeer een geblokkeerde HTTP-methode:
curl -sI -X TRACE https://your-domain.com -o /dev/null -w "%{http_code}"
Dit moet 403 of 405 retourneren.
Voor een externe audit, dien je domein in bij SSL Labs (mik op A of A+) en securityheaders.com voor een header-specifiek rapport.
Compleet hardening-snippet
Het volledige /etc/nginx/snippets/security-hardening.conf-bestand met alles uit deze gids:
# /etc/nginx/snippets/security-hardening.conf
# Nginx security hardening - include in each server block
# --- TLS Hardening (Mozilla Intermediate profile v5.7) ---
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305';
ssl_prefer_server_ciphers off;
ssl_dhparam /etc/nginx/dhparam.pem;
# TLS session settings
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
# --- HSTS ---
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
# --- Security Headers ---
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'self'; base-uri 'self'; form-action 'self';" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=()" always;
# --- Buffer Limits ---
client_max_body_size 10m;
client_body_buffer_size 16k;
large_client_header_buffers 4 16k;
# --- Timeouts ---
client_body_timeout 30s;
client_header_timeout 30s;
send_timeout 30s;
keepalive_timeout 65s;
Vergeet niet ook server_tokens off; in te stellen in het http-blok van /etc/nginx/nginx.conf en het standaard weiger-server-blok apart aan te maken.
Probleemoplossing
Nginx herlaadt niet na wijzigingen:
sudo nginx -t
Lees de fout. Die geeft het bestand en regelnummer aan. Veelvoorkomende oorzaken: ontbrekende puntkomma's, typefouten in directivenamen, verwijzing naar een DH-parameterbestand dat niet bestaat.
Headers ontbreken op sommige paden:
De add_header-overervingsval. Als een location-blok zijn eigen add_header heeft, worden alle bovenliggende headers verwijderd. Neem het snippet op in dat location-blok of gebruik add_header_inherit merge; op Nginx 1.29.3+.
CSP blokkeert legitieme bronnen:
Open de ontwikkelaarsconsole van de browser (F12). Zoek naar Refused to load-fouten. Ze noemen de geblokkeerde bron en welke CSP-directive het blokkeerde. Voeg de bron stapsgewijs toe aan je beleid.
TLS-handshake-fouten:
journalctl -u nginx -f
Kijk naar SSL-fouten tijdens het verbinden. Controleer of je certificaatketen compleet is:
openssl s_client -connect your-domain.com:443 -servername your-domain.com </dev/null 2>/dev/null | grep "Verify return code"
Verwacht: Verify return code: 0 (ok).
Loglocatie:
# Error log
sudo tail -20 /var/log/nginx/error.log
# Real-time monitoring
sudo journalctl -u nginx -f
Volgende stappen: Met de server versterkt, stel Nginx Rate Limiting en DDoS-bescherming in om misbruikende verkeerspatronen af te handelen. Voor per-site configuraties, zie Nginx server blocks: meerdere domeinen op één VPS.
Gerelateerd: Nginx-beheer op een VPS | Nginx configuratiebestandsstructuur uitgelegd | Let's Encrypt SSL/TLS instellen voor Nginx op Debian 12 en Ubuntu 24.04
Klaar om het zelf te proberen?
Deploy uw eigen server in seconden. Linux, Windows of FreeBSD. →