The wikipedia description of the HTTP header X-Forwarded-For is:

X-Forwarded-For: client1, proxy1, proxy2, ...

The nginx documentation for the directive real_ip_header reads, in part:

This directive sets the name of the header used for transferring the replacement IP address.
In case of X-Forwarded-For, this module uses the last ip in the X-Forwarded-For header for replacement. [Emphasis mine]

These two descriptions seem at odds with one another. In our scenario, the X-Forwarded-For header is exactly as described -- the client's "real" IP address is the left-most entry. Likewise, the behavior of nginx is to use the right-most value -- which, obviously, is just one of our proxy servers.

My understanding of X-Real-IP is that it is supposed to be used to determine the actual client IP address -- not the proxy. Am I missing something, or is this a bug in nginx?

And, beyond that, does anyone have any suggestions for how to make the X-Real-IP header display the left-most value, as indicated by the definition of X-Forwarded-For?

4 Answers 4


I believe the key to solving X-Forwarded-For woes when multiple IPs are chained is the recently introduced configuration option, real_ip_recursive (added in nginx 1.2.1 and 1.3.0). From the nginx realip docs:

If recursive search is enabled, an original client address that matches one of the trusted addresses is replaced by the last non-trusted address sent in the request header field.

nginx was grabbing the last IP address in the chain by default because that was the only one that was assumed to be trusted. But with the new real_ip_recursive enabled and with multiple set_real_ip_from options, you can define multiple trusted proxies and it will fetch the last non-trusted IP.

For example, with this config:

real_ip_header X-Forwarded-For;
real_ip_recursive on;

And an X-Forwarded-For header resulting in:


nginx will now pick out as the client's IP address.

As for why nginx doesn't just pick the left-most IP address and requires you to explicitly define trusted proxies, it's to prevent easy IP spoofing.

Let's say a client's real IP address is Let's also say the client is up to no good, and they're trying to spoof their IP address to be They send a request to the server with this header already in place:


Since reverse proxies simply add IPs to this X-Forwarded-For chain, let's say it ends up looking like this when nginx gets to it:


If you simply grabbed the left-most address, that would allow the client to easily spoof their IP address. But with the above example nginx config, nginx will only trust the last two addresses as proxies. This means nginx will correctly pick as the IP address, despite that spoofed IP actually being the left-most.

  • 1
    By default real_ip_header seem to be X-Real-IP according to nginx.org/en/docs/http/ngx_http_realip_module.html Does it mean malicious user can just send request with random X-Real-IP and that will be used as $remote_addr in nginx (and also possibly passed to the application)?
    – gansbrest
    Apr 22, 2014 at 19:17
  • 1
    @gansbrest No, because set_real_ip_from limits the trusted hosts.
    – El Yobo
    Oct 21, 2015 at 22:23
  • You also need to include the $remote_addr (IP of the proxy before nginx which under normal circumstances is not included in X-Forwarded-For header) in the set_real_ip_from directive along with the other proxies in X-Forwarded-For header.
    – Mert Z.
    Nov 18, 2020 at 10:28
  • I know I'm a decade late but this still isn't working for me. Everything shows up as, the address of my firewall/gateway, if it's an internal source. External sources show up properly. Jul 10, 2021 at 23:09
  • 1
    Surprised this issue dates back to 2011... Personally I think real_ip_recursive should be ON by default - the whole purpose of the set_real_ip_from feature is to know the origin ip, not ip to a proxied server.
    – Gary Green
    Jul 6, 2022 at 16:07

The parsing of the X-Forwarded-For header is indeed flawed in the nginx real_ip module.

len = r->headers_in.x_forwarded_for->value.len;
ip = r->headers_in.x_forwarded_for->value.data;

for (p = ip + len - 1; p > ip; p--) {
  if (*p == ' ' || *p == ',') {
    len -= p - ip;
    ip = p;

It starts on the far right of the header string, and as soon as it sees a space or comma, it stops looking and sticks the part to the right of the space or comma in the IP variable. So, it's treating the most recent proxy address as the original client address.

It's not playing nice according to the spec; this is the danger of not having it spelled out in painfully obvious terms in an RFC.

Aside: It's hard to even find a good primary source on the format, which was originally defined by Squid - a dig through their documentation confirms the ordering; leftmost is original client, rightmost is the most recent append. I'm sorely tempted to add a [citation needed] to that wikipedia page. One anonymous edit seems to be the internet's authority on the subject.

If possible, can you have your intermediate proxies stop adding themselves to the end of the header, just leaving it with the real client address only?

  • Thanks for the reply, @Shane. In fact, when reaching nginx, an X-Forwarded-For already exists. (it is the correct client IP address) nginx itself then proceeds to append the IP address of our load balancer (the previous hop) to the X-Forwarded-For header. (presumably appending what it sees as the "remote address") If it simply did not do that, I would be able to just use the X-Forwarded-For header as before. (we've recently been migrating to nginx)
    – Kirk Woll
    Sep 23, 2011 at 20:32
  • @Kirk So, when nginx gets the header, it's just the original client's address? But when it's processing it, it's added on the connecting proxy server's header? That doesn't add up - the only time it should touch that header is when it's sending the connection to another proxy via a proxy_pass - and even then, only with proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; in place. Sep 23, 2011 at 20:54
  • Even the W3C gets this wrong: their documentation states "proxies should add the IP address of the initiator of the request to the end of a comma separated list in an X-Forwarded-For HTTP header field", it should state beginning.
    – Ian Kemp
    Apr 2, 2015 at 14:30
  • 3
    @IanKemp, no, end is correct. To the server side of a proxy, the initiator of the request (i. e. TCP request) is the previous proxy (if there is one). That previous proxy possibly already sends an X-Forwarded-For header with possibly the original client address at the left and possibly any preceding proxies appended to that. So the currently serving proxy would add the previous proxy (= initiator) to the end of that list and serve the thus augmented X-Forwarded-For header to the next upstream hop. Granted, they could have chosen a more obvious wording. Jul 4, 2015 at 18:40

X-Real-IP is the IP address of the actual client the server is talking to (the "real" client of the server), which, in the case of a proxied connection, is the proxy server. That's why X-Real-IP will contain the last IP in the X-Forwarded-For header.

  • 2
    OK, but, for me, that is simply never useful information. I want to get the original IP address of the client -- that is crucial, and according to everything I've read, the purpose of these headers. Why would I want to know the IP address of our proxy servers?
    – Kirk Woll
    Sep 22, 2011 at 21:09
  • 1
    If it's not useful for you then it's not for you. Nobody's forcing you to use X-Real-IP. If you need the IP of the user in your application, then have your application parse X-Forwarded-For (which isn't always reliable because there are some proxies (internet security appliance/firewalls) that don't set X-Forwarded-For). In the context of nginx, X-Forwarded-For is not important because it's not talking to those clients anyway, aside from the last entry (the X-Real-IP) which is nginx's client. If you don't need it then don't set it, unset it, or just ignore it :/
    – user558061
    Sep 23, 2011 at 18:51
  • 4
    No, what I mean is, why would X-Real-IP returning my own proxy server's IP address ever be useful?
    – Kirk Woll
    Sep 23, 2011 at 19:05
  • Great.. answer man. I was looking for this precise information. I need to talk to a ncat server on my proxy server, so I need this on the fly. Jan 26, 2012 at 17:02

More of a warning than an answer ...

I tried adding some Nginx cache servers in multiple map locations without realizing that my main server (source of data) is already behind an Nginx cache server that runs locally, sometimes local server is configured to run Apache and Nginx is put in front of it to act as cache.

Adding a second separate Nginx cache server would result in two cache servers and so your HTTP_X_REAL_IP or HTTP_X_FORWARDED_FOR appears wrong, showing ip of one if the servers instead of visitor IP.

In my case I fixed that by setting the new cache server take data directly from the Apache port (port 7080 in my case) bypassing local Nginx cache at source/main server.

Another solution would be to remove the HTTP_X_REAL_IP at the local cache server.

I was running Plesk panel btw.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .