Net-Base Magazin

23.05.2026

Reverse proxy nginx-szel és Delphi: tiszta Forwarded-kezelés, valódi kliens-IP és robosztus URL-alapok

Ha Delphi-REST-szerverek nginx mögött futnak, gyakran elcsúszik a kliens IP, a HTTPS-felismerés és az abszolút URL-ek kezelése. Ez a kódrészlet egy robusztus Forwarded-/X-Forwarded-kezelést mutat be (beleértve a Trust-Proxy-listát), tipikus nginx-beállításokat és üzemeltetési hibakeresési javaslatokat.

23.05.2026

Ein Reverse Proxy mit nginx und Delphi ist in der Praxis meist kein „nice to have“, sondern die saubere Trennung zwischen Internetkante und Applikation: TLS-Terminierung (HTTPS-Offloading), zentrale Header-/CORS-Regeln, Rate-Limits, einheitliche Logs, Blue/Green-Rollouts oder einfach das Hosting mehrerer Services unter einer Domain. Was dann gerne unterschätzt wird: Sobald nginx „davor“ sitzt, sieht der Delphi-Server nur noch die Proxy-IP, oft nur noch „http“ statt „https“ und generiert falsche absolute Links (Redirects, Callback-URLs, OpenAPI-Server-URL). Genau diese drei Punkte sorgen später für Debugging-Zeit im Betrieb.

Dieser Source-Schnipsel zeigt ein robustes Muster, wie Sie in Delphi Forwarded bzw. X-Forwarded-* sauber auswerten – inklusive Trust-Proxy-Liste (wichtig gegen Header-Spoofing) und einer konsistenten Request-Base-URL. Dazu gibt es praxistaugliche nginx-Konfigurationen und Hinweise zu Randfällen wie WebSockets, große Uploads und Timeouts.

Warum Reverse Proxy-Setups Delphi-Server „verwirren“

nginx spricht als Reverse Proxy mit dem Delphi-Service typischerweise unverschlüsselt (HTTP) im internen Netz oder auf localhost, während der Client außen per HTTPS kommt. Ohne zusätzliche Header weiß Delphi nichts von:

  • Original-Schema (https vs. http) – relevant für Redirects und absolute URLs.
  • Original-Host (kundenspezifische Domain, Port) – relevant für Multi-Tenant-Setups, CORS und Callback-URLs.
  • Original-Client-IP – relevant für Audit, Rate-Limits, Geo-Checks und Security-Auswertungen.

nginx kann diese Informationen über Header transportieren. Üblich sind X-Forwarded-For, X-Forwarded-Proto und X-Forwarded-Host; standardisiert ist zusätzlich der RFC-Header Forwarded. Wichtig: Diese Header sind aus Sicht der Applikation nicht automatisch vertrauenswürdig, weil ein Client sie selbst schicken kann – sie werden erst vertrauenswürdig, wenn sie von einem bekannten Proxy stammen.

nginx-Konfiguration: die minimal sinnvollen Proxy-Header

Ein solider Startpunkt (HTTP/1.1, Keep-Alive, Upgrade für WebSockets) sieht so aus. Das Snippet ist bewusst knapp gehalten; Sie ergänzen je nach Umgebung HSTS, Rate-Limits und Access-Logs.

Delphi
# (nginx-Konfiguration, kein Delphi)
server {
  listen 443 ssl http2;
  server_name api.example.com;

  # ssl_certificate ...; ssl_certificate_key ...;

  location / {
    proxy_http_version 1.1;
    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;

    # optional, aber praktisch für absolute URLs
    proxy_set_header X-Forwarded-Host  $host;
    proxy_set_header X-Forwarded-Port  $server_port;

    # WebSockets / Upgrade
    proxy_set_header Upgrade           $http_upgrade;
    proxy_set_header Connection        $connection_upgrade;

    proxy_pass http://127.0.0.1:8080;

    # Timeouts passend zu Delphi-Backend (lange Reports/Exports)
    proxy_connect_timeout 5s;
    proxy_send_timeout    60s;
    proxy_read_timeout    60s;

    # große Uploads explizit steuern
    client_max_body_size 50m;
  }
}

Cél: Az alkalmazás megbízhatóan megkapja a Hostot, a kliens IP-címét és a sémát. Korlátozás: $proxy_add_x_forwarded_for hozzáfűzi az aktuális proxy IP-címét az esetleg meglévő lánchoz; ez előnyös többproxy-s beállításoknál, ugyanakkor ezért különösen fontossá teszi a helyes kiértékelést a Delphi oldalon. Buktató: Ha nginx-ben nem állítja be a Host-fejlécet, akkor Delphi előfordulhat, hogy csak az upstream hostot (127.0.0.1) látja, ami megtöri az átirányításokat és az origin-ellenőrzéseket.

Delphi forrásrészlet: Forwarded/X-Forwarded robusztus kiértékelése (Trust-Proxy-listával)

A következő kód szándékosan keretrendszer-semleges: egy minimális interfészen (Header + RemoteIP) dolgozik, és adaptálható WebBrokerbe, RAD Serverbe vagy Horse-ba. Főbb pontok:

  • Prioritás: RFC Forwarded (ha rendelkezésre áll) megelőzi az X-Forwarded-*-et.
  • Megbízhatóság: A Forwarded-fejléceket csak akkor értékelje ki, ha a közvetlen peer (RemoteIP) ismert proxy.
  • Parszolás: Vegye figyelembe az IPv6-ot, idézőjeleket, portokat és az X-Forwarded-For láncait.
  • Kimenet: egy alap-URL, amelyet abszolút hivatkozásokhoz, átirányításokhoz vagy OpenAPI-hoz használhat.

unit Net-Base.ProxyForwarding;

interface

uses
System.SysUtils, System.Classes, System.Generics.Collections, System.NetEncoding;

type
// Minimális adapter-interfész: valósítsa meg WebBroker/Horse stb. számára.
IHeaderReader = interface
[‚{C2D2E5B9-2C2E-4D37-9D73-3CDB5A7E7EEA}‘]
function GetHeaderValue(const AName: string): string;
function GetRemoteIP: string; // közvetlen TCP-távoli végpont (általában nginx)
end;

TForwardedInfo = record
ClientIP: string;
Proto: string; // http/https
Host: string;
Port: Integer;
function EffectiveScheme: string;
function EffectiveHostPort: string;
function BaseUrl: string; // pl. https://api.example.com
end;

TTrustedProxyList = class
private
FSet: TDictionary;
class function NormalizeIp(const AIP: string): string; static;
public
constructor Create;
destructor Destroy; override;
procedure Add(const AIP: string);
function Contains(const AIP: string): Boolean;
end;

function ResolveForwardedInfo(const Req: IHeaderReader; const TrustedProxies: TTrustedProxyList): TForwardedInfo;

implementation

function StripBrackets(const S: string): string;
begin
Result := S.Trim;
if (Result.StartsWith(‚[‚)) and (Result.EndsWith(‚]‘)) then
Result := Result.Substring(1, Result.Length – 2);
end;

function RemoveQuotes(const S: string): string;
var
T: string;
begin
T := S.Trim;
if (T.Length >= 2) and (((T[1] = ‚“‚) and (T[T.Length] = ‚“‚)) or ((T[1] = ““) and (T[T.Length] = ““))) then
Result := T.Substring(1, T.Length – 2)
else
Result := T;
end;

function FirstCsvToken(const S: string): string;
var
P: Integer;
begin
// Az X-Forwarded-For lehet „client, proxy1, proxy2″
P := S.IndexOf(‚,‘);
if P >= 0 then
Result := S.Substring(0, P).Trim
else
Result := S.Trim;
end;

procedure SplitHostPort(const HostPort: string; out Host: string; out Port: Integer);
var
S: string;
P: Integer;
begin
Host := “;
Port := 0;
S := HostPort.Trim;

// IPv6 []-ben: [2001:db8::1]:443
if S.StartsWith(‚[‚) then
begin
P := S.IndexOf(‚]‘);
if P >= 0 then
begin
Host := StripBrackets(S.Substring(0, P + 1));
if (P + 1 < S.Length) and (S[P + 2] = ‚:‘) then
Port := StrToIntDef(S.Substring(P + 2), 0);
Exit;
end;
end;

// IPv4/host: host:port (Figyelem: mezítelen IPv6 esetén [] nélkül nem megbízható)
P := S.LastIndexOf(‚:‘);
if (P > 0) and (S.IndexOf(‚:‘) = P) then
begin
Host := S.Substring(0, P);
Port := StrToIntDef(S.Substring(P + 1), 0);
end
else
Host := S;
end;

function ParseForwardedHeader(const ForwardedValue: string; out ClientIP, Proto, Host: string): Boolean;
var
// Példa: Forwarded: for=203.0.113.43;proto=https;host=api.example.com
Parts: TArray<string>;
I: Integer;
KV: TArray<string>;
K, V: string;
FirstElement: string;
begin
Result := False;
ClientIP := “;
Proto := “;
Host := “;

if ForwardedValue.Trim = “ then
Exit;

// Több elem vesszővel elválasztva; az elsőt vesszük (a klienshez legközelebb lévő)
FirstElement := FirstCsvToken(ForwardedValue);
Parts := FirstElement.Split([‚;‘]);
for I := 0 to High(Parts) do
begin
KV := Parts[I].Split([‚=‘], 2);
if Length(KV) <> 2 then
Continue;
K := KV[0].Trim.ToLower;
V := RemoveQuotes(KV[1]);

if K = ‚for‘ then
begin
// a for értéke lehet IP vagy „unknown“; IPv6 esetén []-ben lehet
V := V.Trim;
if SameText(V, ‚unknown‘) then
Continue;
// Előfordulhat, hogy for=1.2.3.4:5678
if V.Contains(‚:‘) and (not V.StartsWith(‚[‚)) then
V := FirstCsvToken(V); // defensiv
ClientIP := StripBrackets(V.Split([‚:‘])[0]);
end
else if K = ‚proto‘ then
Proto := V.Trim.ToLower
else if K = ‚host‘ then
Host := V.Trim;
end;

Result := (ClientIP <> “) or (Proto <> “) or (Host <> “);
end;

{ TForwardedInfo }

function TForwardedInfo.EffectiveScheme: string;
begin
if Proto <> “ then
Exit(Proto);
Result := ‚http‘;
end;

function TForwardedInfo.EffectiveHostPort: string;
begin
if Host = “ then
Exit(“);

if (Port > 0) and not ((EffectiveScheme = ‚https‘) and (Port = 443)) and not ((EffectiveScheme = ‚http‘) and (Port = 80)) then
Result := Host + ‚:‘ + Port.ToString
else
Result := Host;
end;

function TForwardedInfo.BaseUrl: string;
var
HP: string;
begin
HP := EffectiveHostPort;
if HP = “ then
Exit(“);
Result := EffectiveScheme + ‚://‘ + HP;
end;

{ TTrustedProxyList }

constructor TTrustedProxyList.Create;
begin
inherited Create;
FSet := TDictionary<string, Boolean>.Create;
end;

destructor TTrustedProxyList.Destroy;
begin
FSet.Free;
inherited;
end;

class function TTrustedProxyList.NormalizeIp(const AIP: string): string;
begin
// Valódi IPv6-normalizáláshoz IP-elemzés szükséges; itt szándékosan pragmatikus.
Result := AIP.Trim;
Result := StripBrackets(Result);
end;

procedure TTrustedProxyList.Add(const AIP: string);
begin
FSet.AddOrSetValue(NormalizeIp(AIP), True);
end;

function TTrustedProxyList.Contains(const AIP: string): Boolean;
begin
Result := FSet.ContainsKey(NormalizeIp(AIP));
end;

function ResolveForwardedInfo(const Req: IHeaderReader; const TrustedProxies: TTrustedProxyList): TForwardedInfo;
var
RemoteIP: string;
Fwd, XFF, XProto, XHost, XPort: string;
ClientIP, Proto, Host: string;
Port: Integer;
begin
Result := Default(TForwardedInfo);

RemoteIP := Req.GetRemoteIP;
Result.ClientIP := RemoteIP; // Alapértelmezés: közvetlen távoli végpont

// Csak ha a közvetlen távoli végpont ismert proxy, értelmezzük a Forwarded fejlécet.
if (TrustedProxies <> nil) and TrustedProxies.Contains(RemoteIP) then
begin
Fwd := Req.GetHeaderValue(‚Forwarded‘);
if ParseForwardedHeader(Fwd, ClientIP, Proto, Host) then
begin
if ClientIP <> “ then Result.ClientIP := ClientIP;
if Proto <> “ then Result.Proto := Proto;
if Host <> “ then
begin
SplitHostPort(Host, Result.Host, Result.Port);
end;
end;

// X-Forwarded-* mint tartalék/kiegészítés
XFF := Req.GetHeaderValue(‚X-Forwarded-For‘);
if (Result.ClientIP = RemoteIP) and (XFF.Trim <> “) then
Result.ClientIP := StripBrackets(FirstCsvToken(XFF));

XProto := Req.GetHeaderValue(‚X-Forwarded-Proto‘);
if (Result.Proto = “) and (XProto.Trim <> “) then
Result.Proto := FirstCsvToken(XProto).ToLower;

XHost := Req.GetHeaderValue(‚X-Forwarded-Host‘);
if (Result.Host = “) and (XHost.Trim <> “) then
Result.Host := FirstCsvToken(XHost);

XPort := Req.GetHeaderValue(‚X-Forwarded-Port‘);
Port := StrToIntDef(FirstCsvToken(XPort), 0);
if (Result.Port = 0) and (Port > 0) then
Result.Port := Port;
end;

// Szükség esetén vegyük a hostot a „Host“ fejlécből (ha nincs proxy-fejléc)
if Result.Host = “ then
begin
Host := Req.GetHeaderValue(‚Host‘);
if Host <> “ then
SplitHostPort(Host, Result.Host, Result.Port);
end;
end;

end.

Cél: Minden kérésből konzisztens nézetet kap az ClientIP, Proto és Host mezőkre, valamint egy BaseUrl-re. Ezen információkat központilag használhatja naplózásra, biztonsági döntésekhez (pl. IP-engedélylista) és linkek generálásához.

Miért szükséges a Trust-Proxy lista: Trust-ellenőrzés nélkül egy támadó közvetlenül elérheti az Ön Delphi-portját (konfigurációs hiba, belső routing, VPN) és egyszerűen elküldheti az X-Forwarded-For: 127.0.0.1 fejlécet. Ennek következtében audit-nyomvonalak, rate-limit-ek vagy a „csak belső” végpontok támadhatóvá válnának. Csak akkor bízzon a Forwarded-fejlécekben, ha a közvetlen peer (RemoteIP) olyan proxy, amelyet Ön ellenőriz (pl. 127.0.0.1, load-balancer IP, Kubernetes Ingress).

Veszélyforrások: IPv6 szögletes zárójelek nélkül a Host:port jelölésben nem egyértelmű. A HTTP-Host fejlécben az IPv6 általában [] formában szerepel; tartsa be ezt. Komplex IP-tartományok (CIDR) esetén bővítenie kell a Trust-listát (pl. valódi IP-parserekkel).

Integráció WebBroker/Horse/RAD Server: hol csatlakozik a kód

WebBroker (TWebRequest) esetén a fejlécek tipikusan a ContentFields-en vagy a GetFieldByName-en keresztül érkeznek, a Remote-IP pedig a szerver backendjétől függ. Horse (vagy más HTTP-keretrendszerek) esetén általában létezik Req.Headers és egy Remote-IP tulajdonság. A lényeg elve: a RemoteIP-nek a TCP-ellenfélnek kell lennie, nem valamelyik fejlécértéknek.

Gyakorlatban bevált megoldás: a szolgáltatás indításakor hozzon létre egy TTrustedProxyList-et konfigurációból (INI/ENV), pl. „127.0.0.1“ helyi nginx beállításokhoz vagy a Load Balancer IP-jéhez. Ezután hívja meg per kérés a ResolveForwardedInfo-t, és írja a mezőket a strukturált naplójába (JSON-log, Syslog vagy Windows Event Log).

Üzem közbeni hibakeresés: hibák megtalálása percek alatt, nem órák alatt

Ha a kérések „furcsának“ tűnnek, ritkán magával az Delphi-HTTP-val van a baj, sokkal inkább a proxy-fejlécek, átirányítási logika és timeoutok kombinációja. Három hibakeresési ellenőrzés, amely a gyakorlatban megtérül:

  1. Header-dump (célzott): 4xx/5xx esetén naplózza továbbá a Host-ot, Forwarded-et, X-Forwarded-For-t, X-Forwarded-Proto-t, User-Agent-et és a Request-URI-t. De csak hibák esetén – különben a napló költséges és nehezen áttekinthető lesz.
  2. Base-URL ellenőrzése: Ha átirányítások vagy callback-URL-ek meghiúsulnak, naplózza a ForwardedInfo.BaseUrl-t. Sok hiba azonnal látható („http://127.0.0.1“ a „https://api…“ helyett).
  3. Timeout-korreláció: A proxytól kapott 504 nem ugyanaz, mint egy Delphi-timeout. Az nginx proxy_read_timeout és az Delphi-oldali idle-/read-timeoutoknak össze kell hangolódniuk.

Szélső esetek: WebSockets, streamelés és nagy kérések

WebSockets nginx mögött

WebSocketekhez az nginxnek helyesen kell kezelnie az Upgrade és Connection fejléceket. Emellett a backendnek nem szabad „túl korán“ lezárnia a kapcsolatot. Az Delphi-oldalon fontos, hogy a WebSocket-komponense (vagy egy SSE/streaming végpont) kezelni tudja a reverse proxy-kat, és a heartbeatek/keep-alive-ok tisztán legyenek megvalósítva.

Nagy feltöltések és 413 hibák

Egy klasszikus eset: az Delphi elfogadja a feltöltést, de az nginx előtte blokkol 413 Request Entity Too Large hibával. Szabályozza ezt kifejezetten a client_max_body_size-szel, és igazítsa az Delphi-oldali kéréskorlátokat. Folyamatközeli szoftvermegoldásoknál dokumentum- vagy képadatokkal ez nem kivétel, hanem normál üzemmenet.

HTTPS-offload és „Secure Cookies“

Ha az Ön Delphi-szolgáltatása munkamenet-cookie-kat állít be, akkor külső HTTPS esetén ezeket általában Secure jelöléssel kell ellátni. Hogy az alkalmazása ezt megteszi-e, gyakran attól függ, hogy „tudja‑e”, hogy az eredeti kérés HTTPS volt. Éppen ebben segít a X-Forwarded-Proto/Forwarded konzisztens kiértékelése.

Mikor éri meg a ráfordítás – és hol válhat problémássá

Az itt bemutatott megközelítés akkor éri meg, amikor a Delphi-szolgáltatás már nem „meztelenül” él a LAN-ban, hanem egy produktív élvonali környezet része: több domain, SSO/SAML-felületek, Public APIs, több bérlős működés vagy szigorúbb auditkövetelmények. Probléma akkor lép fel, ha a Forwarded-fejléceknek vakon hisznek, vagy a proxy-topológiát nem dokumentálják (több Ingress-szint, Cloud-LB plus nginx plus Sidecar). Ilyenkor a kliens IP-je és a séma gyorsan „valami” lesz.

Egyértelmű határvonal: ha összetett trust-szabályokra van szüksége (CIDR, IPv6-hálózatok, dinamikus LB-IP-k), akkor bővítse a trust-ellenőrzést (valódi IP-parszolás, hálózati maszkok), vagy alakítsa az infrastruktúrát úgy, hogy csak egy definiált proxy érhesse el a Delphi-portot (Firewall/Security Groups). Ez végső soron általában a robosztusabb üzemeltetési döntés.

Fazit: Reverse Proxy mit nginx und Delphi sauber betreiben heißt „Forwarded richtig machen“

Egy nginx alapú reverse proxy jó standard építőelem Delphi-REST-Server számára – de csak a Forwarded és a X-Forwarded-* helyes kezelése teszi a rendszert üzem közben stabilissá. A lényeg egyszerű: fejlécek csak megbízható proxyktól fogadása, Client-IP/Scheme/Host konzisztens levezetése és ennek az alapnak az átvezetése átirányításokba, naplózásba és biztonsági ellenőrzésekbe. A fenti snippettel tiszta, legacy-kompatibilis alapot kap, amely integrálható WebBrokerbe, Horse-ba vagy saját HTTP-szerverekbe.

Ha meglévő Delphi-backendet konszolidál nginx mögé, vagy egyértelmű üzemeltetési felelősséggel rendelkező Delphi REST-API und REST-Server irányába modernizálna, akkor a proxy-lánc és a header-kiértékelés műszaki átvizsgálása gyakran a leggyorsabb beavatkozás. Vegye fel a kapcsolatot Net-Base egy rövid műszaki besorolásért.

A szakmai környezetben az Nginx reverse proxy és a Forwarded fejlécek is fontos szerepet játszanak, ha az integrációk, az adatfolyamok és a további fejlesztés tisztán kell, hogy együttműködjenek.

Projekt vagy modernizációs kezdeményezés megbeszélése Net-Base.

Bejegyzés megosztása

Ezt a bejegyzést közvetlenül megosztani

LinkedIn, X, XING, Facebook, WhatsApp és E-Mail azonnal elérhetők. Instagramra a linket és a rövid szöveget közvetlenül előkészítjük.

E-mail

Az Instagram egy új lapon nyílik meg. A link és a rövid szöveg előzetesen a vágólapra másolódik.