Lokal & kontrolliert
ANLEITUNG · FÜR ALLE, DIE EINE WEBSITE BETREIBEN

Sicherheitsheader in nginx, Apache und Caddy setzen

Jede Zeile genau einmal und auf jeder Antwort — mit Test, Rückweg und Nachprüfung

Geprüft am 3. Oktober 2026 · etwa 8 Minuten Lesezeit

Kurz gesagt

Lege für jeden Header fest, wer ihn setzt — Server, Anwendung oder vorgeschalteter Dienst —, und setze ihn nur dort. Im Server gehören die Zeilen an eine Stelle, die für alle Pfade gilt, bei nginx mit always und bei Apache mit Header always set, damit auch Fehlerseiten sie tragen. Bei nginx verdrängt jede eigene add_header-Zeile in einem location-Block alle geerbten. Teste jede Änderung vor dem Neuladen mit nginx -t, apachectl configtest oder caddy validate und prüfe danach mehrere Arten von Antworten.

Worum es geht

Am Ende trägt jede Antwort deiner Website — Seiten, Dateien, Weiterleitungen und Fehlerseiten — jeden Sicherheitsheader genau einmal, mit dem Wert, den du beschlossen hast. Welche Header das sind, steht in Was Sicherheitsheader leisten, wie du eine Content Security Policy (CSP) entwirfst, in Content Security Policy schrittweise einführen.

Ein Beispiel: Lena betreut die Website ihres Sportvereins, www.example.org. Ihr nginx liefert PDFs und Bilder selbst aus und reicht alles andere an die Vereinssoftware weiter. Lena setzt die Header im server-Block, und die Startseite trägt sie alle. Erst beim Nachprüfen merkt sie: Auf den PDFs unter /downloads/ und auf der Fehlerseite fehlen alle Sicherheitsheader, und auf der Anmeldeseite steht X-Frame-Options doppelt.

Was du brauchst

  • Die Befugnis, die Konfiguration zu ändern, und den Zugang dazu: eine Konsole auf dem Server, die Einstellungen deines Hosting-Pakets oder das Konto beim vorgeschalteten Dienst, etwa Cloudflare, der Anfragen vor deinem Server annimmt.
  • Eine Sicherung der heutigen Konfiguration, am besten in einer Versionsverwaltung. Sie ist dein Rückweg.
  • Die Fassung deines Servers: nginx -v, apachectl -v oder caddy version. Befehle laut Herstellerdokumentation, Stand Oktober 2026.
  • Eine Liste der Header und Werte, die du setzen willst. HSTS beginnt mit einer kurzen Laufzeit (siehe „So kehrst du zurück“).

1. Herausfinden, wer heute was setzt

Kopiere die Header von vier Antworten, wie in Antwort-Header richtig kopieren beschrieben: Startseite, eine Datei, die Anmeldeseite und eine Adresse, die es nicht gibt. Unter macOS und Linux zeigt diese Zeile, welche Header mehrfach vorkommen:

curl -sS -D - -o /dev/null https://www.example.org/ | cut -d: -f1 | sort -f | uniq -di

Mehrere Set-Cookie-Zeilen sind normal, jeder andere Treffer gehört geklärt. Dann suchst du, wo sie entstehen: bei nginx nach add_header in der Ausgabe von nginx -T, die alle Konfigurationsdateien samt eingebundenen zeigt; bei Apache nach Header in Konfiguration und .htaccess-Dateien.

Auch Anwendungen setzen Header. Spring Security sendet von Haus aus etwa X-Frame-Options: DENY, WordPress auf Anmelde- und Verwaltungsseiten X-Frame-Options: SAMEORIGIN und seit Fassung 6.9 eine CSP mit frame-ancestors 'self'. Lege je Header fest, wer ihn setzt: Feste Werte wie HSTS oder nosniff passen in den Server, der auch Dateien und Fehlerseiten ausliefert. Eine CSP mit Nonce kommt in der Regel von der Anwendung, denn sie kennt die Zufallszahl der Antwort.

2. nginx: eine Stelle, immer mit always

Lena legt die Zeilen in eine eigene Datei und bindet sie im server-Block der HTTPS-Seite ein:

# /etc/nginx/sicherheitsheader.conf
add_header Strict-Transport-Security "max-age=300" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), geolocation=(), microphone=()" always;

# im server-Block
server_tokens off;
include /etc/nginx/sicherheitsheader.conf;

location /downloads/ {
    include /etc/nginx/sicherheitsheader.conf;
    add_header Cache-Control "max-age=86400";
}
  • always: Ohne diesen Zusatz setzt nginx die Zeile nur bei den Statuscodes 200, 201, 204, 206, 301 bis 304, 307 und 308; auf einer Fehlerseite wie 404 fehlt sie.
  • server_tokens off nimmt die Fassung aus der Zeile Server; der Name nginx bleibt.
  • add_header ersetzt nichts: Sendet die Anwendung denselben Header, steht er doppelt da. Lena blendet den der Vereinssoftware im weiterreichenden Block mit proxy_hide_header X-Frame-Options; aus (bei PHP über FastCGI: fastcgi_hide_header).
Die Vererbungsfalle

nginx erbt add_header-Zeilen der Ebene darüber nur, wenn die aktuelle Ebene selbst keine hat. Lenas Block für die Downloads hatte nur add_header Cache-Control, deshalb fehlten dort alle Sicherheitsheader; jetzt steht das include auch dort. Ab nginx 1.29.3, stabil ab 1.30.0, fügt add_header_inherit merge; im http-Block die geerbten Zeilen zu den eigenen hinzu. Debian 13 liefert nginx 1.26, Ubuntu 26.04 nginx 1.28; beide kennen die Anweisung nicht.

nginx -t
nginx -s reload

nginx -t prüft die Schreibweise und ob sich alle genannten Dateien öffnen lassen; erst danach lädst du neu. Scheitert das Neuladen doch, arbeitet nginx mit der alten Konfiguration weiter.

3. Apache: Header always set

Bei Apache arbeitet das Modul mod_headers. Ob es geladen ist, zeigt apachectl -M (Eintrag headers_module); unter Debian und Ubuntu schaltet a2enmod headers es ein. Die Zeilen gehören in den VirtualHost der HTTPS-Seite:

Header always set Strict-Transport-Security "max-age=300"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header onsuccess unset X-Frame-Options
Header always set X-Frame-Options "DENY"
  • Ohne always fehlen die Zeilen auf Fehlern und Weiterleitungen, die Apache selbst erzeugt.
  • Apache führt zwei getrennte Listen von Antwort-Headern; ein Header der Anwendung kann deshalb neben deinem stehen. Das Muster aus der Apache-Dokumentation, erst onsuccess unset, dann always set, lässt nur deinen Wert übrig.
  • Nimm set, nicht add: add hängt eine Zeile an.
  • ServerTokens Prod in der Hauptkonfiguration kürzt die Zeile Server auf „Apache“.
apachectl configtest
apachectl graceful

configtest meldet, ob die Schreibweise stimmt. graceful lädt neu, ohne offene Verbindungen abzubrechen, und testet vorher selbst.

Beim Hoster: .htaccess

Ohne Zugang zur Hauptkonfiguration gehen dieselben Zeilen in die Datei .htaccess, sofern der Hoster das erlaubt (AllowOverride mit FileInfo). Apache liest sie bei jedem Abruf: Eine Änderung wirkt sofort, ein Tippfehler auch — ein unbekannter Befehl führt zu Fehler 500. Manche Vorlagen, etwa die von OWASP, setzen die Zeilen in <IfModule mod_headers.c>. Dann bleibt die Seite erreichbar, wenn das Modul fehlt, aber die Header fehlen still.

4. Caddy: mit > vor einer Anwendung

www.example.org {
	header {
		>Strict-Transport-Security "max-age=300"
		>X-Content-Type-Options "nosniff"
		>X-Frame-Options "DENY"
		>Referrer-Policy "strict-origin-when-cross-origin"
		-X-Powered-By
	}
	reverse_proxy localhost:8080
}

Ohne Vorzeichen setzt Caddy einen Header sofort und überschreibt einen vorhandenen; - löscht ihn. Eine Anwendung dahinter schreibt ihre Header erst später, ihre und Caddys Zeile stünden dann nebeneinander. > lässt Caddy bis zum Senden warten, sodass dein Wert gilt. Meldet Caddy selbst einen Fehler, etwa weil die Anwendung nicht antwortet, fehlen aufgeschobene Zeilen; dafür gibt es den Block handle_errors.

caddy validate --config Caddyfile
caddy reload --config Caddyfile

validate lädt die Konfiguration probeweise, ohne sie zu starten. reload übergibt sie dem laufenden Caddy; scheitert sie, läuft die alte weiter.

5. Ohne eigenen Server

  • Netlify: eine Datei _headers im veröffentlichten Ordner, darin ein Pfad wie /* und darunter eingerückt die Header. Sie gilt nur für Dateien, die Netlify selbst ausliefert, nicht für Funktionen, serverseitig erzeugte Seiten oder weitergereichte Inhalte.
  • Cloudflare: Rules › Overview › Create rule › Response Header Transform Rule. „Set static“ ersetzt einen vorhandenen Header, „Add static“ hängt eine Zeile an. Die fertige Auswahl „Add security headers“ setzt laut Dokumentation auch das veraltete X-XSS-Protection: 1; mode=block, das die Werkstatt als Hinweis meldet.
  • Erlaubt dein Anbieter keine eigenen Header, kann eine CSP als meta-Element im HTML stehen, mit Einschränkungen. HSTS und X-Frame-Options wirken dort nicht.

Was du siehst

  • Jeder Sicherheitsheader steht in jeder geprüften Antwort genau einmal. Die Zeile aus Schritt 1 findet außer Set-Cookie keinen mehr doppelt.
  • Die Zeile Server nennt keine Fassung mehr; die Werkstatt meldet das als Information statt als Hinweis.
  • Mit max-age=300 meldet die Werkstatt einen Hinweis zur kurzen HSTS-Laufzeit, wenn du bei „Kontext der Antwort“ HTTPS gewählt hast — gewollt bis zur nächsten Stufe.
  • Bei HexWarte, abrufbar mit „HexWarte selbst prüfen“, setzt der Webserver alle Sicherheitsheader außer HSTS nur im server-Block, mit always; die abweichende CSP zweier Seiten steckt in einer Variablen (map). Ein vorgeschalteter Server ergänzt HSTS und ersetzt gleichnamige Zeilen. Am 3. Oktober 2026 trug so auch die Fehlerseite jede Zeile genau einmal.

Wenn etwas nicht passt

  • Auf manchen Pfaden fehlen alle Sicherheitsheader: Dort steht bei nginx eine eigene add_header-Zeile.
  • Auf Fehlerseiten fehlen sie: Es fehlt always bei nginx oder Header always bei Apache; bei Caddy meldet Caddy den Fehler selbst (Schritt 4).
  • Ein Header steht doppelt. Browser lösen das je Header anders: Bei HSTS gilt die erste Zeile, bei Referrer-Policy der letzte gültige Wert, zwei CSP gelten beide. X-Frame-Options mit DENY und SAMEORIGIN verbietet jede Einbettung, außer eine durchgesetzte CSP mit frame-ancestors hat Vorrang. Verlass dich auf nichts davon — setze jeden Header an einer Stelle.
  • nginx -t kennt add_header_inherit nicht: Deine Fassung ist älter als 1.29.3. Nimm den Weg mit include.
  • Apache kennt den Befehl Header nicht: mod_headers ist nicht geladen.
  • Die .htaccess wirkt nicht: Bei AllowOverride None liest Apache sie nicht.
  • Nach dem Neuladen ändert sich nichts: Ein Zwischenspeicher liefert die alte Antwort, oder ein vorgeschalteter Dienst überschreibt deine Werte.

So kehrst du zurück

Spiele die gesicherte Konfiguration zurück, teste sie und lade neu; bei .htaccess genügt die alte Datei. Prüfe danach mit denselben Abrufen, ob der alte Stand ankommt.

HSTS lässt sich nicht einfach zurücknehmen

Browser halten sich bis zum Ablauf von max-age an HSTS (Strict-Transport-Security), auch wenn die Zeile verschwindet; max-age=0 über HTTPS erreicht nur Besucher, die wiederkommen. Deshalb beginnst du mit wenigen Minuten; zur Einführung in Stufen siehe Was HTTPS schützt — und was nicht.

So prüfst du das Ergebnis

  1. Rufe die vier Adressen aus Schritt 1 erneut mit curl ab. Jede Zeile muss überall stehen.
  2. Füge die Header von Startseite, Datei und Anmeldeseite nacheinander in der Header-Werkstatt ein. Bei der Datei bleiben Befunde „Unklar“, die eine HTML-Seite voraussetzen. Doppelte Header wertet sie meist als „Unklar“, doppelte Permissions-Policy- oder Cross-Origin-Zeilen als „Hinweis“; mehrere CSP-Zeilen liest sie als Richtlinien, die alle gelten.
  3. Bei einem Status wie 404 oder 301 über den Headern bleiben in der Werkstatt alle Befunde „Unklar“; dort genügt die curl-Ausgabe.
  4. Prüfe erneut nach jedem Update von Server, Anwendung oder vorgeschaltetem Dienst.

Was dieser Artikel nicht sagt

  • Die Anleitung beschreibt nginx, Apache und Caddy im Stand Oktober 2026. Spätere Fassungen können sich anders verhalten; andere Server wie IIS, Traefik oder HAProxy behandelt sie nicht.
  • Welche Header und Werte deine Website braucht, sagt sie nicht; die Beispielwerte sind nur Ausgangspunkte.
  • Dass alle Zeilen ankommen, macht die Website nicht sicher. Header begrenzen den Schaden einer Lücke, sie schließen keine.

Quellen

Geprüft am 3. Oktober 2026. Die Einordnung ist eine fachliche Bewertung, keine Rechtsberatung. Einen Fehler gefunden? Die Kontaktdaten stehen im Impressum.