73

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?

Kirk Woll
  • 833
  • 1
  • 7
  • 7

4 Answers4

119

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:

set_real_ip_from 127.0.0.1;
set_real_ip_from 192.168.2.1;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

And an X-Forwarded-For header resulting in:

X-Forwarded-For: 123.123.123.123, 192.168.2.1, 127.0.0.1

nginx will now pick out 123.123.123.123 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 123.123.123.123. Let's also say the client is up to no good, and they're trying to spoof their IP address to be 11.11.11.11. They send a request to the server with this header already in place:

X-Forwarded-For: 11.11.11.11

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:

X-Forwarded-For: 11.11.11.11, 123.123.123.123, 192.168.2.1, 127.0.0.1

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 123.123.123.123 as the IP address, despite that spoofed IP actually being the left-most.

Nick M
  • 1,306
  • 1
  • 9
  • 3
  • 1
    By default real_ip_header seem to be X-Real-IP according to http://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 '14 at 19:17
  • 1
    @gansbrest No, because set_real_ip_from limits the trusted hosts. – El Yobo Oct 21 '15 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 '20 at 10:28
  • I know I'm a decade late but this still isn't working for me. Everything shows up as `10.1.0.1`, the address of my firewall/gateway, if it's an internal source. External sources show up properly. – THE JOATMON Jul 10 '21 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 06 '22 at 16:07
  • When using `real_ip_header X-Forwarded-For` it is essential to use exactly that capital letters, because if you use lower case `real_ip_header x-forwarded-for` nginx will NOT apply recursive logic and will just use first value. – petr Sep 09 '22 at 15:54
9

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 == ',') {
    p++;
    len -= p - ip;
    ip = p;
    break;
  }
}

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?

Shane Madden
  • 112,982
  • 12
  • 174
  • 248
  • 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 '11 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. – Shane Madden Sep 23 '11 at 20:54
  • [Even the W3C gets this wrong](http://www.w3.org/TR/ct-guidelines/#sec-additional-headers): 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 02 '15 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. – blubberdiblub Jul 04 '15 at 18:40
5

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.

user558061
  • 116
  • 4
  • 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 '11 at 21:09
  • 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 '11 at 18:51
  • 3
    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 '11 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. – Yugal Jindle Jan 26 '12 at 17:02
0

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.

adrianTNT
  • 1,007
  • 5
  • 21
  • 41