Net-Base Tímarit

23.05.2026

Reverse proxy með nginx og Delphi: hreint Forwarded-meðhöndlun, raunveruleg Client-IP og traustir URL-grunnar

Þegar Delphi-REST-þjónar keyra aftan við nginx brotna oft upplýsingar um Client-IP, HTTPS-uppgötvun og algildar vefslóðir. Þessi kóðabútur sýnir áreiðanlega meðhöndlun Forwarded-/X-Forwarded-hausanna (þ.m.t. Trust-Proxy-listu), dæmigerðar nginx-stillingar og leiðbeiningar um villuleit fyrir rekstur.

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 „fyrir framan“ 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.

Af hverju Reverse Proxy-uppsetningar rugla Delphi-þjóna

nginx talar sem Reverse Proxy við Delphi-þjónustuna yfirleitt ódulkóðað (HTTP) á innra neti eða á localhost, á meðan viðskiptavinurinn kemur utan með HTTPS. Án aukalegra headera veit Delphi ekkert um:

  • Upprunalegt schema (https vs. http) – mikilvægt fyrir Redirects og algildar URL-slóðir.
  • Upprunalegur Host (viðskiptavinssértæk lén, Port) – mikilvægt fyrir Multi-Tenant-uppsetningar, CORS og Callback-URLs.
  • Upprunaleg Client-IP – mikilvægt fyrir Audit, Rate-Limits, Geo-Checks og öryggisgreiningar.

nginx getur fært þessar upplýsingar í gegnum headera. Algengt er X-Forwarded-For, X-Forwarded-Proto og X-Forwarded-Host; staðlað er auk þess RFC-headerinn Forwarded. Mikilvægt: Þessir headerar eru úr sjónarhóli forritsins ekki sjálfkrafa traustsverðir, því viðskiptavinur getur sent þá sjálfur – þeir verða aðeins traustsverðir þegar þeir koma frá þekktum proxy.

nginx-Konfiguration: die minimal sinnvollen Proxy-Header

Traustur byrjunarpunktur (HTTP/1.1, Keep-Alive, Upgrade fyrir WebSockets) lítur svona út. Brotinu er meðvitað haldið hnitmiðuðu; þú bætir eftir umhverfi við HSTS, Rate-Limits og 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;
  }
}

Markmið: Forritið fær Host-ið, Client‑IP‑ið og URL‑sniðið áreiðanlega áfram. Skilyrði: $proxy_add_x_forwarded_for bætir núverandi Proxy‑IP við mögulega fyrirliggjandi keðju; það er gott fyrir margfaldar proxy‑uppsetningar, en gerir rétta úrvinnslu á Delphi‑síðu enn mikilvægari. Hætta: Ef þú setur ekki Host-hausinn í nginx, sér Delphi hugsanlega aðeins upstream‑host‑ið (127.0.0.1), sem brýtur endurvísanir og upprunaathuganir.

Delphi Upprunabútur: Traust úrvinnsla á Forwarded/X-Forwarded (með traust-proxy-lista)

Fylgjandi kóði er meðvitað haldinn óháður ramma: Hann vinnur gegn einföldu viðmóti (Header + RemoteIP) og má aðlaga í WebBroker, RAD Server eða Horse. Kjarnatriði:

  • Forgangsregla: RFC Forwarded (ef til staðar) á undan X-Forwarded-*.
  • Traust: Forwarded‑hausar eru aðeins greindir ef beinn peer (RemoteIP) er þekktur Proxy.
  • Greining: Taka tillit til IPv6, gæsamerkja, porta og keðja í X-Forwarded-For.
  • Úttak: grunn‑URL sem hægt er að nota fyrir fullar slóðir, endurvísanir eða OpenAPI.
Delphi
unit Net-Base.ProxyForwarding;

interface

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

type
  // Lágmarks adapter-viðmót: útfærið það fyrir WebBroker/Horse o.s.frv.
  IHeaderReader = interface
    ['{C2D2E5B9-2C2E-4D37-9D73-3CDB5A7E7EEA}']
    function GetHeaderValue(const AName: string): string;
    function GetRemoteIP: string; // beint TCP-mótstykki (oft nginx)
  end;

  TForwardedInfo = record
    ClientIP: string;
    Proto: string; // http/https
    Host: string;
    Port: Integer;
    function EffectiveScheme: string;
    function EffectiveHostPort: string;
    function BaseUrl: string; // t.d. 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 getur verið "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 í []: [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 (Athugið: við beran IPv6 án [] er þetta ekki áreiðanlegt)
  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
  // Dæmi: Forwarded: for=203.0.113.43;proto=https;host=api.example.com
  Parts: TArray;
  I: Integer;
  KV: TArray;
  K, V: string;
  FirstElement: string;
begin
  Result := False;
  ClientIP := '';
  Proto := '';
  Host := '';

  if ForwardedValue.Trim = '' then
    Exit;

  // Fjöldi þátta er aðskilinn með kommu; við tökum fyrsta hlutann (nær klientinum)
  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 getur verið IP eða "unknown"; IPv6 getur verið í []
      V := V.Trim;
      if SameText(V, 'unknown') then
        Continue;
      // for=1.2.3.4:5678 getur komið fyrir
      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.Create;
end;

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

class function TTrustedProxyList.NormalizeIp(const AIP: string): string;
begin
  // Fyrir raunverulega IPv6-normalíseringu þyrfti IP-greiningu; hér erum við meðvitað pragmatísk.
  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; // Varagildi: beint mótstykki

  // Einungis ef beint mótstykki er þekktur proxy lesum við Forwarded-header.
  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-* sem varagildi/viðbót
    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;

  // Ef allt bilar, taktu Host úr 'Host' hausnum (ef enginn proxy-haus er til staðar)
  if Result.Host = '' then
  begin
    Host := Req.GetHeaderValue('Host');
    if Host <> '' then
      SplitHostPort(Host, Result.Host, Result.Port);
  end;
end;

end.

Tilgangur: Kerfið fær úr hverri beiðni samræmda sýn á ClientIP, Proto og Host auk BaseUrl. Þessar upplýsingar má miðstýra fyrir skráningu, öryggisákvarðanir (t.d. IP-allowlist) og tenglasköpun.

Af hverju Trust-Proxy-listinn er nauðsynlegur: Án Trust-prófunar gæti árásarmaður náð beint á portið ykkar á Delphi (villa í stillingum, innra routing, VPN) og sent einfaldlega X-Forwarded-For: 127.0.0.1. Þá yrðu audit‑keðjur, rate‑limits eða „aðeins innra leyft“-endapunktar í hættu. Treystu aðeins Forwarded‑hausum ef bein mótaðili (RemoteIP) er proxy sem þið stjórnið (t.d. 127.0.0.1, Load-Balancer-IP, Kubernetes-Ingress).

Fallstrik: IPv6 án ferkantaðra sviga ([]) er ekki ótvírætt í Host:port-ritun. Í HTTP-Host-hausnum er IPv6 venjulega skráð í []; fylgið því. Fyrir flóknar IP-svið (CIDR) þarf að stækka Trust‑listann (t.d. með raunverulegri IP‑greiningu).

Integration in WebBroker/Horse/RAD Server: wo der Code „andockt“

Í WebBroker (TWebRequest) koma hausar venjulega í gegnum ContentFields eða GetFieldByName, Remote‑IP fer eftir server‑backend. Í Horse (eða öðrum HTTP‑ramma) er yfirleitt Req.Headers og Remote‑IP‑eiginleiki. Mikilvægt er meginreglan: RemoteIP verður að vera TCP‑mótaðili, ekki eitthvert hausgildi.

Praktískt reynslusnið: Búið til við þjónustuhýsingu TTrustedProxyList úr stillingum (INI/ENV), t.d. „127.0.0.1“ fyrir staðbundin nginx‑uppsetning eða IP hjá Load Balancer. Þá körið þið ResolveForwardedInfo fyrir hverja beiðni og skrifið reitina í uppbyggða skráningu (JSON‑logg, Syslog eða Windows Event Log).

Debugging im Betrieb: so finden Sie Fehler in Minuten statt Stunden

Ef beiðnir virka „skrýtnar“ þá stafar það sjaldan af Delphi‑HTTP sjálfu, heldur af samspili proxy‑hausar, redirect‑rökfræði og timeouts. Þrjár debugging‑athuganir sem gagnast í daglegum rekstri:

  1. Header‑Dump (gezielt): Skráið við 4xx/5xx aukalega Host, Forwarded, X-Forwarded-For, X-Forwarded-Proto, User-Agent og Request-URI. En aðeins við villur – annars verður loggið dýrt og óskipulegt.
  2. Base‑URL prüfen: Þegar redirectar eða callback‑URLir mistakast, skrifið ForwardedInfo.BaseUrl í loggið. Margar villur sjást strax („http://127.0.0.1“ í stað „https://api…“).
  3. Timeout‑Korrelation: 504 frá proxy er ekki það sama og Delphi‑timeout. nginx proxy_read_timeout og Delphi‑hliðar Idle/Read‑timeouts þurfa að samræmast.

Randfälle: WebSockets, Streaming und große Requests

WebSockets hinter nginx

Fyrir WebSockets þarf nginx að hafa Upgrade og Connection rétt sett. Ennfremur má bakendinn ekki loka „of snemma“. Á Delphi‑hlið er mikilvægt að WebSocket‑hlutinn ykkar (eða SSE/streaming‑endapunktur) geti tekist á við reverse proxies og að Heartbeats/Keep‑Alives séu hreint útfærð.

Große Uploads und 413-Fehler

Klassískur vandi: Delphi samþykkir upphleðslu en nginx blokkir hana fyrirfram með 413 Request Entity Too Large. Stjórnið þetta sérstaklega með client_max_body_size og stillið Delphi‑hliðar takmörk fyrir beiðnir. Fyrir ferlamiðuð hugbúnaðarlausn með skjala‑ eða mynda‑gögnum er þetta ekki undantekning heldur venjulegur rekstur.

HTTPS-Offloading und „Secure Cookies“

Ef Delphi-þjónustan ykkar stillir session-cookies, þarf þær yfirleitt að merkja sem Secure þegar um ytri HTTPS-samskipti er að ræða. Hvort forritið ykkar gerir það ræðst oft af því hvort það „viti“ að upphaflegu beiðnin var HTTPS. Hér kemur samstæð úrvinnsla á X-Forwarded-Proto/Forwarded að gagni.

Hvenær vinnan borgar sig – og hvar hún getur brugðist

Hugmyndin sem sýnd er réttlætir sig þegar Delphi-þjónustan er ekki lengur „nakin“ í LANinu heldur hluti af framleiðslujaðri: margar lénaskrár, SSO/SAML-viðmót, Public APIs, margleigufærni eða strangari kröfur um endurskoðun. Hún bilar þar sem menn treysta Forwarded-headerum blindandi eða lýsa ekki Proxy-topólógíunni (margar Ingress-stig, Cloud-LB plus nginx plus Sidecar). Þá verða Client-IP og scheme fljótt „eitthvað“.

Skýr mörk: Ef þið þurfið flókin traustreglur (CIDR, IPv6-net, dynamískar LB-IPs) ættuð þið að auka traustprófunna (raunverulegt IP-parsing, netmaskar) eða hanna innviði þannig að aðeins skilgreindur proxy geti náð Delphi-portinu (Firewall/Security Groups). Að lokum er það yfirleitt stöðugri rekstrarleg ákvörðun.

Fyrirgreining: Reverse Proxy með nginx og Delphi áreiðanlega reka þýðir „gera Forwarded rétt“

Reverse Proxy með nginx er fyrir Delphi-REST-Server góður staðlaður byggingarhluti – en einungis rétt meðhöndlun Forwarded og X-Forwarded-* gerir uppsetninguna í rekstri stöðuga. Kjarninn er einfaldur: samþykkja aðeins headera frá traustum Proxies, leiða Client-IP/Scheme/Host af á samræmdan hátt og láta þessa grunnreglu ná yfir redirects, skráningu og öryggisskoðanir. Með snippetrinu hér að ofan hafið þið hreint, legacy-viðeigandi grunnlag sem má samþætta í WebBroker, Horse eða eigin HTTP-þjóna.

Ef þið ætlið að sameina núverandi Delphi-backend aftan við nginx eða nútímavæða í átt að Delphi REST-API und REST-Server með skýrri rekstrarlínu, er tæknilegt yfirvarp á proxy-keðjunni og header-úrvinnslunni oft hraðasta leiðin til árangurs. Hafðu samband við Net-Base fyrir stutta tæknilega staðsetningu.

Í faglegu samhengi gegna einnig Nginx Reverse Proxy og Forwarded-header mikilvægri stöðu þegar samþættingar, gagnastreymi og áframhaldandi þróun þurfa að spila hreint saman.

Ræddu verkefni eða nútímavæðingarverkefni með Net-Base.

Deila færslu

Deila þessari færslu beint

LinkedIn, X, XING, Facebook, WhatsApp og tölvupóstur eru strax tiltækir. Fyrir Instagram undirbúum við hlekk og stuttan texta beint.

Tölvupóstur

Instagram opnast í nýjum flipa. Tengill og stuttur texti eru afritaðir í klippiborðið á undan.