17

In a situation where Apache is sitting behind a reverse proxy (such as Squid), the cgi environment variable REMOTE_ADDR gets the address of the proxy rather than the client.

However, the proxy will set a header called X-Forwarded-For to contain the original IP address of the client so that Apache can see it.

The question is, how do we get Apache to replace REMOTE_ADDR with the value in the X-Forwarded-For header so that all of the web applications will transparently see the correct address?

tylerl
  • 30,197
  • 13
  • 80
  • 113

9 Answers9

13

You can use mod_rpaf for that. http://stderr.net/apache/rpaf/

maciekb
  • 162
  • 2
  • 3
  • 2
    I'm +1ing this, but want to note that apparently rpaf doesn't take a network range. So if your provider is doing the balancing for you, you have to hope they never change their balancer IPs. Also, ubuntu 12.04 doesn't correctly load the conf, you have to comment out and the closing in the rpaf.conf or else it doesn't load that conf. – Dev Null Jul 14 '13 at 20:23
  • @DevNull: see the updated mod_rpaf, it supports CDIR ranges. https://github.com/gnif/mod_rpaf. This repository is the official one now as I have it over with permission from the original author. – Geoffrey Nov 26 '13 at 04:29
  • @Geoffrey, glad you took over. The balancer I used at my hosting company said the source would be over a /24 network and with the older rpaf I had to put in 253 entries, one for each possible IP. – Dev Null Nov 27 '13 at 07:49
  • 1
    Apache 2.4 has a built-in [mod_remoteip](http://httpd.apache.org/docs/current/mod/mod_remoteip.html) that does just this, making mod_rpaf obsolete. – rustyx Nov 20 '20 at 09:53
8

Note that the X-Forwarded-For header may contain a list of IP addresses if the request has traversed more than one proxy. In this case, you usually want the leftmost IP. You can extract this with a SetEnvIf:

SetEnvIf X-Forwarded-For "^(\d{1,3}+\.\d{1,3}+\.\d{1,3}+\.\d{1,3}+).*" XFFCLIENTIP=$1

Note the use of $1 to set the XFFCLIENTIP environment variable to hold the contents of the first group in the regex (in the parentheses).

Then you can use the value of the environment variable to set headers (or use it in Apache log formats so that the logs contain the actual client IP).

Ben Last
  • 801
  • 8
  • 6
  • 2
    I think your regular expression is incorrect: you want to grab the right-most IP address in the `X-Forwarded-For` header, not the left-most. Your expression should instead be `"^.*?(\d{1,3}+\.\d{1,3}+\.\d{1,3}+\.\d{1,3}+$"`. – Christopher Schultz Oct 02 '14 at 19:04
  • I realize Wikipedia is the the definitive source, but it currently states the left-most IP is the client: https://en.wikipedia.org/wiki/X-Forwarded-For – Nathan J.B. Dec 01 '15 at 01:53
  • So, who should I believe? :) – Nathan J.B. Dec 01 '15 at 01:54
  • 1
    So the leftmost is indeed the client's IP, however, every IP-address afterwards should be checked and validated as so-called trusted proxies. As a pre-requirement you'll have to verify that each of these proxies also check if their request makes sense. Something they should check for is clients spoofing, thus sending, the `X-Forwarded-By` header. – hbogert Jul 12 '17 at 14:51
8

Currently apache module mod_remoteip is the recommended way to do this; rpaf hasn't been reliably maintained, and can cause problems.

Kirrus
  • 302
  • 2
  • 11
  • mod_remoteip does not send the real IP to the access logs. You have to modify the default LogFormat https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=806941 – Xorax Mar 08 '23 at 15:39
  • Yes, specifically you need to change %h to %a in the LogFormat line in /etc/apache2/apache2.conf (or httpd.conf) for combined (or whichever log format you're using) – Kirrus Mar 09 '23 at 17:49
4

In addition to mod_rpaf as mentioned before, it appears that mod_extract_forwarded will perform this function as well.

One advantage to mod_extract_forwarded is that it is available from EPEL for RHEL/CentOS servers whereas mod_rpaf is not.

It appears that neither of these two modules allow you to whitelist an entire subnet of proxy servers, which is why the CloudFlare folks created their own plugin: mod_cloudflare which, it should be noted, is not a general-purpose tool like the other two; it contains a hardcoded list of CloudFlare subnets.

tylerl
  • 30,197
  • 13
  • 80
  • 113
3

Yes, we can do this.

Just add a auto_prepend_file in your PHP.ini like auto_prepend_file = "c:/prepend.php" and in this file add this:

if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
    $_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
}

You need the MOD_REMOTEIP in apache width RemoteIPHeader X-Real-IP.

Cheers,

Guiremach

Nathan J.B.
  • 10,215
  • 3
  • 33
  • 41
Guiremach
  • 55
  • 1
3

Since Apache 2.4 there is mod_remoteip built-in module that does this.

  1. Enable mod_remoteip
    (e.g. a2enmod remoteip)

  2. Create a list of trusted IP ranges (the IPs from which you accept the remote IP header). You can put them in a file like conf/trusted-ranges.txt

  3. Add this line to the Apache config:

    RemoteIPTrustedProxyList conf/trusted-ranges.txt
    
  4. Change your log file formats to use %a instead of %h for logging the client IP.


For Cloudflare you need to trust all their IP ranges and use a custom header CF-Connecting-IP:

RemoteIPHeader CF-Connecting-IP

You can get Cloudflare ranges like this:

curl https://www.cloudflare.com/ips-v4 > trusted-ranges.txt
curl https://www.cloudflare.com/ips-v6 >> trusted-ranges.txt
rustyx
  • 80,671
  • 25
  • 200
  • 267
2

Unfortunately,

at the time of this writing, none of the backports and forks at freshports.org, people.apache.org or gist.github.com worked. They were all based on an early alpha version of apache httpd 2.3 which was neither compatible with current versions of 2.2 nor 2.4.

So after hours of wasting time while trying to adjust the backports to create a real working one for httpd 2.2, I decided to move to httpd 2.4. Within httpd 2.4, mod_remoteip works smoothly, even if a load balancer has permanent keepalive connections which it uses to proxy requests from different actual client ip addresses to the backend. I'm not sure if the other modules can handle this situation (changing client ip addresses on each request within the same connection).

1

Remember that this value can be spoofed. See http://blog.c22.cc/2011/04/22/surveymonkey-ip-spoofing/ for a real-life example with Cross-site Scripting consequences.

  • 5
    Which is why every mechanism to do this translation that I've seen (including the very useful rpaf module mentioned by maciekb) uses a REMOTE_HOST whitelist so that you know you're coming from a trusted proxy. – tylerl Apr 23 '11 at 03:40
0

You can install the module mod_extract_forwarded and set MEFaccept parameter to all.

freemanpolys
  • 1,848
  • 20
  • 19