Stop Cloudflare bypassing on shared hosting

Hey guys, I hope this woks! :wink:

With the recent announcement of the new free tier for Cloudflare Workers, I found no more excuses not to use the full depth of my copy & paste JavaScript programming skills. (Thank you, Cloudflare Workers Recipes authors!)

One of the big problems for Cloudflare users with sites on shared hosting is that hackers may find the IP address and connect directly to it, bypassing Cloudflare and the protections it offers. So here’s a solution involving a Cloudflare worker, a few .htaccess directives, and a Firewall Rule.

With a very simple Cloudflare Worker, we can add a request header, a header that will be sent from the edge (any of Cloudflare’s 180+ data centers) to the origin (your server), and therefore won’t be visible to site visitors. As long as the header name and value are kept secret by the site admin, any requests not coming through Cloudflare will not have this header, and will therefore trigger a rewrite condition at the origin server, and be redirected back to, well, Cloudflare - where a Firewall Rule will block it.

The worker (taken from this recipe):

addEventListener('fetch', event => {

 * Send header to origin, allowing for
 * .htaccess to block requests
 * not coming from Cloudflare

async function handleRequest(request) {
  // Make the headers mutable by re-constructing the Request.
  request = new Request(request)
  request.headers.set('Secret-Header', 'SeCrEt-kEy')

  return await fetch(request)

The .htaccess directives (place them at the top of the file):

# Route visitors not coming from Cloudflare to, well, Cloudflare
<IfModule mod_rewrite.c>
	RewriteEngine On
	RewriteBase /
	# Both the header and the value should be kept secret
	RewriteCond "%{HTTP:Secret-Header}" "!SeCrEt-kEy"
	# Uncomment and edit w/ IP of services such as certs, cron, Softaculous etc
	# RewriteCond "%{REMOTE_HOST}" "!^xxx\.xxx\.xxx\.xxx$"
	RewriteRule .* "accessdenied.php" [R,L]

What these directives do is check every request to see if it has a request header named “Secret-Header” and whether its value does not contain the string “SeCrEt-kEy”. If the header does not exist, or does not contain the key, the request will be redirected to a non-existing URI named here “accessdenied.php”, (a fictitious, non-existing file) which must be added in a Firewall Rule:

(http.request.uri.path eq "/accessdenied.php") Then... Block

I’ve seen a similar solution, that tested for the existence of any CF-* header, mentioned a few times in this Community, but a hacker can always add a bunch of phony CF-* headers to the requests performed by their bots.

Also, I prefer to redirect to Cloudflare instead of blocking these requests at the origin because a redirect takes ~200 bytes, while a standard 403 response page from my origin will take about 6x that amount. I could rewrite the 403 page down to a few bytes, but I still want to keep my site functional for legit visitors, and that includes meaningful error pages whenever they face one.

One consequence of this approach is that if you monitor your server logs, you’ll find it may contain both legit 302s as well as, should anyone ever try to access your website via IP address, 302s for the URLs the bot was trying to reach. The Cloudflare Firewall Events log will then have the entries for the blocks executed for URL "/accessdenied.php".

After implementing this solution, you may find that there are 302s on your server log that have no match on the Firewall Events log. This is because bots may be programmed not to follow redirects. It’s important to note that for legit bots accessing directly your site to provide services such as cron jobs, cert renewal etc, there’s the bypass rule on the .htaccess directives above, you just need to uncomment and edit it to include the service’s own IP address.

Cloudflare Workers is a paid service, and costs $5/month for the first 10 million requests, $0.50 per up to 1 million additional requests. (While I’ve tested this setup using the “free tier”, it isn’t advisable to use the free tier on a production website, as it has some limits that, when reached, will make the site break and generate 1015/1025 error pages for the duration of the rate limiting period, something about which site owners have no control.)

I can’t say this hack is going to work for everybody. I’ve tested it many, many times in the past few days (what a royal pain to test htaccess!), but since every host has different configs, firewalls, caching etc and since I just had one platform to test it (shared hosting on Siteground, Apache 2.4.x), I hope you guys will be kind enough to share your feedback if something goes wrong.

Happy Workering!