---
title: "WordPress auf nginx härten: sensible Dateien gezielt sperren"
date: 2026-09-30T22:43:21+02:00
modified: 2026-11-09T09:11:20+01:00
category: wordpress
type: how_to
author: Marcel Schönfelder
canonical: https://s-edv.com/anleitungen/wordpress-nginx-haerten-sensible-dateien-sperren
keywords:
  - WordPress
  - nginx
  - Sicherheit
  - Härtung
  - Server
reading_time: 10
---

# WordPress auf nginx härten: sensible Dateien gezielt sperren

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.

**Kurzfassung:** Legen Sie die Sperren in einer eigenen Datei `/etc/nginx/snippets/wordpress-sperren.conf` an und binden Sie diese im Server-Block vor dem PHP-Block ein. Gesperrt werden versteckte Dateien außer `.well-known`, die `wp-config.php`, Sicherungskopien und Logdateien, PHP im Upload-Ordner sowie interne PHP-Dateien in `wp-admin/includes` und `wp-includes`. nginx nimmt die erste passende Regex-Location, deshalb greift eine Sperre hinter dem PHP-Block nicht. Prüfen Sie vorher und nachher mit `curl`, welche Adressen 200 und welche 403 liefern. Das Löschen der Dateien bleibt trotzdem der erste Schritt, die Sperre ist die zweite Verteidigungslinie.

## 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](https://s-edv.com/anleitungen/wordpress-nginx-php-fpm-konfigurieren-server-block).
- [ ] **WordPress:** eine aktuelle Installation, getestet mit WordPress 7.1.2. Die WordPress-Versionsschnittstelle nennt PHP 7.4 als Mindestversion, nicht als Empfehlung.
- [ ] **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:

Datei: `/root/sperrtest.sh`

```bash
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:

```bash
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.

> **Prüfen:** 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:

Datei: `/etc/nginx/snippets/wordpress-sperren.conf`

```nginx
# /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](https://s-edv.com/anleitungen/wordpress-uploads-php-ausfuehrung-verhindern-htaccess-nginx).
- **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.

> **Prüfen:** `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:

Datei: `uploads/test.php`

```nginx
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:

```bash
$ sudo nginx -t && sudo systemctl reload nginx
```

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

> **Prüfen:** 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:

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

Im Test sah eine Zeile so aus:

```plaintext
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.

> **Prüfen:** 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](https://s-edv.com/anleitungen/wordpress-fail2ban-wp-login-xmlrpc-serverebene-sperren) 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](https://wordpressupdate.de/?utm_source=s-edv.com&utm_medium=artikel&utm_campaign=109) 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.

> **Prüfen:** 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:

Datei: `wp-config.php`

```php
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](https://s-edv.com/anleitungen/wordpress-xml-rpc-deaktivieren-einschraenken).

### 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.

## 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](https://wordpressupdate.de/?utm_source=s-edv.com&utm_medium=fazit&utm_campaign=109).

## Weiterführende Anleitungen und Quellen

- [WordPress auf nginx mit PHP-FPM konfigurieren](https://s-edv.com/anleitungen/wordpress-nginx-php-fpm-konfigurieren-server-block)
- [Uploads absichern: PHP-Ausführung im Upload-Verzeichnis verhindern](https://s-edv.com/anleitungen/wordpress-uploads-php-ausfuehrung-verhindern-htaccess-nginx)
- [wp-config.php härten: Salts, Dateibearbeitung und Debug-Einstellungen](https://s-edv.com/anleitungen/wordpress-wp-config-haerten-salts-dateibearbeitung-debug)
- [WordPress-Firewall auf Serverebene: fail2ban für wp-login und XML-RPC](https://s-edv.com/anleitungen/wordpress-fail2ban-wp-login-xmlrpc-serverebene-sperren)
- [WordPress Advanced Administration Handbook: Nginx](https://developer.wordpress.org/advanced-administration/server/web-server/nginx/)
- [WordPress Advanced Administration Handbook: Hardening WordPress](https://developer.wordpress.org/advanced-administration/security/hardening/)
- [nginx-Dokumentation: location und Auswahlreihenfolge](https://nginx.org/en/docs/http/ngx_http_core_module.html#location)
- [nginx-Dokumentation: ngx_http_access_module (allow, deny)](https://nginx.org/en/docs/http/ngx_http_access_module.html)
- [Linux-Server mit UFW und Fail2ban absichern](https://s-edv.com/anleitungen/linux-server-absichern-ufw-fail2ban)
