In some cases, you would have traffic flowing to your NGINX instance from two different cloud services / load balancers.
Some users will visit your site via service A, and other users coming from service B.
Then there is need to configure NGINX properly in order to log the correct visitor IP. When using load balancers, the connecting machine’s IP is not the end visitor’s IP address.
The standard means for communicating end visitor’s IP address is by supplying it in an HTTP header, commonly X-Forwarded-For
. However, you may stumble on a case when the two different services will use different headers, e.g. X-Real-IP
and CF-Connecting-IP
.
NGINX is very flexible with its map
and geo
directives. And the real_ip_header
directive can be set to a variable.
Let’s put those great features together and not without some duplication, achieve completion for this tricky task.
In the following example solution, some users access the site through Cloudflare and sometimes through DDos Guard service.
Non-working solution
I keep this obvious solution for educational purpose. It doesn’t work! See reasons why below.
The following goes into http {}
section of your nginx.conf
:
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
# .. put all Cloudflare ranges like above
# ddos-guard.net range:
set_real_ip_from 186.2.160.0/24;
# DDoS Guard
geo $use_x_real_ip {
default 0;
186.2.160.0/24 1;
}
# Cloudflare
geo $use_x_cf_connecting_ip {
default 0;
103.21.244.0/22 1;
103.22.200.0/22 1;
103.31.4.0/22 1;
# all other Cloudflare's ranges ...
}
map "$use_x_real_ip:$use_x_cf_connecting_ip" $real_ip_header {
default 'X-Forwarded-For';
"1:0" 'X-Real-Ip';
"0:1" 'CF-Connecting-IP';
}
real_ip_header $real_ip_header;
How this is flawed:
- The
realip
module does not really support variables forreal_ip_header
header. When we pass$real_ip_header
, then that’s what it actually receives – the raw string “$real_ip_header” - The
geo
module works with$remote_addr
by default. And this variable gets rewritten byrealip
module! So ourgeo
maps had to use original connecting (load balancer’s) IP address, which is available in$realip_remote_addr
variable
Working solution
The working solution accounts for the fact that the realip
module does not support variables in real_ip_header
. Thus, we have to rewrite X-Forwarded-For
with the value of the relevant headers. We also fix our geo
maps to look into the correct value for connecting IP.
First, make sure you have installed Headers More module. If you have installed NGINX from our repository, this can be done via:
yum install nginx-module-headers-more
Then load it at the top of your nginx.conf
:
load_module modules/ngx_http_headers_more_filter_module.so;
The following goes into http {}
section of your nginx.conf
:
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
# .. put all Cloudflare ranges like above
# ddos-guard.net range:
set_real_ip_from 186.2.160.0/24;
# DDoS Guard
geo $realip_remote_addr $use_x_real_ip {
default 0;
186.2.160.0/24 1;
}
# Cloudflare
geo $realip_remote_addr $use_x_cf_connecting_ip {
default 0;
103.21.244.0/22 1;
103.22.200.0/22 1;
103.31.4.0/22 1;
# all other Cloudflare's ranges ...
}
map "$use_x_real_ip:$use_x_cf_connecting_ip" $real_ip {
default $http_x_forwarded_for;
"1:0" $http_x_real_ip;
"0:1" $http_cf_connecting_ip;
}
more_set_input_headers "X-IP: $real_ip";
real_ip_header X-IP;
Note that in this solution it is essential to use different, arbitrary name for our rewritten real client header, e.g. real_ip_header X-IP;
. Otherwise NGINX would not be able to lookup maps properly, and the result would be preference toward the default (X-Forwarded-For
) even from networks where we care about other headers.