Introducation

The purpose of this article is to modify your Nginx configuration to allow you to get the real IP addresses of your visitors for the web application behind the Cloudflare reverse proxy network (and your Nginx is your internal reverse proxy, haha😄). Bash scripts can be scheduled to create an automatically updated Cloudflare IP list file.

To get the IP address of each request-providing client (visitor) at the origin, Cloudflare adds the “CF-Connecting-IP” header. We will grab this header and get the real IP address of the visitor.

Nginx Configuration

With a small configuration change, we can integrate Nginx’s own module ngx_http_realip_module to automatically replace the real IP address of the visitor instead of getting the IP address of the CloudFlare load balancer.

Open the /etc/nginx/nginx.conf file with your favorite text editor, e.g. vim, nano, and add the following lines into http{....} block of nginx.conf.

1
2
3
4
http {
    # get real ip from cf
    include /etc/nginx/cloudflare;    
}

Add Bash Script

This bash script may run manually or can be scheduled to refresh the IP list of Cloudflare automatically.

path: /opt/scripts/cloudflare-ip-whitelist-sync.sh

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/bash
# accept user input, otherwise write to default location
CLOUDFLARE_FILE_PATH=${1:-/etc/nginx/cloudflare}

echo "#Cloudflare" > $CLOUDFLARE_FILE_PATH;
echo "" >> $CLOUDFLARE_FILE_PATH;

echo "# - IPv4" >> $CLOUDFLARE_FILE_PATH;
for i in `curl -s -L https://www.cloudflare.com/ips-v4`; do
        echo "set_real_ip_from $i;" >> $CLOUDFLARE_FILE_PATH;
done

echo "" >> $CLOUDFLARE_FILE_PATH;
echo "# - IPv6" >> $CLOUDFLARE_FILE_PATH;
for i in `curl -s -L https://www.cloudflare.com/ips-v6`; do
        echo "set_real_ip_from $i;" >> $CLOUDFLARE_FILE_PATH;
done

echo "" >> $CLOUDFLARE_FILE_PATH;
echo "real_ip_header CF-Connecting-IP;" >> $CLOUDFLARE_FILE_PATH;

# test configuration and reload nginx
nginx -t && systemctl reload nginx

Saved Result

After run the shell file, Your /etc/nginx/cloudflare file may look like as follow;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#Cloudflare ip addresses

# - IPv4
set_real_ip_from 173.245.48.0/20;
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;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 131.0.72.0/22;

# - IPv6
set_real_ip_from 2400:cb00::/32;
set_real_ip_from 2606:4700::/32;
set_real_ip_from 2803:f800::/32;
set_real_ip_from 2405:b500::/32;
set_real_ip_from 2405:8100::/32;
set_real_ip_from 2a06:98c0::/29;
set_real_ip_from 2c0f:f248::/32;

real_ip_header CF-Connecting-IP;

Field Meaning

Just for Short, the meaning of these fields in above output are as below:

  • set_real_ip_from: Define trusted addresses that are known to send the correct replacement address.

    If this is a postal carrier you trust, for example, then you can safely open the package, find the origin marker (to see who sent the package, not the people responsible for shipping it), and the field below tells you where to locate the marker.

  • real_ip_header: Defines the request header field, whose value is used as the real address of the client (package sender).

So Nginx know how to replace remote_addr with client real IP address.

Crontab Task

To prevent a situation where Cloudflare updates their IP list, we need to add a crontab talk that automatically refreshes CloudFlare’s IP address to a local file every day, and when the synchronization is complete, Nginx will be reload.

1
2
# Auto sync ip addresses of Cloudflare and reload nginx
30 2 * * * /opt/scripts/cloudflare-ip-whitelist-sync.sh >/dev/null 2>&1

Nginx Logger

Additionally, we can modify the Nginx site configuration to record important fields, such as CF request ray ID, CF IP country, for further analysis.

1
2
3
4
# Logging
log_format cloudflare '$remote_addr - $remote_user [$time_local] "$request" $http_host $status $body_bytes_sent '
    '"$http_referer" "$http_user_agent" $http_cf_ray $http_cf_connecting_ip $http_cf_ipcountry '
    '$request_time $upstream_response_time $upstream_connect_time $upstream_header_time';

In Which :

  • http_cf_ray: the ray id allocated by Cloudflare CDN for your request
  • $http_cf_connecting_ip : client real IP
  • http_cf_ipcountry: country alias, like GB, US, CA, FR, DE, CN

Example Logs

Let’s take some records from the actual Nginx access logs as an example (the IPs of these logs have been desensitized).

1
2
3
66.X.Y.93 - - [10/Apr/2023:08:44:57 +0800] "GET /robots.txt HTTP/1.1" kg7x.com 200 62 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" (7b56xxxxffba0b7a-DFW US 66.X.Y.93) 0.000 (- - -)
66.X.Y.95 - - [10/Apr/2023:08:44:58 +0800] "GET /tags/shell/ HTTP/1.1" kg7x.com 200 2957 "-" "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.5563.146 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" (7b56xxxx78b2e706-DFW US 66.X.Y.95) 0.000 (- - -)
112.X.Y.208 - - [09/Apr/2023:20:10:04 +0800] "GET /categories/ HTTP/1.1" kg7x.com 200 5019 "-" "Mozilla/5.0 (iPad; CPU OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3" (7b5xxxxecc6404d4 SG 112.X.Y.208) 63.956 (63.955 0.000 0.001)

References

  1. nginx-cloudflare-real-ip | GitHub (Thanks ergin)

EOF, Thanks for reading.