An application behind F5 BIG-IP works, but every access-log entry shows the load balancer instead of the visitor. Before disabling SNAT, separate two requirements: preserving the packet source address and conveying a client address in an HTTP header. BIG-IP SNAT changes the source address of a connection; an HTTP profile can insert client-address information into a request without undoing that translation.[1][2]
This troubleshooting guide provides a reusable decision table, a narrowly scoped NGINX configuration example, and an acceptance matrix for detecting spoofed or incorrectly trusted X-Forwarded-For headers. The examples are hypothetical and unexecuted on BIG-IP or NGINX; the workflow is an original operational recommendation, not a vendor-certified deployment recipe.
Quick answer: fix the identity path, not just the log format
For an HTTP application behind SNAT, investigate header insertion and the backend's trusted-proxy configuration before changing routing. F5 documents HTTP header insertion specifically as a way to preserve client-address information across SNAT.[1] NGINX's real-IP module can replace its effective client address using a configured header, but the sending address must match its configured trusted sources.[3]
Do not treat a header containing an address as proof that the address is trustworthy. Our recommended acceptance criterion is stronger: a client-supplied forged address must not become the effective client identity, and the application must not bypass the approved parsing path by reading the raw header independently.
Three addresses to record for the same request
Use a test request identifier to correlate the following observations. Keep test logs private and avoid recording cookies, authorization headers, or production payloads.
- Client-side peer: the address BIG-IP actually sees connecting to the virtual server. If another proxy is in front, document that hop rather than assuming this is the visitor.
- Backend socket peer: the address the server sees on its incoming connection. With SNAT, this is a translation address, not the original client address.[2]
- Effective application client: the address selected by the backend's trusted-proxy logic. In NGINX, the real-IP module changes the effective address, while
$realip_remote_addrpreserves the original connection address.[3]
A fourth useful observation is the raw X-Forwarded-For (XFF) header received by the backend. Treat it as diagnostic input, not an already-validated identity. Collect it temporarily with appropriate log escaping and retention controls.
Decision table: which requirement are you solving?
| Requirement or symptom | First investigation | Avoid this shortcut |
|---|---|---|
| HTTP logs show a SNAT address | Inspect the effective HTTP profile, backend-received XFF, and backend trust list | Removing SNAT just to change a log field |
| Backend receives XFF but logs still show BIG-IP | Confirm the real-IP module, header name, trust source and actual log variable | Assuming header insertion automatically updates every application |
| A CDN address appears instead of the visitor | Map every trusted proxy and inspect the complete header chain | Trusting all addresses to make the example work |
| Forged XFF changes the effective address | Stop rollout and review edge normalization and backend parsing | Treating a successful normal request as sufficient validation |
| Application requires the client address at the IP layer | Review routing and the whole source-preservation design | Expecting an HTTP header to change packet headers |
| TLS passes through without HTTP visibility | Identify where HTTP is actually decrypted and processed | Adding an HTTP-header solution at a hop that cannot inspect HTTP |
| Only some requests or failovers lose identity | Compare virtual-server/profile paths and actual backend peer addresses | Widening the trust list to an entire internal network |
The table is a suggested diagnosis order. F5's SNAT guide explains that SNAT is used to keep return traffic through BIG-IP when the server's normal route would not do so; removing it is therefore a routing change, not merely a logging fix.[2]
BIG-IP checks: insertion and acceptance are different settings
F5 documents X-Forwarded-For header insertion separately from X-Forwarded-For header acceptance. Insertion places client information into requests sent downstream; acceptance controls trusting client-address information and statistics based on received XFF headers.[1]
For the direct-client case, do not enable acceptance merely because a backend needs an inserted header. That would conflate the upstream trust decision with downstream identity transport. Record these independently in the change worksheet:
| Change worksheet field | What to record |
|---|---|
| Traffic path | Direct client, approved upstream proxy, or mixed entry paths |
| HTTP processing point | Where TLS terminates and HTTP becomes visible |
| Effective HTTP profile | Assigned profile, parent inheritance, insertion and acceptance settings |
| Other header writers | Relevant policies, iRules, upstream proxies and downstream gateways |
| Backend connection source | Actual translation addresses observed during normal operation and failover |
| Trusted-proxy owner | Who maintains the backend trust list and upstream source restrictions |
| Effective identity consumer | Access logs, rate limits, application middleware, audit pipeline |
| Rollback | Previous profile assignment and previous backend configuration |
F5 profiles inherit from parent profiles and must be assigned to a virtual server to govern its traffic.[1] For a controlled change, use an application-specific profile and inventory other consumers before modifying shared objects. Inspect the backend request rather than guessing how several header-manipulating features interact.
Direct client versus approved upstream proxy
For a direct-client deployment, the intended contract should be explicit: the address derived by the application is the client-side peer observed at the trusted edge, regardless of any XFF the client submitted. Decide whether the edge normalizes to a single value or maintains a chain that the backend parses correctly; verify the exact behavior on your software version.
For a deployment behind an approved CDN or reverse proxy, overwriting everything with BIG-IP's immediate peer can lose the visitor address. Conversely, accepting every incoming header can make the visitor address client-controlled. Our recommendation is to restrict the upstream connection path, document what the upstream proxy guarantees about its header, and allow only that documented chain. Test direct-to-origin attempts separately.
This article deliberately does not supply a universal header-rewriting iRule: a rule appropriate for direct clients can destroy useful information in a multi-proxy path. Resolve the trust contract first.
NGINX backend example: narrow trust, explicit header
NGINX documents set_real_ip_from as the addresses trusted to send correct replacement addresses, real_ip_header as the header to read, and recursive mode as selecting the last non-trusted address when the original peer is trusted.[3] With recursion disabled, it selects the last header address instead; enabling recursion is not a substitute for a correct trust list.[3]
The following is an unexecuted configuration example, placed inside the relevant existing server block. The documentation-only address represents a dedicated BIG-IP server-side translation address; it is not a real environment address. Replace it with the observed, approved source and add only other genuinely required proxy sources.
# Example only: merge into the intended existing server block.
set_real_ip_from 192.0.2.10;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
Do not substitute 0.0.0.0/0, ::/0, or an entire enterprise network for convenience. Treat every entry as authority to influence effective client identity. Review shared SNAT addresses carefully: trusting an address assumes that traffic arriving from that address follows the approved header-processing path.
The real-IP module is not built by default in upstream NGINX and requires the relevant build option; verify your installed package rather than assuming availability.[3] These are unexecuted inspection and syntax-check commands, not a reload procedure:
nginx -V
nginx -t
A successful syntax check does not validate the proxy trust model. Before any separately approved reload, preserve the prior configuration and agree on an application-level rollback test. Compare the effective client address, original socket peer and raw header for the same request. NGINX preserves the original address in $realip_remote_addr, which is useful for that comparison.[3]
If NGINX proxies onward to another application, establish a second explicit contract for that hop. Verify what the application actually consumes; a correct NGINX access log alone is not evidence that downstream authorization or rate limiting uses the same identity.
Controlled test workflow
Run these checks only on an authorized test virtual server or during an approved maintenance window. Use a harmless endpoint and synthetic requests; do not probe other people's services.
- Capture the baseline. Record the current virtual-server/profile associations and backend configuration. Send a normal request and correlate its identifier across the edge and application logs.
- Verify header transport. Confirm whether XFF reaches the backend and which component wrote each portion. If it is missing, investigate the HTTP processing path before changing the backend trust list.
- Apply one scoped change. Avoid changing routing, SNAT, header logic and backend parsing simultaneously. Keep the previous profile/configuration ready for rollback.
- Check effective identity. Verify both normal requests and requests containing a deliberately forged address.
- Check alternate paths. Repeat through each approved proxy path, backend member and relevant address family. Include HA transition testing only within its own approved scope.
- Remove temporary diagnostics. Keep the acceptance evidence but remove temporary debug endpoints and excessive header logging.
The following client requests are unexecuted templates. Replace the reserved example hostname with your authorized test endpoint. The forged documentation address is a test input, not a predicted output.
curl --fail --silent --show-error \
'https://app.example.com/health?probe=xff-baseline'
curl --fail --silent --show-error \
-H 'X-Forwarded-For: 198.51.100.77' \
'https://app.example.com/health?probe=xff-forged'
Do not use the HTTP status alone as the verdict. A successful response can coexist with an incorrect or spoofable effective identity. Inspect the server-side evidence; the header does not need to be echoed back to the client.
Acceptance matrix: copy this into the change ticket
These are proposed pass criteria, not claimed test results.
| Test | Required evidence | Pass criterion |
|---|---|---|
| Normal direct-client request | Edge peer, backend peer, effective identity | Effective identity matches the expected client-side peer, not the SNAT address |
| Client submits forged XFF | Raw received header and effective identity | Forged value is not accepted as effective identity |
| Client submits a multi-value or duplicate header | Edge/backend observations and parser result | Approved normalization/parsing policy holds, or request is rejected safely |
| Approved upstream proxy | Upstream evidence and documented chain | Correct visitor address is recovered according to the agreed trust contract |
| Direct-to-origin attempt | Network enforcement and backend logs | Blocked where required; an untrusted sender cannot supply effective identity |
| Multiple backend members | Correlated evidence from every member | Identical identity policy on each member |
| Approved HA transition | Observed backend peer before and after | Correct identity remains available without broadening trust unnecessarily |
| Application policy use | Audit/rate-limit/middleware evidence | Application uses the validated identity, not a separate raw-header parser |
| Rollback | Prior profile/configuration and normal request | Prior known-good service behavior is restored |
If the spoofed-header test fails, stop. Do not compensate by hiding the raw-header field from the logs. Correct the boundary that admitted or misinterpreted untrusted identity information, then repeat the entire relevant test set.
When preserving the packet source really is necessary
Some requirements concern the server-side connection source, not HTTP metadata. In that case, write down why an application-layer identity will not satisfy the requirement and review routing as a separate design task. F5 documents SNAT as source-address translation and explains its role in ensuring return traffic traverses BIG-IP.[2]
For TLS passthrough or non-HTTP services, this XFF workflow is not a generic solution. Do not assume that an HTTP-profile change can add identity inside opaque application traffic. Evaluate protocol-specific support and return-path design with the service owner rather than disabling SNAT experimentally on production traffic.
For adjacent troubleshooting, see BIG-IP server-side TLS handshake diagnosis and cookie persistence with OneConnect. The Data Center hub and Start Here index organize the broader design guides.
Practical takeaways
Separate packet source, received header and effective application identity. Use insertion to transport HTTP client information and a deliberately narrow trust policy to interpret it. Do not confuse BIG-IP's insertion and acceptance controls.[1] Treat forged-header rejection, alternate-path consistency and application-level identity use as mandatory acceptance checks—not optional cleanup after the logs look correct.
Source scope: The F5 references below are the retrieved Services Profiles chapter under the 16.1 documentation path and the NATs/SNATs chapter under the 14.1 documentation path. They support the cited feature semantics, not a claim that either release is the latest or recommended for deployment. Validate supported settings and behavior against your installed release. The NGINX source is its public real-IP module reference.
Post a Comment