Net-Base Ajakiri

23.05.2026

Reverse-proxy nginxiga ja Delphi: korrektne Forwarded-käsitlemine, reaalne kliendi-IP ja robustsed URL-baasid

Kui Delphi-REST-serverid töötavad nginxi taga, kipuvad Client-IP, HTTPS-tuvastus ja absoluutsed URL-id sageli valeks jääma. See koodilõik näitab robustset Forwarded-/X-Forwarded-päiste käsitlemist (sh Trust-Proxy-nimekiri), tüüpilisi nginx-seadeid ja silumisjuhiseid operatiivseks käitamiseks.

23.05.2026

Ajakirjateemast projektipraktikasse

Sobivad teenuse- ja tehnilised lehed postituse jaoks

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.

See lähtekoodilõik näitab robustset mustrit, kuidas Delphi-is Forwarded ehk X-Forwarded-* korrektselt töödelda – kaasa arvatud Trust-Proxy-Liste (tähtis päiste võltsimise vastu) ja ühtne Request-Base-URL. Lisaks on olemas praktilised nginx-konfiguratsioonid ja juhised äärejuhtude kohta, näiteks WebSocketid, suured üleslaadimised ja timeout’id.

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:

  • Algne skeem (https vs. http) – relevant für Redirects und absolute URLs.
  • Algne host (kundenspezifische Domain, Port) – relevant für Multi-Tenant-Setups, CORS und Callback-URLs.
  • Algne kliendi-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;
  }
}

Eesmärk: Rakendusele edastatakse Host, kliendi-IP ja skeem usaldusväärselt. Piirtingimus: $proxy_add_x_forwarded_for lisab praeguse proksi-IP olemasolevasse ketti; see on hea mitme proksi seadistuse puhul, kuid muudab korrektse tõlgendamise Delphi-poolel veelgi olulisemaks. Püünis: Kui te nginx-is Host-päist ei sea, näeb Delphi võib-olla ainult upstream-hosti (127.0.0.1), mis rikub ümbersuunamisi ja origin-kontrolle.

Delphi koodilõik: Forwarded/X-Forwarded robustne tõlgendamine (usaldusproksi nimekirjaga)

Alljärgnev kood on teadlikult raamistikuneutraalne: see töötab minimaalse liidese (Header + RemoteIP) vastu ja on kohandatav WebBrokeri, RAD Serveri või Horse’iga. Olulised punktid:

  • Prioriteet: RFC Forwarded (kui olemas) eelistada X-Forwarded-*-ile.
  • Usaldus: Forwarded-päiseid tõlgendada ainult siis, kui otsene peer (RemoteIP) on teadaolev proksi.
  • Parsimine: arvestada IPv6, jutumärke, porte ja X-Forwarded-For’i aadressikette.
  • Väljund: baas-URL, mida saate kasutada absoluutsete linkide, ümbersuunamiste või OpenAPI jaoks.

unit Net-Base.ProxyForwarding;

interface

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

type
// Minimaalne adapterliides: rakendage seda WebBroker/Horse/jne jaoks.
IHeaderReader = interface
[‚{C2D2E5B9-2C2E-4D37-9D73-3CDB5A7E7EEA}‘]
function GetHeaderValue(const AName: string): string;
function GetRemoteIP: string; // otsene TCP-vastaspool (enamasti nginx)
end;

TForwardedInfo = record
ClientIP: string;
Proto: string; // http/https
Host: string;
Port: Integer;
function EffectiveScheme: string;
function EffectiveHostPort: string;
function BaseUrl: string; // nt. 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
// X-Forwarded-For võib olla „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 in []: [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 (Tähelepanu: alasti IPv6 ilma [] ei ole usaldusväärne)
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
// Näide: 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;

// Mitmed elemendid on komadega eraldatud; võtame esimest (kliendi lähim)
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
// for võib olla IP või „unknown“; IPv6 võib olla [] sees
V := V.Trim;
if SameText(V, ‚unknown‘) then
Continue;
// for=1.2.3.4:5678 võib esineda
if V.Contains(‚:‘) and (not V.StartsWith(‚[‚)) then
V := FirstCsvToken(V); // defensiivne
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
// Tõelise IPv6 normaliseerimise jaoks oleks vaja IP-parsimist; siin on see teadlikult pragmaatiline.
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; // Varuplaan: otsene vastaspool

// Ainult kui otsene vastaspool on tuntud proksi, analüüsime Forwarded päiseid.
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-* varuplaanina/täiendavalt
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;

// Vajadusel võtta Host päisest ‚Host‘ (kui proksi päiseid pole)
if Result.Host = “ then
begin
Host := Req.GetHeaderValue(‚Host‘);
if Host <> “ then
SplitHostPort(Host, Result.Host, Result.Port);
end;
end;

end.

Eesmärk: Te saate igast päringust konsistentse vaate ClientIP, Proto ja Host ning ka BaseUrl. Neid andmeid võite kasutada keskse logimise, turvalisuseotsuste (nt IP-Allowlist) ja linkide genereerimise jaoks.

Miks Trust-Proxy-loend on vajalik: Ilma usalduskontrollita võiks ründaja otse teie Delphi-pordini jõuda (valekonfiguratsioon, sisemine reiting, VPN) ja lihtsalt saata X-Forwarded-For: 127.0.0.1. Sellega muutuvad auditeerimisjäljed, Rate-Limits või „ainult sisevõrgus lubatud“ lõpp-punktid rünnatavaks. Usaldage Forwarded-päisteid ainult siis, kui otsene peer (RemoteIP) on proxy, mida kontrollite (nt 127.0.0.1, Load-Balancer-IP, Kubernetes-Ingress).

Tõrkepunktid: IPv6 ilma nurksulgudeta ei ole Host:port-notatsioonis ühemõtteline. HTTP-Host-päises on IPv6 tavaliselt märgitud []-ga; järgige seda. Komplekssete IP-vahemike (CIDR) toetamiseks peate Trust-loendit laiendama (nt tõelise IP-parsimise kaudu).

Integratsioon WebBroker/Horse/RAD Serveriga: kuhu kood „ankurdub“

WebBrokeris (TWebRequest) tulevad päised tavaliselt läbi ContentFields või GetFieldByName, Remote-IP sõltub serveri back-endist. Horses (või teistes HTTP-raakides) on tavaliselt Req.Headers ja Remote-IP omadus. Oluline on põhimõte: RemoteIP peab olema TCP-kaaslane, mitte mingi päise väärtus.

Praktiliselt hästi toimiv lähenemine: looge teenuse käivitamisel konfiguratsioonist (INI/ENV) TTrustedProxyList, nt „127.0.0.1“ kohalike nginx-seadistuste jaoks või teie Load Balanceri IP. Seejärel kutsuge iga päringu puhul ResolveForwardedInfo ja kirjutage väljad oma struktureeritud logisse (JSON-Log, Syslog või Windows Event Log).

Tõrkeotsing käigus: nii leiate vead minutitega, mitte tundidega

Kui päringud tunduvad „veidrad“, ei ole süüdi tavaliselt Delphi-HTTP ise, vaid kombinatsioon proxy-päistest, redirect-loogikast ja timeout’idest. Kolm debug-kontrolli, mis igapäevaselt tasuvad end ära:

  1. Header-Dump (sihipärane): Logige 4xx/5xx korral lisaks Host, Forwarded, X-Forwarded-For, X-Forwarded-Proto, User-Agent ja Request-URI. Aga ainult vigade korral – vastasel juhul muutub log kalliks ja ülevaatamatu.
  2. Base-URL kontroll: Kui redirectid või callback-URLid ebaõnnestuvad, logige ForwardedInfo.BaseUrl. Paljud vead on kohe nähtavad („http://127.0.0.1“ selle asemel, et „https://api…“).
  3. Timeoutide korrelatsioon: 504 proxylt ei ole sama mis Delphi-timeoute. nginx proxy_read_timeout ja Delphi-poolel olevad Idle-/Read-timeoutid peavad kokku sobima.

Erijuhtumid: WebSockets, streaming ja suured päringud

WebSockets nginx-i taga

WebSocketide jaoks peab nginx-i Upgrade ja Connection olema õigesti seadistatud. Lisaks ei tohi backend liiga vara ühendust sulgeda. Delphi-poolt on oluline, et teie WebSocket-komponent (või SSE/Streaming-lõpp-punkt) oskaks suhelda reverse proxydega ning et Heartbeats/Keep-Alives oleksid korrektselt implementeeritud.

Suured üleslaadimised ja 413-vead

Klassika: Delphi võtab üleslaadimise vastu, aga nginx blokeerib eelnevalt 413 Request Entity Too Large. Juhtige seda selgelt üle client_max_body_size ja kohandage Delphi-poolel Request-limiite. Protsessipõhiste tarkvaralahenduste puhul, mis töötlevad dokumente või pilte, ei ole see erand – see on igapäevatöö.

HTTPS-Offloading und „Secure Cookies“

Kui teie Delphi-teenus seab sessiooniküpsiseid, tuleb need välise HTTPS-i korral tavaliselt märgistada kui Secure. Kas teie rakendus seda teeb, sõltub tihti sellest, kas see „teab“, et algne päring oli HTTPS. Siin aitab järjekindel X-Forwarded-Proto/Forwarded-päiste analüüs.

Millal see pingutus end tasub – ja kus see võib ebaõnnestuda

Esitatud lähenemine tasub end ära alati, kui Delphi-teenus ei ela enam „alasti“ LAN-is, vaid on osa tootmisservast: mitu domeeni, SSO/SAML-liidesed, avalikud API-d, mitmeklienditugi või rangemad auditinõuded. See lähenemine kukub läbi seal, kus Forwarded-päistele usaldatakse pimesi või proxy-topoloogiad ei ole dokumenteeritud (mitmed ingress-kihid, Cloud-LB plus nginx plus Sidecar). Sellisel juhul muutuvad kliendi IP ja skeem kiiresti „midagi“.

Selge piir: kui vajate keerukaid usaldusreegleid (CIDR, IPv6-võrgud, dünaamilised LB-IP-d), peaksite usalduskontrolli laiendama (päris IP-parsimine, alamvõrgu maskid) või kujundama infrastruktuuri nii, et ainult määratud proxy pääseb Delphi-pordile ligi (Firewall/Security Groups). See on lõpptulemusena sageli töökindlam operatsioonide otsus.

Järeldus: Reverse-proxy nginxiga ja Delphi korrektselt pidamine tähendab „Forwarded õigesti käsitleda“

Reverse-proxy nginxiga on Delphi-REST-Server jaoks hea standardkomponent – kuid alles Forwarded ja X-Forwarded-* korrektne käsitlus teeb selle ülesseadistuse töös stabiilseks. Tuum on lihtne: päiseid aktsepteerida ainult usaldusväärsetelt proxy-delt, tuletada järjekindlalt Client-IP/Scheme/Host ja rakendada seda alusprintsiipi suunamistes, logimises ja turvakontrollides. Ülaltoodud näidisega on teil selleks puhas, legacy-sobiv alus, mida saab integreerida WebBrokeri, Horse’i või enda HTTP-serveritesse.

Kui soovite olemasolevat Delphi-backendit nginxi taha konsolideerida või suunata moderniseerimist Delphi REST-API ja REST-Server suunas koos selge operatsiooniliiniga, on proxy-ahela ja päiste analüüsi tehniline review sageli kiireim tõhus käik. Võtke ühendust Net-Base lühikese tehnilise hinnangu saamiseks.

Asjatundlikus kontekstis mängivad ka Nginx Reverse Proxy ja Forwarded-päised olulist rolli, kui integratsioonid, andmevood ja edasiarendus peavad puhtalt koos töötama.

Arutage projekti või moderniseerimisettevõtmist koos Net-Base.

Järgmine samm

Kui teema muutub reaalseks projektiks, tuleks arhitektuuri, olemasolevat süsteemi ja käitust varakult ühiselt vaadelda.

Me ei toeta ainult üksikute küsimuste lahendamist, vaid ka siis, kui lähtekoodilõikudest, pärandsüsteemidest või portaalikontseptsioonidest peab saama usaldusväärne ettevõtteprojekt.

  • Olemasolev olukord, sihtpilt ja tehnilised riskid hinnatakse üheskoos.
  • REST, andmete juurdepääs, portaalid ja juurutamine ei lükata hilisemaks.
  • Te näete varakult, milline tee on majanduslikult ja operatiivselt jätkusuutlik.

Jaga postitust

Jaga seda postitust otse

LinkedIn, X, XING, Facebook, WhatsApp ja e-post on kohe saadaval. Instagrami jaoks valmistame kohe lingi ja lühiteksti ette.

e-post

Instagram avatakse uues vahekaardis. Link ja lühitekst kopeeritakse eelnevalt lõikepuhvrisse.