Today you will learn how to block TOR network IP’s (relays and bridges) using Cloudflare Workers for Free.

Before we dig into anything, I must inform you that Cloudflare has recently introduced Managed Lists where blocking TOR can be done with a click. The only downside is that these are available only for Enterprise customers.

If you can afford it, great; you can ignore this post. However if you cannot afford an Enterprise plan, do continue on reading.

Blocking TOR Network

Initially I have created a worker that would be used only as a cron job, fetch the TOR IP’s, parse them and store them into Cloudflare Lists. This list would be then used across any website you own inside the Firewall Rules.

The main issue with the Lists feature was that it’s limited to 10,000 items across all lists. So it won’t matter even if you are on Business or Enterprise plan, that’s the limit right now as documented here .

The other issue was that you cannot store individual IPv6 addresses as of now, as stated here .

With these two major issues, using lists is not a viable option; so I went with Workers KV instead. After many tests and discussions with several people on Cloudflare’s Discord channel, I have found the best way to store these TOP IP’s and query them.

Using a “smart” approach, I am storing just one entry (key:value) for all the TOR IP’s; saving costs considerably.

This works because Workers KV value limit is 25 Mb, whilst the many IP’s we store take up to… a few Kb only.

The limits on the Workers KV free plan are more than enough to write/read these IP’s constantly. And in case it’s not enough, Caching can be applied inside the worker (I already applied a 10 minute caching on READ) and the plan can be upgraded for just 5$/month.

Of course you can also be smart about it’s usage and check the user IP only in specific areas you want to protect, for example Login areas and others.

The service worker

I have prepared a ready to use worker for you on Github .

All you have to do is clone it, configure the requirements and publish it.

Configuration

In order for this to work, you need a couple of things:

  • Wrangler
  • Your Account ID
  • One KV Namespace

To obtain your Account ID, simply open any of your websites on Cloudflare and check the lower right side on the “Overview” page. Once you have the Account ID, simply store it in wrangler.toml:

1account_id = "..."

To create the Worker KV Namespace execute the following command:

sudo wrangler kv:namespace create storage

The result should be:

1🌀  Creating namespace with title "cloudflare-block-tor-worker-storage"
2✨  Success!
3Add the following to your configuration file:
4kv_namespaces = [
5   { binding = "storage", id = "cc309bd612d9476f8abdc43ec4f93fd6" }
6]

Using the above ID, edit wrangler.toml and store the ID:

1[[kv_namespaces]]
2  binding = "TOR_COMBINED_LIST"
3  id = "cc309bd612d9476f8abdc43ec4f93fd6"

Please do not create the KV Namespace yourself, use the above command. KV Binding is required, do not ignore it.

Once the above steps are finished, publish the worker:

sudo wrangler publish

The worker is configured to run every 60 minutes (one hour), if you want to change this just edit wrangler.toml. Shorter periods will not help though as the API updates only hourly.

Using the list

Now you have a list of TOR Network IP’s and it’s time to use it. This is extremely simple if you are already familiar with workers, if you are not it’s still easy.

The idea is to check the incoming user IP against the list and if there is a match, block the request. Below a simple starter script that you can simply copy and paste into a new worker:

 1const clientIP = request.headers.get('CF-Connecting-IP');
 2const ipset = await TOR_COMBINED_LIST.get("ipset", { type: "json", cacheTtl: 600 });
 3
 4if (ipset[clientIP]) 
 5{
 6return new Response("TOR is Forbidden", { 
 7  headers: {
 8	'content-type': 'text/html;charset=UTF-8',
 9  },
10  status: 403
11});
12}

The lookup is extremely fast because the list of IP’s are stored as one object, so you don’t need to parse or lookup anything.

Caching is also applied (you can disable it if you want) to speed up things even more. Workers KV cache works by using the Cache API .

Once you retrieved the value you simply check if the IP exists, if it does return whatever you want.

Use the above snippet anywhere inside your new or existing worker (and even Cloudflare Pages), as long as you have the request object available.

Testing the list

As previously mentioned, it is extremely easy to use this list even if you never used Workers before.

I’ve made a small example that you can simply copy paste and use it on any of your websites:

 1addEventListener("fetch", event => {
 2  event.respondWith(handleRequest(event.request))
 3})
 4
 5const html = `<!doctype html>
 6<html lang="en">
 7  <head>
 8    <title>Access Denied</title>
 9    <meta charset="utf-8">
10    <meta http-equiv="X-UA-Compatible" content="IE=Edge" />
11    <meta name="robots" content="noindex, nofollow" />
12    <meta name="viewport" content="width=device-width, initial-scale=1">
13    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous"> 
14  </head>
15  <body>
16    <div class="container">
17      <h1 class="mt-5 text-center">Oops!</h1>
18      <h2 class="mt-2 text-center text-danger">TOR Network is not allowed.</h2>
19    </div>
20  </body>
21</html>`;
22
23async function handleRequest(request) 
24{
25  const clientIP = request.headers.get('CF-Connecting-IP');
26  const ipset = await TOR_COMBINED_LIST.get("ipset", { type: "json", cacheTtl: 600 });
27  
28  if (ipset[clientIP]) 
29  {
30    return new Response(html, { 
31      headers: {
32        'content-type': 'text/html;charset=UTF-8',
33      },
34      status: 403
35    });
36  }
37
38  const response = await fetch(request);
39
40  return response;
41}

Don’t forget to add the KV Binding to this worker.

With the above worker deployed, we fire up the TOR Browser and test:

Block TOR Result

As you can see, it works exactly as expected. TOR Network IP’s blocked, relays and bridges.

Please note that I have removed this from my own website, so if you try to access via TOR now it will work (I have no reason to block TOR).

Calculating costs

Right at the start of this posts I mentioned the Free Worker limits are enough to block TOR, and they are. I also mentioned that I have done many tests to figure out the best way of creating this project.

During my tests I have used many READ/WRITE/LIST/DELETE requests that ended up costing around $60-$100 per month. Not ideal is it? 😳

So I figured out a way to do just one WRITE per hour. That means 24 WRITE(s) per Day, 744 WRITE(s) per Month (31 days).

This stays within the Free tier for Workers KV and you can block TOR.

Well that’s it, I hope this article and tool has helped you.

Let me know your thoughts!