Q&A Klassiker: nginx / Apache – 502, 504, Config-Fehler

Kategorie: Q&A · Channel: #klassiker

Format: BĂŒhnenspiel – Admin · Angreifer · Kunde


Die Situation

Uhrzeit:  09:47 Uhr (Morgenpeak)
Alarm:    HTTP 502 Bad Gateway / 504 Gateway Timeout
Symptom:  Website nicht erreichbar, API antwortet nicht
Kunde:    meldet sich sofort

đŸ‘šâ€đŸ’» Admin-Perspektive: Was sehe ich, was tue ich?

Schritt 1: Welcher Fehler genau?

HTTP 502 Bad Gateway
→ nginx/Apache erreicht Backend aber bekommt ungĂŒltige Antwort
→ Backend (PHP-FPM, App-Server, Upstream) ist kaputt oder crashed

HTTP 504 Gateway Timeout
→ nginx/Apache erreicht Backend nicht rechtzeitig
→ Backend ist zu langsam oder gar nicht erreichbar

HTTP 503 Service Unavailable
→ Backend ĂŒberlastet oder explizit down

HTTP 500 Internal Server Error
→ Backend antwortet aber wirft Fehler
→ meist Applikationsfehler, nicht nginx/Apache selbst

Schritt 2: Logs lesen

# nginx Error-Log
tail -50 /var/log/nginx/error.log
tail -f /var/log/nginx/error.log   # live

# Apache Error-Log
tail -50 /var/log/apache2/error.log
tail -f /var/log/apache2/error.log

# Typische 502 Meldung nginx:
# connect() failed (111: Connection refused) while connecting to upstream
# → PHP-FPM lĂ€uft nicht!

# Typische 504 Meldung nginx:
# upstream timed out (110: Connection timed out)
# → Backend zu langsam (DB? Slow Query?)

# Access-Log: welche URLs betroffen?
tail -100 /var/log/nginx/access.log | grep " 502 \| 504 "
tail -100 /var/log/apache2/access.log | grep " 502 \| 504 "

Schritt 3: Backend-Dienste prĂŒfen

# PHP-FPM Status
systemctl status php8.1-fpm
systemctl status php7.4-fpm
systemctl status php7.2-fpm

# PHP-FPM neu starten (schnelle Lösung)
systemctl restart php8.1-fpm

# PHP-FPM Socket vorhanden?
ls -la /run/php/
# php8.1-fpm.sock sollte da sein

# PHP-FPM Log
tail -30 /var/log/php8.1-fpm.log
journalctl -u php8.1-fpm -n 30

# Upstream App-Server (Node, Python, Java...)
systemctl status myapp
netstat -tlnp | grep :3000    # lÀuft der App-Server?
curl -s http://localhost:3000/health  # antwortet er?

Schritt 4: nginx Konfiguration prĂŒfen

# Config-Syntax testen (IMMER vor reload!)
nginx -t
# nginx: configuration file /etc/nginx/nginx.conf test is successful

# nginx neu laden (kein Verbindungsabbruch)
systemctl reload nginx
# NICHT: systemctl restart nginx (trennt aktive Verbindungen)

# nginx Status
systemctl status nginx
nginx -v   # Version

# Konfiguration debuggen
nginx -T   # komplette effektive Config ausgeben
nginx -T | grep -A 5 "upstream"   # Upstream-Definitionen

Schritt 5: Typische nginx Upstream-Config

# /etc/nginx/sites-available/example.com

upstream php_backend {
    server unix:/run/php/php8.1-fpm.sock;
    # oder TCP:
    # server 127.0.0.1:9000;
}

upstream app_backend {
    server 127.0.0.1:3000;
    server 127.0.0.1:3001 backup;   # Fallback
    keepalive 32;
}

server {
    listen 80;
    server_name example.com;

    # Timeouts – bei 504 hier anpassen:
    proxy_connect_timeout   10s;
    proxy_send_timeout      60s;
    proxy_read_timeout      60s;    # ← bei langsamen Backends erhöhen

    location / {
        proxy_pass http://app_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location ~ \.php$ {
        fastcgi_pass php_backend;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_read_timeout 60s;   # ← bei langsamen PHP-Scripts erhöhen
    }
}

Schritt 6: Apache Konfiguration prĂŒfen

# Config-Syntax testen
apache2ctl configtest
apachectl configtest

# Apache neu laden
systemctl reload apache2

# Apache Status und Module
apache2ctl -M | grep -E "proxy|php|rewrite"

# PHP-Modul aktiv?
a2query -m php8.1    # Debian/Ubuntu
httpd -M | grep php  # RHEL

# Virtualhosts anzeigen
apache2ctl -S
apachectl -S

# Typische Apache PHP-FPM Config:
# /etc/apache2/sites-available/example.com.conf
<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /var/www/html

    # PHP via FPM (mod_proxy_fcgi)
    <FilesMatch \.php$>
        SetHandler "proxy:unix:/run/php/php8.1-fpm.sock|fcgi://localhost"
    </FilesMatch>

    # Proxy Timeout (bei 504 hier anpassen)
    ProxyTimeout 60

    ErrorLog ${APACHE_LOG_DIR}/example.com-error.log
    CustomLog ${APACHE_LOG_DIR}/example.com-access.log combined
</VirtualHost>

Schritt 7: Verbindung zum Backend direkt testen

# TCP-Verbindung testen
curl -v http://localhost:3000/
telnet localhost 3000

# PHP-FPM Socket direkt testen
cgi-fcgi -bind -connect /run/php/php8.1-fpm.sock
# (apt install libfcgi-dev)

# Upstream von außen erreichbar? (sollte es NICHT sein!)
curl http://server-ip:9000/   # PHP-FPM
curl http://server-ip:3000/   # App-Server

đŸŠč Angreifer-Perspektive: Was nutze ich aus?

nginx Fehlermeldungen als Reconnaissance

502/504 Fehler verraten:
→ welches Backend lĂ€uft (PHP-FPM Version im Header)
→ interne IPs wenn Debug-Modus aktiv
→ Technologie-Stack

curl -I https://www.ziel.de
# Server: nginx/1.18.0
# X-Powered-By: PHP/7.4.33   ← Version sichtbar!

Fix:
server_tokens off;            # nginx Version verstecken
# php.ini: expose_php = Off   # PHP Version verstecken

Direkt zugÀngliche Backend-Ports

HĂ€ufiger Fehler: PHP-FPM oder App-Server lauscht auf 0.0.0.0

netstat -tlnp | grep :9000
# tcp 0 0 0.0.0.0:9000 → PHP-FPM von ĂŒberall erreichbar!

Angriff:
# SSRF oder direkte FPM-Anfrage möglich
# PHP-FPM ohne Auth = Remote Code Execution möglich
# CVE-2019-11043: nginx + PHP-FPM = RCE

Fix:
# PHP-FPM auf Socket oder 127.0.0.1 beschrÀnken:
# /etc/php/8.1/fpm/pool.d/www.conf:
listen = /run/php/php8.1-fpm.sock
# NICHT: listen = 0.0.0.0:9000

HTTP Request Smuggling

Bei falsch konfigurierten Proxys:
→ Angreifer schmuggelt zweite HTTP-Anfrage in erste
→ Backend verarbeitet versteckten Request
→ Auth-Bypass möglich

Erkennbar wenn:
→ nginx und Backend interpretieren
  Content-Length und Transfer-Encoding unterschiedlich

PrÀvention:
proxy_http_version 1.1;
proxy_set_header Connection "";

👔 Kunden-Perspektive: Was erlebe ich?

09:47 Uhr – Nachricht:

"Die Website zeigt 'Bad Gateway'.
 Unsere Kunden können nicht bestellen.
 Das passiert jeden Morgen gegen 9-10 Uhr
 wenn viel Betrieb ist. Was ist das Problem?"

→ "jeden Morgen" = wichtiger Hinweis!
   Kein zufĂ€lliger Fehler – reproduzierbar!

Ursache bei "tÀglich morgens": PHP-FPM Pool erschöpft

# PHP-FPM Pool-Config prĂŒfen:
cat /etc/php/8.1/fpm/pool.d/www.conf | grep -E "pm\.|max_children"

# Typische Standardkonfig (zu klein fĂŒr Traffic-Peaks):
# pm = dynamic
# pm.max_children = 5        ← nur 5 PHP-Prozesse gleichzeitig!
# pm.start_servers = 2
# pm.min_spare_servers = 1
# pm.max_spare_servers = 3

# Angepasst fĂŒr Traffic-Peaks:
# pm.max_children = 50
# pm.max_requests = 500      ← Memory-Leak-Schutz

# Aktuellen Pool-Status anzeigen:
curl http://localhost/php-fpm-status  # wenn status page konfiguriert
# oder:
systemctl status php8.1-fpm | grep "processes"

Kommunikation mit dem Kunden:

09:55 Uhr:
"Wir haben die Ursache identifiziert: Der PHP-Prozess-Pool
 war fĂŒr den Morgen-Traffic zu klein konfiguriert.
 Bei mehr als 5 gleichzeitigen Anfragen entstanden Wartezeiten.
 Wir haben die KapazitÀt auf 50 Prozesse erhöht.
 Das Problem sollte morgen frĂŒh nicht mehr auftreten."

🎯 Erkenntnis – Drei Perspektiven zusammen

Perspektive Kernaussage
đŸ‘šâ€đŸ’» Admin nginx -t vor jedem reload · tail -f error.log bei 502
đŸŠč Angreifer PHP-FPM auf 0.0.0.0 = direkter Angriffsvektor
👔 Kunde "tĂ€glich morgens" = PHP-FPM Pool zu klein, nicht Zufall

🔧 PrĂ€vention

# 1. server_tokens off in nginx.conf
# 2. expose_php = Off in php.ini
# 3. PHP-FPM nur auf Socket oder 127.0.0.1
# 4. PHP-FPM Pool-GrĂ¶ĂŸe ans Traffic-Profil anpassen
# 5. nginx Status-Page fĂŒr Monitoring
#    stub_status on; in nginx.conf
# 6. PHP-FPM Status-Page aktivieren
#    pm.status_path = /php-fpm-status
# 7. Timeout-Werte auf Backend-Performance abstimmen
# 8. Monitoring: Alert bei HTTP 502/504 > 5/min

📋 Schnellreferenz: nginx/Apache 502/504 – Einzeiler

# Diagnose
tail -f /var/log/nginx/error.log          # live errors
systemctl status php8.1-fpm               # FPM lÀuft?
ls -la /run/php/                          # Socket vorhanden?
curl -v http://localhost:3000/health      # Backend direkt

# Config-Test (IMMER vor reload!)
nginx -t && systemctl reload nginx
apache2ctl configtest && systemctl reload apache2

# PHP-FPM neu starten
systemctl restart php8.1-fpm

# Pool-Config
grep -E "pm\.|max_children" /etc/php/*/fpm/pool.d/www.conf

# Verbindungen zum Backend
ss -tnp | grep :9000                      # PHP-FPM Verbindungen
netstat -tlnp | grep nginx                # nginx lauscht?