HOW TO: Block non-Cloudflare requests to your site with a Worker

April 7, 2020

Putting Cloudflare in front of your site is a fine way to benefit from some top-tier security tools. But there's nothing stopping an attacker from bypassing it and hitting your site directly, all they need is your server IP.

You could whitelist traffic from Cloudflare's IP's. In nginx it would look like this:

allow 173.245.48.0/20;
allow 103.21.244.0/22;
allow 103.22.200.0/22;
allow 103.31.4.0/22;
allow 141.101.64.0/18;
allow 108.162.192.0/18;
allow 190.93.240.0/20;
allow 188.114.96.0/20;
allow 197.234.240.0/22;
allow 198.41.128.0/17;
allow 162.158.0.0/15;
allow 104.16.0.0/12;
allow 172.64.0.0/13;
allow 131.0.72.0/22;
allow 2400:cb00::/32;
allow 2606:4700::/32;
allow 2803:f800::/32;
allow 2405:b500::/32;
allow 2405:8100::/32;
allow 2a06:98c0::/29;
allow 2c0f:f248::/32;
deny all;

...but that's the boring way. Also, those IPs are subject to change (see the current list from Cloudflare here).

Here's a method to block this traffic and serve a 403 page (roll credits) to any traffic that doesn't pass through Cloudflare first using a Worker by creating a new custom header with a secret code that your site is configured to listen for.

If the header is not present your site will issue a 403 response.

The Cloudflare Worker

First, create a worker that adds a new custom header. In this example, the new header is called cfonly and the value for it is #ImAsUpErSeCrEtCoDe# (feel free to change this to something not lame);

addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
  request = new Request(request)
  request.headers.set('cfonly', '#ImAsUpErSeCrEtCoDe#')
  return await fetch(request)
}

Assign this worker to the route YOURDOMAIN.COM/*.

Option 1: Apache/.htaccess

If your site is running on Apache, add this to your .htaccess:

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteCond "%{HTTP:cfonly}" "#ImAsUpErSeCrEtCoDe#"

# (OPTIONAL) Uncomment the following line and add any IP you need to 
# whitelist permanently (ie. for crons, etc), you can duplicate the line as needed
# RewriteCond "%{REMOTE_HOST}" "!^123\.123\.123\.123$"

RewriteRule .* "deny_dump.403" [F,L,NC]
</IfModule>

If you're running a default WordPress site, the full .htaccess should look like this:

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteCond "%{HTTP:cfonly}" "#ImAsUpErSeCrEtCoDe#"

# (OPTIONAL) Uncomment the following line and add any IP you need to 
# whitelist permanently (ie. for crons, etc), you can duplicate the line as needed
# RewriteCond "%{REMOTE_HOST}" "!^123\.123\.123\.123$"

RewriteRule .* "deny_dump.403" [F,L,NC]
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>

Option 2: nginx

Alternatively, if your site is serving over nginx just add this to your config:

if ($http_cfonly != "#ImAsUpErSeCrEtCoDe#") {
return 403; 
}

...and reload with either sudo service nginx reload or sudo /etc/init.d/nginx reload.

Great work!

Now all visitors have to go through Cloudflare (like normal people) to reach your site without getting 403'd.