Ein Reverse Proxy wie NGINX nimmt HTTP-Anfragen entgegen und leitet sie an ein Backend weiter. Das Backend sieht dabei nur den Proxy als direkten Kommunikationspartner. Client-IP, Hostname und Protokoll der ursprünglichen Anfrage gehen verloren – außer der Proxy reicht sie über Forwarded-Header weiter.

Siehe auch: Webanwendungen hinter Reverse Proxies (Pfadpräfixe und X-Forwarded-Prefix).

X-Forwarded-* vs. Forwarded

X-Forwarded-* – historisch gewachsen, kein Standard, aber praktisch überall unterstützt:

X-Forwarded-For: 203.0.113.10
X-Forwarded-Proto: https
X-Forwarded-Host: example.com
X-Forwarded-Port: 443

Forwarded (RFC 7239) – standardisiert, ein Header mit mehreren Parametern:

Forwarded: for=203.0.113.10;host=example.com;proto=https
ParameterBedeutung
forWer die Anfrage an diesen Proxy geschickt hat (Client oder vorheriger Proxy)
hostHost-Header, wie ihn der Proxy empfangen hat
protoProtokoll der eingehenden Verbindung (http/https)
bySchnittstelle des Proxys, an der die Anfrage ankam

Alle Parameter sind optional. Statt einer IP sind auch unknown oder anonyme Kennungen wie _proxy1 erlaubt.

RFC 7239 ist ein eigenständiger Standard. Er wurde nicht in RFC 9110 (HTTP Semantics) übernommen.

Die Kette über mehrere Proxies

Browser ──HTTPS──▶ Proxy A (192.0.2.20) ──HTTP──▶ NGINX ──HTTP──▶ Backend

Proxy A setzt:

Forwarded: for=203.0.113.10;proto=https

NGINX hängt seinen Eintrag mit Komma an. for ist jetzt die Adresse von Proxy A, proto die Verbindung zwischen A und NGINX:

Forwarded: for=203.0.113.10;proto=https, for=192.0.2.20;proto=http

Bei X-Forwarded-For funktioniert es genauso: 203.0.113.10, 192.0.2.20.

Header sind Client-Eingaben

Jeder Client kann Forwarded oder X-Forwarded-For selbst mitschicken. Vertrauenswürdig sind nur die Einträge, die deine eigenen Proxies angehängt haben – also die von rechts gelesenen, bis zum ersten Hop, den du nicht kontrollierst. Der Eintrag ganz links ist frei fälschbar.

Konfiguration in NGINX

location / {
    proxy_pass http://backend:8080;
    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;
    proxy_set_header X-Forwarded-Host  $host;
    proxy_set_header X-Forwarded-Port  $server_port;
}

$proxy_add_x_forwarded_for hängt $remote_addr an einen vorhandenen X-Forwarded-For-Header an (oder ist nur $remote_addr, wenn keiner da ist). Für Forwarded gibt es keine vergleichbare eingebaute Variable.

Ist NGINX selbst der äußerste Proxy, sollte er Client-Werte nicht übernehmen, sondern überschreiben:

proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Forwarded       "";   # leerer Wert = Header wird nicht weitergegeben

Das Entfernen von Forwarded ist auch dann wichtig, wenn du ihn gar nicht nutzt: Manche Frameworks (z. B. Springs ForwardedHeaderFilter) bevorzugen Forwarded, sobald er vorhanden ist.

Forwarded mit map erweitern

Um eine vorhandene Forwarded-Kette zu erhalten und zu erweitern, braucht man eine map im http-Kontext:

map $http_forwarded $proxy_forwarded {
    ""      "for=$remote_addr;proto=$scheme";
    default "$http_forwarded, for=$remote_addr;proto=$scheme";
}
proxy_set_header Forwarded $proxy_forwarded;

Das ist bewusst vereinfacht:

  • IPv6 muss in Anführungszeichen und eckigen Klammern stehen: for="[2001:db8::1]". Das Beispiel oben erzeugt dann einen ungültigen Header.
  • Ein vorhandener Header darf nur übernommen werden, wenn er von einem vertrauenswürdigen Proxy stammt.

Braucht man beide?

Meist nicht. Spring Boot, Express (trust proxy) und die meisten anderen Frameworks kommen mit X-Forwarded-* aus – Express wertet Forwarded gar nicht aus. Forwarded lohnt sich nur, wenn ein Backend oder die bestehende Infrastruktur ihn ausdrücklich erwartet.

Beides gleichzeitig zu setzen, erhöht den Aufwand und kann zu widersprüchlichen Werten führen.

Steht vor NGINX noch ein Load Balancer, beziehen sich $remote_addr, $scheme, $host und $server_port auf die Verbindung vom Load Balancer, nicht auf den Client. Die echte Client-IP stellt das Modul ngx_http_realip_module her (set_real_ip_from + real_ip_header). Das Protokoll muss dann aus dem eingehenden X-Forwarded-Proto übernommen werden.

Fazit

  • X-Forwarded-*: mehrere einzelne, weitverbreitete Header – der bewährte Standardweg.
  • Forwarded: ein standardisierter Header (RFC 7239) mit mehreren Parametern – nur bei Bedarf.
  • $proxy_add_x_forwarded_for: erweitert die X-Forwarded-For-Kette. Am äußersten Proxy stattdessen $remote_addr verwenden.
  • map: nötig, um eine Forwarded-Kette zu erweitern.

Entscheidend bleibt: Die Anwendung darf nur Proxy-Informationen auswerten, die von vertrauenswürdigen Proxies stammen.