Last updated on 14. August 2025
Kontext: Nginx + PHP-FPM (WordPress), vorgeschaltetes CDN (Cloudflare), Lasttests mit k6; Ziel: Stabilität unter Last, Schutz gegen Cache-Bypass und Bot-Traffic.
Einleitung
Webserver müssen nicht nur normalen Benutzerverkehr verarbeiten, sondern auch böswillige Anfragen, Bot-Angriffe und Lastspitzen abwehren können. Besonders bei Nginx + PHP-FPM kann eine bestimmte Art von Requests die CPU stark belasten und im schlimmsten Fall zum Dienstabbruch führen. In diesem Leitfaden zeigen wir Schritt für Schritt, wie wir mit k6 realistische Last erzeugt und Nginx so gehärtet haben, dass der Server auch bei Cache-Bypass-Wellen und hoher Parallelität stabil bleibt.
Was ist k6?
k6 ist ein modernes, in Go entwickeltes Open-Source-Lasttest-Tool mit JavaScript-Skripting. Es eignet sich für realistische Szenarien (Ramp-up, konstante Raten) und liefert präzise Metriken (Latenz, Fehlerrate, Durchsatz).
- Testskripte in JavaScript
- Stufenweise Erhöhung/Verringerung der Last
- HTTP, WebSocket und API-Tests
- Echtzeit-Reporting der Ergebnisse
Installation (Debian/Ubuntu)
sudo apt update sudo apt install gnupg2 ca-certificates curl -fsSL https://dl.k6.io/key.gpg | sudo gpg --dearmor -o /usr/share/keyrings/k6-archive-keyring.gpg echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list sudo apt update sudo apt install k6
Test-Szenario (miss_test2.js)
Ziel des Szenarios: in kurzer Zeit viele Anfragen generieren, u. a. mit Cache-Bypass-Parametern, um CPU-Last und Fehlerraten realistisch zu beobachten.
import http from 'k6/http';
import { sleep } from 'k6';
export let options = {
stages: [
{ duration: '30s', target: 150 },
{ duration: '30s', target: 150 },
{ duration: '35s', target: 400 },
],
thresholds: {
http_req_duration: ['p(95)<800'],
http_req_failed: ['rate<0.05'],
},
};
export default function () {
const target = __ENV.TARGET || 'https://ihre-seite.de';
let res = http.get(`${target}/?_k6=${Date.now()}`, {
headers: { 'Cache-Control': 'no-cache', 'User-Agent': 'k6-loadtest' }
});
sleep(1);
}
k6 run -e TARGET=https://ihre-seite.de miss_test2.js
Ausgangssituation
- Schneller Anstieg des CPU-Loads (z. B. von 0.50 → 1.19)
- Viele 503 Service Unavailable-Antworten
- Hohe Fehlerrate bei gleichzeitigen Requests
nocache-Parameter leitete alles direkt zu PHP-FPM → hohe CPU-Last
Nginx-Konfigurationsänderungen
1) nginx.conf – Grundkonfiguration
user www-data;
worker_processes auto;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 8192;
multi_accept on;
}
http {
# Real client IP hinter Cloudflare
include /etc/nginx/conf.d/realip.conf;
real_ip_header CF-Connecting-IP;
real_ip_recursive on;
# IP-Anonymisierung für Logs
map $remote_addr $ip_anonym1 {
default 0.0.0;
"~(?P<ip>(\d+)\.(\d+)\.(\d+))\.\d+" $ip;
"~(?P<ip>[^:]+:[^:]+):" $ip;
}
map $remote_addr $ip_anonym2 {
default .0;
"~(?P<ip>(\d+)\.(\d+)\.(\d+))\.\d+" .0;
"~(?P<ip>[^:]+:[^:]+):" ::;
}
map $ip_anonym1$ip_anonym2 $ip_anonymized {
default 0.0.0.0;
"~(?P<ip>.*)" $ip;
}
log_format anonymized '$ip_anonymized - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';
# Core settings
sendfile on;
tcp_nopush on;
types_hash_max_size 2048;
server_tokens off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Timeouts / keepalive
keepalive_timeout 5;
keepalive_requests 1000;
client_header_timeout 10s;
client_body_timeout 10s;
send_timeout 15s;
reset_timedout_connection on;
# SSL
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
# FastCGI (PHP-FPM) Puffer & Timeouts
fastcgi_connect_timeout 5s;
fastcgi_send_timeout 60s;
fastcgi_read_timeout 60s;
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
# Gzip
gzip on;
gzip_comp_level 5;
gzip_min_length 256;
gzip_types text/plain text/css application/javascript application/json application/xml image/svg+xml;
# Anonymized access log
access_log /var/log/nginx/access.log anonymized;
# Include site configs
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
2) /etc/nginx/conf.d/limit.conf – Limit-Zonen
limit_req_zone $binary_remote_addr zone=reqzone:10m rate=5r/s; limit_conn_zone $binary_remote_addr zone=connzone:10m;
3) /etc/nginx/conf.d/realip.conf – Cloudflare Real-IP
Damit Nginx die echte Besucher-IP statt Cloudflare-IPs sieht, werden die offiziellen Cloudflare-Netze als vertrauenswürdig hinterlegt und der Header CF-Connecting-IP ausgewertet:
set_real_ip_from 173.245.48.0/20; set_real_ip_from 103.21.244.0/22; set_real_ip_from 103.22.200.0/22; set_real_ip_from 103.31.4.0/22; set_real_ip_from 141.101.64.0/18; set_real_ip_from 108.162.192.0/18; set_real_ip_from 190.93.240.0/20; set_real_ip_from 188.114.96.0/20; set_real_ip_from 197.234.240.0/22; set_real_ip_from 198.41.128.0/17; set_real_ip_from 162.158.0.0/15; set_real_ip_from 104.16.0.0/13; set_real_ip_from 104.24.0.0/14; set_real_ip_from 172.64.0.0/13; set_real_ip_from 2400:cb00::/32; set_real_ip_from 2606:4700::/32; set_real_ip_from 2803:f800::/32; set_real_ip_from 2405:b500::/32; set_real_ip_from 2405:8100::/32; set_real_ip_from 2a06:98c0::/29;
Ergebnis: Logs und Schutzmechanismen (z. B. limit_req/limit_conn) beziehen sich auf die echte Besucher-IP, nicht auf Cloudflare-IPs.
Da sich die IP-Bereiche von Cloudflare gelegentlich ändern, ist es empfehlenswert, diese Liste automatisch aktuell zu halten. Eine Schritt-für-Schritt-Anleitung dafür findest du hier: Nginx: Cloudflare Real-IP-Liste automatisch aktualisieren (Cron)
4)/etc/nginx/conf.d/fastcgi_cache.conf – fastcgi_cache
fastcgi_cache_path /var/cache/nginx/fastcgi levels=1:2 keys_zone=FPMCACHE:100m inactive=60m max_size=2g; fastcgi_cache_key "$scheme$request_method$host$request_uri";
5) vHost – Limits, Parameter-Blockade & PHP-Micro-Cache
# innerhalb des server { }-Blocks
# IP-basierte Limits
limit_conn connzone 30;
limit_req zone=reqzone burst=30 nodelay;
# Cache-Bypass-Parameter blockieren (optional, aber sinnvoll)
if ($arg_nocache) { return 403; }
if ($arg__bust) { return 403; }
# PHP-Location mit FastCGI-Micro-Cache (anonyme Nutzer)
# location @php { ... } oder location ~ \.php$ { ... }
set $no_cache 0;
if ($request_method = POST) { set $no_cache 1; }
if ($http_cookie ~* "comment_author|wordpress_(?!test_cookie)|wp-postpass|wordpress_logged_in") { set $no_cache 1; }
fastcgi_cache_bypass $no_cache;
fastcgi_no_cache $no_cache;
fastcgi_cache FPMCACHE;
fastcgi_cache_valid 200 301 302 1m;
add_header X-Cache $upstream_cache_status;
Ergebnisse nach den Anpassungen
- CPU-Load stabil bei ~1.0–1.2 (unter kontrollierter Last)
- Schädliche Requests → 403 Forbidden bzw. werden durch Limits gedrosselt
- Normale Besucher → keine spürbaren Einbußen
- Deutlich weniger 503-Antworten
Fazit
Mit k6 als Lasttest-Werkzeug und gezielten Nginx-Anpassungen (Real-IP, limit_req/limit_conn, Micro-Cache) bleibt ein PHP-basierter Webserver auch bei Cache-Bypass-Wellen und hoher Parallelität stabil. Die Maßnahmen reduzieren die CPU-Last, verhindern unnötige PHP-Ausführung und schützen die Verfügbarkeit ohne den normalen Benutzerverkehr zu beeinträchtigen.
Tipp: Passe rate/burst an dein Traffic-Profil an, überprüfe regelmäßig die Cloudflare-IP-Liste und nutze das X-Cache-Header-Monitoring für schnelle Checks (MISS → HIT).
Download: Beispielkonfiguration für Anti-DDoS mit Nginx
Alle in diesem Beitrag erwähnten Konfigurationsdateien können Sie unter folgendem Link als ZIP-Archiv herunterladen. Die Dateien sind gemäß der /etc/nginx/-Verzeichnisstruktur organisiert. Bitte passen Sie die Inhalte vor der Verwendung an Ihre eigenen Anforderungen an.
SHA256-Hash:
530bb4b46ce1c8b299f34db8dc9b1cdaba18e7d5ec3e7a8c9f6d58d6e620d62f
Archivinhalt:
/etc/nginx/nginx.conf/etc/nginx/conf.d/fastcgi_cache.conf/etc/nginx/conf.d/limit.conf/etc/nginx/conf.d/realip.conf/etc/nginx/sites-available/example.com.vhost
Hinweis:: Wenn dir dieser Beitrag gefallen oder geholfen hat, kannst du mich gerne mit einer kleinen Unterstützung motivieren 😊
₿/Ξ: Donate with Bitcoin
Address: bc1qt7wc6jfth4t2szc2hp6340sqp3y0pa9r3ywgrr
Schreib den ersten Kommentar