Difference between X-Forwarded-For and X-Real-IP headers

What is the difference between these headers?

Did you check the $proxy_add_x_forwarded_for variable documentation?

the X-Forwarded-For client request header field with the $remote_addr variable appended to it, separated by a comma. If the X-Forwarded-For field is not present in the client request header, the $proxy_add_x_forwarded_for variable is equal to the $remote_addr variable.

If the incoming request already contains the X-Forwarded-For header, lets say

X-Forwarded-For: 203.0.113.195, 150.172.238.178

and your request is coming from the IP 198.51.100.17, the new X-Forwarded-For header value (to be passed to the upstream) will be the

X-Forwarded-For: 203.0.113.195, 150.172.238.178, 198.51.100.17

If the incoming request doesn’t contain the X-Forwarded-For header, this header will be passed to the upstream as

X-Forwarded-For: 198.51.100.17

On the other hand, the X-Real-IP header being set the way you show in your question will be always be equal to the $remote_addr nginx internal variable, in this case it will be

X-Real-IP: 198.51.100.17

(unless the ngx_http_realip_module will get involved to change that variable value to something other than the actual remote peer address; read the module documentation to find out all the details; this SO questions has some useful examples/additional details too.)

Whether I need to use both at the same time?

The very first your question should be “do I need to add any those headers to the request going to my backend at all?” That really depends on your backend app. Does it count on any of those headers? Does those headers values actually make any difference on the app behavior? How your backend app treats those headers values? As you can see, the request origin assumed to be the very first address from the X-Forwarded-For addresses list. On the other hand, that header can be easily spoofed, so some server setups may allow to use that header for the trusted sources only, removing it otherwise. If you set the X-Real-IP header by your server setup, it will always contain the actual remote peer address; if you don’t, and you’ve got a spoofed request with the X-Real-IP header already present in it, it will be passed to your backend as is, which may be really bad if your app will prefer to rely on that header rather than X-Forwarded-For one. Different backend apps may behave differently; you can check this GitHub issue discussion to get the idea.

Summarizing all this up.

If you definitely know what headers your backend app can actually process and how it will be done, you should set required headers according to the way they will be processed and skip non-required to minimize the proxied payload. If you don’t, and you don’t know if your app can be spoofed with the incorrect X-Forwarded-For header, and you don’t have a trusted proxy server(s) in front of your nginx instance, the most safe way will be to set both according to an actual remote peer address:

proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP       $remote_addr;

If you know for sure your backend app cannot be spoofed with the wrong X-Forwarded-For HTTP header and you want to provide it with all the information you’ve got in the original request, use the example you’ve shown in your question:

proxy_set_header X-Forwarded-For $proxy_x_forwarded_for;
proxy_set_header X-Real-IP       $remote_addr;

Some additional technical information.

Actually, those X-Forwarded-... HTTP headers are some kind of non-standard headers. According to MDN, it was assumed that the standard headers for transmitting such information would be Via, described in the RFC7230, and Forwarded, described in the RFC7239. However the X-Forwarded-For, X-Forwarded-Host and X-Forwarded-Proto became an alternative and de-facto standard version instead of those. Instead of using X-Forwarded-Host, which may or may not be interpreted by your backend app, a more reliable approach is to explicitly set the Host HTTP header for the proxied request using either

proxy_set_header Host $host;

or

proxy_set_header Host $http_host;

or even

proxy_set_header Host $server_name;

(you can check the difference between $host, $http_host and $server_name nginx internal variables here.) On the other hand the X-Forwarded-Proto used quite often to tell the backend app if the original request was made over the encrypted HTTPS protocol or not. Sometimes you can even see the X-Forwarded-Proxy header used in the configuration; as for me, this one looks senseless since the backend app should not behave differently depending of the reverse proxy software you actually use; however I can believe there can be web apps that really can deal with that one in a some useful way. MDN does not mention the X-Real-IP header at all; however that are definitely quite a lot of web apps that should be provided with that one.

One more technical detail. Like some other reverse proxy servers, nginx will “fold” multiple X-Forwarded-For headers into a single one, so the

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

and the

proxy_set_header X-Forwarded-For $http_x_forwarded_for;
proxy_set_header X-Forwarded-For $remote_addr;

configuration fragments will behave identically, passing the single X-Forwarded-For header to your backend app, equal no matter of what configuration will be used.

Leave a Comment