Automatically Retrieve the Latest IPs from CloudFlare for Nginx

Automatically Retrieve the Latest IPs from CloudFlare for Nginx

comments

If you followed my guide on getting your visitors real IP addresses with Nginx and CloudFlare, you're probably wondering to yourself what happens if CloudFlare's IP ranges change.

The simple solution to this is to create a bash script, which will automatically download the latest list of IP ranges from CloudFlare and save them to a file to be used with Nginx.

Conveniently for this purpose, CloudFlare offers text lists of both their IPv4 and IPv6 IP ranges.

What I've done is written a bash script that downloads the IPv4 and IPv6 lists from CloudFlare's website. It then checks that each address and its CIDR notation is valid. If one of the downloads fails, or if one of the addresses isn't valid, the script will exit.

It then will write the newly downloaded list of IPs to file and reload the Nginx server.

I've incorporated checks to make sure that if there's an error, the script will terminate early and not overwrite your current CloudFlare IPs file, and/or reload the service. However, even if somehow the config file gets corrupted, the worst that will happen is Nginx won't reload and your server will still remain online.

The script

#!/bin/bash

#
# A bash script to download the latest list of CloudFlare IP address 
# ranges to be used with Nginx for the purpose of displaying a
# visitor's real IP address
# 
# Author: Eric Mathison - https://ericmathison.com
#

# CloudFlare URLs where IP ranges are located at
CLOUDFLARE_IPSV4="https://www.cloudflare.com/ips-v4"
CLOUDFLARE_IPSV6="https://www.cloudflare.com/ips-v6"

# Nginx config file which contains CloudFlare's IP ranges
CLOUDFLARE_NGINX_CONFIG="/etc/nginx/cloudflare-ips"

# Temporary file location
TEMP_FILE_IPV4="/tmp/cloudflare-ipv4"
TEMP_FILE_IPV6="/tmp/cloudflare-ipv6"

# Validate IPv4 CIDR addresses
validateIPv4() {
	regex="^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$"
	while read ip
	do
		if [[ ! "$ip" =~ $regex ]]; then
			echo "FAILED. Reason: Invalid IPv4 address [$ip]"
			exit 1
		fi
	done < "$TEMP_FILE_IPV4"
}

# Validate IPv6 CIDR addresses
validateIPv6() {
	regex="^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$"
	while read ip
	do
		if [[ ! "$ip" =~ $regex ]]; then
			echo "FAILED. Reason: Invalid IPv6 address [$ip]"
			exit 1
		fi
	done < "$TEMP_FILE_IPV6"
}

# Download the files from CloudFlare
if [ -f /usr/bin/curl ];
then
	# IPv4
	HTTP_STATUS=$(curl -sw '%{http_code}' -o /tmp/cloudflare-ipv4 $CLOUDFLARE_IPSV4)
	if [ "$HTTP_STATUS" -ne 200 ]; then
		echo "FAILED. Reason: unable to download IPv4 list [Status code: $HTTP_STATUS]"
		exit 1
	fi
	# IPv6
	HTTP_STATUS=$(curl -sw '%{http_code}' -o $TEMP_FILE_IPV6 $CLOUDFLARE_IPSV6)
	if [ "$HTTP_STATUS" -ne 200 ]; then
		echo "FAILED. Reason: unable to download IPv6 list [Status code: $HTTP_STATUS]"
		exit 1
	fi
else
	echo "FAILED. Reason: curl wasn't found on this system."
	exit 1
fi

# Validate IP addresses
validateIPv4
validateIPv6

# Generate the new config file with the latest IPs
echo "# CloudFlare IP addresses" > $CLOUDFLARE_NGINX_CONFIG
echo "# > IPv4" >> $CLOUDFLARE_NGINX_CONFIG

while read ip
do
	echo "set_real_ip_from $ip;" >> $CLOUDFLARE_NGINX_CONFIG
done< "$TEMP_FILE_IPV4"

echo "# > IPv6" >> $CLOUDFLARE_NGINX_CONFIG

while read ip
do
	echo "set_real_ip_from $ip;" >> $CLOUDFLARE_NGINX_CONFIG
done < "$TEMP_FILE_IPV6"

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

# Clean-up temporary files
rm $TEMP_FILE_IPV4 $TEMP_FILE_IPV6

# Reload Nginx to implement changes
service nginx reload

Set proper permissions

Only the root user is allowed to reload nginx. So we'll set the permissions accordingly.

sudo chmod 700 /opt/scripts/cloudflare-nginx-ip-updater.sh

Test it out manually

Run the script manually to make sure that it's working. Check to see if the CLOUDFLARE_NGINX_CONFIG file was updated properly and that you see no error messages in the console.

sudo bash /opt/scripts/cloudflare-nginx-ip-updater.sh

Run it automatically via cronjob

I run mine daily. But if you prefer you can run yours weekly as the IP ranges won't be changing too frequently.

sudo crontab -e

Daily (Every night at 3am)

0 3 * * * /opt/scripts/cloudflare-nginx-ip-updater.sh > /dev/null 2>&1

Weekly (Every Sunday at 3am)

0 3 * * sun /opt/scripts/cloudflare-nginx-ip-updater.sh > /dev/null 2>&1

Did you find this script useful, or even use it on your own server? Let us know in the comments below!