Zum Hauptinhalt springen
S-EDV news
← Alle Anleitungen
📘 Anleitung WordPress 30.09.2026 · 11 min Lesezeit

WordPress auf nginx härten: sensible Dateien gezielt sperren

Getestetes Sperr-Snippet für WordPress auf nginx: wp-config.php, Sicherungskopien, .env und .git, Logdateien, PHP im Upload-Ordner und interne PHP-Dateien sperren, in der richtigen Reihenfolge einbinden und mit curl prüfen.

Mit KI erstellt – redaktionelle Prüfung ausstehend

WerbelinksMit * markierte Links sind Werbelinks: Bei einem Kauf erhalten wir eine Provision, der Preis bleibt gleich. Mehr dazu

Grafik mit der Überschrift WordPress auf nginx härten und einem stilisierten WordPress-Adminbereich mit gesperrten Dateien

Auf fast jedem WordPress-Server liegen Dateien, die nie jemand über den Browser abrufen sollte: die wp-config.php mit den Datenbank-Zugangsdaten, eine vergessene Sicherungskopie wp-config.php.bak, ein Datenbank-Export im Webverzeichnis, das debug.log mit Pfaden und Fehlermeldungen oder ein .git-Ordner aus der Entwicklung. Apache schützt einige dieser Dateien über .htaccess. nginx liest keine .htaccess, deshalb müssen Sie jede Sperre selbst in die Server-Konfiguration schreiben. Diese Anleitung zeigt ein getestetes Sperr-Snippet, erklärt, warum die Reihenfolge der Regeln entscheidet, und liefert eine Prüfschleife für jede Änderung.

Voraussetzungen

  • Server: ein Linux-Server oder VPS, auf dem WordPress bereits mit nginx und PHP-FPM läuft. Die Befehle beziehen sich auf Debian 13 mit nginx 1.26.3 und PHP-FPM 8.4. Für eine kleine Unternehmenswebsite reichen 2 CPU-Kerne und 2 bis 4 GB RAM. Wie Sie den Grundaufbau einrichten, steht in WordPress auf nginx mit PHP-FPM konfigurieren.
  • WordPress: eine aktuelle Installation, getestet mit WordPress 7.1.2. Die WordPress-Versionsschnittstelle empfiehlt mindestens PHP 7.4.
  • Zugriff: SSH mit Root- oder sudo-Rechten und ein Administrator-Konto in WordPress, um den Adminbereich nach der Änderung zu prüfen.
  • Werkzeuge: curl auf dem Server und ein Texteditor wie nano.
  • Backup: eine Sicherung der bisherigen nginx-Konfiguration, zum Beispiel mit sudo cp -a /etc/nginx /root/nginx-sicherung, sowie ein aktuelles Backup von Dateien und Datenbank.
  • Hosting: Ohne Zugriff auf die nginx-Konfiguration, etwa bei Shared Hosting, fragen Sie Ihren Hoster nach seinen Sperren.

Schritt 1: Ist-Zustand mit curl erfassen

Bevor Sie etwas ändern, sollten Sie wissen, was Ihr Server heute ausliefert. Erst dann sehen Sie, ob eine Regel wirkt, und Sie erkennen später, ob Sie aus Versehen etwas gesperrt haben, das WordPress braucht. Speichern Sie dazu die folgende Prüfschleife als /root/sperrtest.sh und passen Sie die Adresse in der ersten Zeile an:

BASIS="https://www.ihre-domain.de"
for p in / /wp-login.php /wp-config.php /wp-config.php.bak /wp-config.php~ \
         /.env /.git/config /.htaccess /backup.sql /wp-content/debug.log \
         /readme.html /license.txt /wp-includes/version.php \
         /wp-admin/includes/file.php /wp-content/uploads/test.php \
         /wp-content/plugins/akismet/readme.txt /composer.json \
         /.well-known/acme-challenge/test.txt \
         /wp-includes/js/jquery/jquery.min.js "/index.php?rest_route=/"; do
  printf "%-45s %s\n" "$p" "$(curl -s -o /dev/null -w '%{http_code}' "$BASIS$p")"
done

Damit die Ausnahme für Let's Encrypt prüfbar ist, legen Sie vorher eine harmlose Testdatei an: sudo mkdir -p /var/www/html/.well-known/acme-challenge && echo ok | sudo tee /var/www/html/.well-known/acme-challenge/test.txt. Nicht vorhandene Pfade liefern vorher 404, danach 403.

Die Liste enthält auch Adressen, die weiter funktionieren müssen. Auf der unveränderten Testinstallation mit einem einfachen Server-Block lieferten fast alle sensiblen Pfade den Status 200. wp-config.php.bak und wp-config.php~ kamen als Klartext mit allen Zugangsdaten zurück, weil nginx Dateien ohne Endung .php als Download ausliefert. Auch .env und .git waren lesbar. Die echte wp-config.php selbst lieferte zwar 200, aber einen leeren Inhalt, weil PHP sie ausführt statt anzuzeigen. Das schützt nur, solange PHP-FPM läuft.

Suchen Sie dann nach Dateien, die nicht ins Webverzeichnis gehören:

cd /var/www/html
sudo find . -maxdepth 3 \( -name '*.bak' -o -name '*.old' -o -name '*.sql' -o -name '*.sql.gz' \
  -o -name '*~' -o -name '.env' -o -name '.git' -o -name 'debug.log' \) -print

Alles, was hier auftaucht, verschieben Sie aus dem Webverzeichnis oder löschen es. Die Sperre ersetzt das Aufräumen nicht.

Verifizieren: Die Prüfschleife läuft ohne Fehlermeldung durch und zeigt für jede Adresse einen Statuscode. Notieren Sie sich die Ausgabe, damit Sie nach Schritt 3 vergleichen können.

Schritt 2: Sperr-Snippet anlegen

Eine eigene Datei hält den Server-Block übersichtlich und lässt sich in mehrere Websites einbinden. Legen Sie /etc/nginx/snippets/wordpress-sperren.conf mit diesem Inhalt an:

# /etc/nginx/snippets/wordpress-sperren.conf
# Vor dem PHP-Block einbinden: nginx nimmt die erste passende Regex-Location.

# Versteckte Dateien und Ordner (.env, .git, .htaccess), aber Let's Encrypt erlauben
location ~ /\.(?!well-known/) {
    deny all;
}

# Konfiguration, Sicherungskopien und Editor-Reste
location = /wp-config.php {
    deny all;
}
location ~* \.(?:bak|old|orig|save|swp|sql|sql\.gz|log)$ {
    deny all;
}
location ~ ~$ {
    deny all;
}

# PHP im Upload-Ordner nie ausführen
location ~* /(?:uploads|files)/.*\.php$ {
    deny all;
}

# Interne PHP-Dateien, die nur eingebunden, nie direkt aufgerufen werden
location ^~ /wp-admin/includes/ {
    deny all;
}
location ~ ^/wp-includes/[^/]+\.php$ {
    deny all;
}

# Versionshinweise und Entwicklerdateien
location ~* ^/(?:readme\.html|license\.txt|wp-config-sample\.php)$ {
    deny all;
}
location ~* ^/wp-content/(?:plugins|themes)/[^/]+/(?:readme|changelog)\.(?:txt|md)$ {
    deny all;
}
location ~* /(?:composer\.(?:json|lock)|package(?:-lock)?\.json)$ {
    deny all;
}

Was die einzelnen Regeln bewirken und warum sie so geschrieben sind:

  • Versteckte Dateien: Der Ausdruck /\.(?!well-known/) trifft jeden Pfadteil, der mit einem Punkt beginnt, also .env, .git, .htaccess oder .DS_Store. Die Ausnahme für .well-known/ braucht Let's Encrypt für die HTTP-Prüfung.
  • wp-config.php: Die exakte Location mit = hat bei nginx Vorrang vor allen anderen. Fällt PHP-FPM aus, liefert nginx die Datei trotzdem nicht als Text aus.
  • Sicherungskopien und Logs: Der Schalter ~* ignoriert Groß- und Kleinschreibung. Die Endung .log deckt das debug.log ab, das WordPress bei aktivem WP_DEBUG_LOG in wp-content schreibt. Die Regel ~$ fängt Sicherungsdateien von Editoren wie wp-config.php~.
  • Uploads: Legt ein Angreifer eine PHP-Datei im beschreibbaren Upload-Ordner ab, darf nginx sie nie an PHP-FPM weiterreichen. Die Regel stammt aus der Beispielkonfiguration im WordPress-Handbuch für nginx, Details in PHP-Ausführung im Upload-Verzeichnis verhindern.
  • Interne PHP-Dateien: Die Dateien direkt unter wp-includes und in wp-admin/includes bindet WordPress nur ein, das Handbuch zur Härtung empfiehlt für Apache eine Sperre. Die nginx-Regel für wp-includes trifft absichtlich nur die oberste Ebene, damit Unterordner wie wp-includes/js/tinymce für den klassischen Editor erreichbar bleiben.
  • Versionshinweise: readme.html und die readme.txt von Plugins verraten, welche Version installiert ist. Die Sperre erschwert automatisierte Scans, ersetzt aber keine Updates.

Verifizieren: sudo nginx -t meldet „syntax is ok“ und „test is successful“. Eingebunden ist das Snippet noch nicht.

Schritt 3: Snippet an der richtigen Stelle einbinden

Die Position im Server-Block entscheidet darüber, ob die Sperren wirken. Laut nginx-Dokumentation prüft nginx zuerst alle Präfix-Locations und merkt sich die längste passende. Danach prüft es die Regex-Locations in der Reihenfolge, in der sie in der Datei stehen, und nimmt die erste, die passt. Steht der PHP-Block location ~ \.php$ vor den Sperren, gewinnt er bei jeder PHP-Datei, auch bei uploads/test.php. Binden Sie das Snippet deshalb direkt nach index und vor allen anderen Locations ein:

server {
    listen 80;
    server_name www.ihre-domain.de;
    root /var/www/html;
    index index.php;

    include snippets/wordpress-sperren.conf;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.4-fpm.sock;
    }
}

Bei HTTPS gehört die Zeile in den server-Block mit listen 443. Testen und laden Sie die Konfiguration neu:

sudo nginx -t && sudo systemctl reload nginx

Das && sorgt dafür, dass nginx nur neu lädt, wenn der Test erfolgreich war.

Verifizieren: Führen Sie bash /root/sperrtest.sh erneut aus. Alle sensiblen Pfade liefern jetzt 403, dagegen liefern die Startseite (je nach Einstellung 200 oder eine Weiterleitung 301), wp-login.php, jquery.min.js, die REST-API über rest_route und die Testdatei unter .well-known weiterhin 200.

Schritt 4: WordPress-Funktionen gegenprüfen

Eine zu breite Sperre fällt oft erst Tage später auf. Prüfen Sie deshalb direkt nach der Änderung die wichtigsten Funktionen. Melden Sie sich im Adminbereich an und öffnen Sie nacheinander das Dashboard, „Beiträge“ > „Erstellen“, „Plugins“, „Einstellungen“ > „Permalinks“ und „Werkzeuge“ > „Website-Zustand“. Laden Sie ein Bild in die Mediathek hoch und prüfen Sie, ob es im Frontend angezeigt wird.

Parallel dazu zeigt das Fehlerprotokoll von nginx jede Anfrage, die an einer Sperre hängen geblieben ist:

sudo tail -f /var/log/nginx/error.log | grep 'forbidden by rule'

Im Test sah eine Zeile so aus:

2026/09/30 20:38:10 [error] 9948#9948: *59 access forbidden by rule, client: 127.0.0.1, server: _, request: "GET /WP-CONFIG.PHP.BAK HTTP/1.1", host: "127.0.0.1:8080"

Gehört eine dort gesperrte Adresse zu einem Plugin, das sie braucht, setzen Sie für genau diese Datei eine exakte Location mit = vor das Snippet, statt die ganze Sperre zu entfernen.

Verifizieren: Alle genannten Seiten im Adminbereich öffnen sich ohne Fehlermeldung, der Upload funktioniert, und im Fehlerprotokoll stehen nur Anfragen, die Sie absichtlich gesperrt haben.

Schritt 5: Sperren dauerhaft pflegen

Neue Plugins bringen eigene Dateien mit, und bei einem Umzug wird der Server-Block oft neu geschrieben. Drei Gewohnheiten halten die Sperren wirksam:

  • Führen Sie die Prüfschleife aus Schritt 1 nach jedem nginx-Update, jedem Umzug und jeder größeren Änderung aus.
  • Prüfen Sie, ob der Server-Header die nginx-Version preisgibt: curl -sI https://www.ihre-domain.de/ | grep -i server. Steht dort eine Versionsnummer, setzen Sie im http-Block von /etc/nginx/nginx.conf die Direktive server_tokens off;.
  • Werten Sie die Zeilen mit „forbidden by rule“ gelegentlich aus. Häufen sich Anfragen auf .env oder wp-config.php.bak, klopfen Scanner Ihre Website ab. Solche Muster wertet fail2ban aus, siehe fail2ban für wp-login und XML-RPC beschrieben.

Die Serverregeln sind nur ein Baustein, genauso wichtig sind aktuelle Plugins und Backups außerhalb des Servers. Wer diese laufenden Aufgaben nicht selbst im Kalender halten möchte, kann sie abgeben: Die WordPress-Wartung von wordpressupdate.de spielt Updates von WordPress, Plugins und Themes nach einer Kompatibilitätsprüfung ein, legt wöchentlich ein Backup auf externem Speicher ab und richtet die Firewall ein und pflegt sie.

Verifizieren: Die Prüfschleife liegt auf dem Server, ein Termin für die nächste Kontrolle steht in Ihrem Kalender, und curl -sI zeigt keine nginx-Versionsnummer.

Typische Fehler

PHP im Upload-Ordner wird trotz Sperre ausgeführt

Im Test haben wir das Snippet absichtlich ans Ende des Server-Blocks gesetzt, also hinter den PHP-Block. nginx -t meldete keinen Fehler, trotzdem gab /wp-content/uploads/test.php mit Status 200 die Ausgabe des Skripts zurück, und /wp-includes/version.php war ebenfalls erreichbar. Die Regeln für wp-config.php und .sql griffen dagegen weiter, ein Test mit nur einer Adresse übersieht den Fehler also. Lösung: Snippet vor den PHP-Block verschieben und die komplette Prüfschleife ausführen.

Upload-Ordner mit ^~ hebelt die Sperre aus

Für Caching-Regeln wird gern location ^~ /wp-content/uploads/ { … } ergänzt. Der Schalter ^~ sagt nginx, nach dieser Präfix-Location keine Regex-Locations mehr zu prüfen. Im Test lieferte nginx die Datei test.php daraufhin als application/octet-stream aus, also mit ihrem Quelltext. Verwenden Sie für Uploads kein ^~, oder schreiben Sie innerhalb dieses Blocks eine eigene verschachtelte Sperre für \.php$.

nginx startet nicht: duplicate location

Steht dieselbe exakte Location zweimal im Server-Block, etwa weil die alte Konfiguration bereits eine Regel für wp-config.php enthält, bricht der Test ab:

nginx: [emerg] duplicate location "/wp-config.php" in /etc/nginx/sites-enabled/wordpress:9
nginx: configuration file /etc/nginx/nginx.conf test failed

Entfernen Sie die ältere Regel aus dem Server-Block und behalten Sie nur die im Snippet.

Zertifikat lässt sich nicht verlängern

Eine Regel wie location ~ /\. { deny all; } ohne Ausnahme sperrt auch /.well-known/acme-challenge/. Die Verlängerung bei Let's Encrypt schlägt dann fehl, oft erst Wochen später. Die Testdatei aus Schritt 1 muss deshalb 200 liefern.

Häufige Fragen

Reicht es nicht, die gefährlichen Dateien einfach zu löschen?

Löschen ist der wichtigste Schritt. Die Sperre schützt vor dem, was ungeplant liegen bleibt, etwa einer Kopie beim Bearbeiten oder einem Export vor dem Umzug.

Sollte ich auch xmlrpc.php hier sperren?

Das hängt davon ab, ob Sie XML-RPC brauchen, etwa für die WordPress-App oder Jetpack. Die Abwägung und die Varianten beschreibt XML-RPC in WordPress deaktivieren oder einschränken.

Funktioniert das Snippet auch bei Multisite?

Nicht ohne Prüfung. Ältere Multisite-Netzwerke liefern Medien über wp-includes/ms-files.php aus, das die Regel für wp-includes sperren würde. Testen Sie Multisite zuerst auf einer Kopie.

Testumfang

Wir haben das Snippet auf einer Testinstallation mit WordPress 7.1.2 unter nginx eingebaut und gezielt Köder wie Sicherungskopien der wp-config.php ausgelegt. Ohne Sperren waren fast alle lesbar, danach lieferten sie 403, während Anmeldung und Adminbereich normal funktionierten.

HTTPS mit echtem Zertifikat, Multisite und verbreitete Plugins mit eigenen Dateiaufrufen haben wir nicht geprüft. Sehen Sie dort nach der Änderung besonders genau ins Fehlerprotokoll.

Fazit

Ohne .htaccess ist nginx genau so sicher, wie Sie es konfigurieren. Ein eigenes Sperr-Snippet vor dem PHP-Block schließt die typischen Lecks wie Sicherungskopien, versteckte Dateien und PHP im Upload-Ordner, und die Prüfschleife zeigt nach jeder Änderung schwarz auf weiß, ob die Regeln greifen. Wenn Sie die laufende Pflege von WordPress, Plugins und Backups auf diesem Server lieber abgeben möchten, übernimmt das die WordPress-Wartung von Marcel Schönfelder.

Weiterführende Anleitungen und Quellen

WordPressnginxSicherheitHärtungServer