Understanding the X-Forwarded-For HTTP Header – Security Risks and Best Practices

April 16, 2025

Between working on various projects, I took a moment to explore the infamous X-Forwarded-For HTTP header, often misunderstood and viewed negatively—particularly from a security perspective. But is this reputation deserved? In this article, I would like to present the header’s purpose, potential security vulnerabilities, secure implementation practices and real-world use cases. Whether you’re a penetration tester, CTF enthusiast or a developer, this topic might be interesting for you.

What is X-Forwarded-For HTTP Header?

According to Mozilla’s Developer documentation:

The HTTP X-Forwarded-For (XFF) request header is a de-facto standard header for identifying the originating IP address of a client connecting to a web server through a proxy server.

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For

This header typically contains an ordered list of IP addresses involved in forwarding HTTP requests:

X-Forwarded-For: <client>, <proxy>
X-Forwarded-For: <client>, <proxy>, …, <proxyN>

Examples:

X-Forwarded-For: 203.0.113.195
X-Forwarded-For: 203.0.113.195, 2001:db8:85a3:8d3:1319:8a2e:370:7348

In practice, the X-Forwarded-For header helps applications identify the original client IP when requests pass through a proxy or a load balancer. As HTTP headers can be arbitrarily set by clients, this header should only be trusted when set by a known, trusted intermediary component.

Furthermore, a trusted component should replace any existing X-Forwarded-For header from the request received by the client. This avoids trusting user-supplied spoofed values and ensures the application only receives headers set by trusted infrastructure.

The Role of X-Forwarded-For

The following diagram presents a scenario where the application’s user is connecting to the application via a reverse proxy with a public IP address which is a typical scenario in a production environment. We assume that the reverse proxy is a part of an application’s infrastructure and is trusted and controlled by the developer.

In this scenario, the reverse proxy terminates encrypted traffic and forwards the request internally to the application. As the application’s user does not connect to the application directly, the application does not have the information about the IP address of the user. Thus, the responsibility for identifying the client’s IP address lies with the reverse proxy. The most popular method for sending the IP address from the reverse proxy to the application is to pass it via the X-Forwarded-For header. Each subsequent proxy may append its IP address, building a traceable path.

As we already introduced the concept of a reverse proxy which is often placed between the application and its users, let’s understand what is the purpose of this component. The following list presents the most common ones:

  • terminating TLS (HTTPS) connections.
  • efficiently serving static content.
  • routing traffic to the correct backend service based on the HTTP Host header
  • modifying or setting HTTP request and response headers, including X-Forwarded-For

Web applications typically rely on client IP addresses for several use cases, including:

  • Implementing IP-based security controls (e.g., blocking malicious IPs).
  • Event auditing (logging IP with user actions).
  • Rate limiting at the application layer.

Accessing Client IP Address in Application Frameworks

It should be noted that some web applications frameworks may already provide ways for obtaining client’s IP address using built-in methods such as “getRealIP” or attributes of requests object. For example, in FastAPI it’s possible to obtain the client’s IP address using the Request.client.host as presented below:

@app.get("/my-ip")
async def get_ip(request: Request):
    client_host = request.client.host
    return {"client_ip": client_host}

However, such methods usually rely on TCP connections, meaning if a reverse proxy or load balancer is in use, they often return the IP of the intermediary component, not the original client. That’s why headers such as X-Forwarded-For were introduced.

Secure Implementation Example with Cloudflare

As services such as Cloudflare are widely used for security and performance purposes, I decided to explore the implementation challenges of obtaining the client’s IP address in applications using a more advanced example with Cloudflare.

Cloudflare acts as a reverse proxy that protects backend applications from DDoS attacks, malicious bots and other threats, while also accelerating traffic through caching and network optimizations. As such services are very popular, I decided to describe the implementation challenges of obtaining client’s IP address by applications using more advanced example with Cloudflare.

Cloudflare’s reverse proxy sits between the client and the origin server. It uses DNS-based routing — when a domain’s A or CNAME record points to Cloudflare, user traffic is transparently routed through Cloudflare’s infrastructure. This setup allows Cloudflare to inspect, filter and forward requests to your server while optionally adding headers like:

  • X-Forwarded-For – a standard header listing all client and proxy IPs.
  • CF-Connecting-IP – A Cloudflare-specific header that contains only the original client IP address.

Choosing Between X-Forwarded-For and CF-Connecting-IP

While both headers can be used to obtain the user’s IP address, relying on CF-Connecting-IP often simplifies implementation. Unlike X-Forwarded-For, which may contain multiple comma-separated IPs representing the full proxy chain, CF-Connecting-IP provides a single, unambiguous value — assuming the traffic has passed through Cloudflare.

Caveat – Bypassing Cloudflare

This implementation, however, has a critical security caveat.

HTTP headers like CF-Connecting-IP and X-Forwarded-For should only be trusted if you are 100% sure the traffic has come through Cloudflare. If an attacker knows the origin IP address of your application — that is, the IP of the server behind Cloudflare — they can bypass Cloudflare and connect directly to your service. In doing so, they can spoof headers and send forged values for CF-Connecting-IP or X-Forwarded-For.

This can lead to IP spoofing vulnerabilities, especially if your application uses IP-based logic (e.g., rate limiting, access control, audit logging) and trusts these headers blindly.

The following HTTP request could be sent to the publicly exposed application’s reverse proxy instead of Cloudflare reverse proxy to spoof IP address and bypass security mechanisms relying on the IP.

GET /test HTTP/1.1
Host: application-domain.com
X-Forwarded-For: 103.21.244.0, 131.0.72.1
CF-Connecting-IP: 103.21.244.0

To ensure Cloudflare cannot be bypassed, your origin server or reverse proxy must be configured to only accept requests from Cloudflare IP ranges. This way, even if an attacker discovers the server’s IP, their direct connection attempts will be dropped.

Cloudflare publishes an up-to-date list of their IP ranges, which should be used to configure allowed IP addresses to communicate with application’s reverse proxy. Alternatively, mTLS can be utilised to ensure that only Cloudflare service can connect to the application’s reverse proxy.

Potential Misconfigurations

The following list presents potential misconfigurations that can lead to IP address spoofing when X-Forwarded-For or similar header is in use.

  • No Trusted Proxy Present (Application Publicly Exposed w/o Proxy)
    The application relies on the X-Forwarded-For header to determine the client’s IP address but there is no trusted reverse proxy in the infrastructure to set this header. In such cases, the client can arbitrarily inject a forged X-Forwarded-For header, leading to IP spoofing and false attribution in logging, rate-limiting or access control mechanisms.
  • Trusted Proxy Does Not Set the Header
    Even if a trusted reverse proxy is used (e.g., internally managed Nginx) the application may incorrectly assume that the X-Forwarded-For header is always present and valid. If the proxy isn’t explicitly configured to set this header, the application might end up trusting headers injected by the client.
  • Proxy Can Be Bypassed
    The application assumes traffic always flows through a trusted proxy and trusts the headers accordingly. However, the reverse proxy can be bypassed as presented in the previous section. An attacker could then send requests directly to the application, including forged X-Forwarded-For or CF-Connecting-IP headers, effectively spoofing their IP address.

Summary

Relying on the X-Forwarded-For header is a useful mechanism to identify IP address of a connected user or a proxy used by this user. The security issues arise primarily from misconfigurations or misplaced trust. Following secure implementation practices and using trusted infrastructure for setting those headers will ensure the security and reliability of identifying clients IP addresses.

Summarising, relying on X-Forwarded-For header itself is not a vulnerability—the issue lies entirely in how it’s configured and managed.


Interesting Article?

Join DevSec Selection!

DevSec Selection is a bi-weekly Newsletter with the latest outstanding articles related with DevSecOps and application security.


Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments