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.
# (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:
- 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.
- 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).
- 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.