🚀 Executive Summary

TL;DR: This guide provides a Python script to automate the blocking of Tor exit nodes on an Nginx firewall. It fetches the latest Tor exit node IP list, formats it into Nginx ‘deny’ rules, and integrates it into the Nginx configuration, significantly enhancing security and saving manual effort.

🎯 Key Takeaways

  • The Tor Project maintains a public `torbulkexitlist` URL for fetching current Tor exit node IP addresses.
  • Nginx’s `include` directive allows dynamic loading of a separate configuration file (e.g., `blocked_tor_ips.conf`) containing `deny` rules, making the blocklist easily manageable.
  • Automating the script execution and Nginx reload using a cron job ensures the Tor exit node blocklist is regularly updated and applied without manual intervention.

Auto-Block Tor Exit Nodes on Nginx Firewall using Python

Auto-Block Tor Exit Nodes on Nginx Firewall using Python

Hey there, Darian Vance here. As a Senior DevOps Engineer at TechResolve, I spend a lot of my time staring at logs and looking for anomalies. A few years back, I noticed a pattern of suspicious, automated traffic hitting some of our web apps. After some digging, a significant chunk was coming from the Tor network. Blocking IPs manually was a reactive, time-consuming nightmare. I realized I was wasting hours a week on a task a simple script could do in seconds.

So, I built a small Python utility to automate it. It pulls the latest list of Tor exit nodes and plugs it directly into our Nginx firewall configuration. It’s a “set it and forget it” solution that has saved me countless hours and significantly hardened our security posture. Today, I’m going to walk you through how to build the exact same thing. Let’s get this done so you can get back to your day.

Prerequisites

Before we start, make sure you have the following ready:

  • A server running Nginx.
  • Python 3 installed on that server.
  • Basic familiarity with the command line and editing configuration files.
  • Permissions to modify Nginx configuration files and set up scheduled tasks (cron jobs).

The Step-by-Step Guide

Step 1: Understand the Logic

This is simpler than it sounds. The Tor Project generously maintains a public list of all active exit node IP addresses. Our goal is to fetch this list, format it in a way Nginx understands (`deny ip_address;`), and save it to a configuration file that Nginx can read. We’ll then automate this process to keep our blocklist fresh.

Step 2: The Python Script

First, you’ll want to set up a project directory and a Python virtual environment. I’ll skip the standard `venv` setup steps since you likely have your own workflow for that. The one external library we’ll need is `requests` for making HTTP calls. You can add it to your project’s dependencies.

Here’s the script. I’ve named it `update_tor_blocklist.py`. I’ve added comments to explain what each part does.


import requests
import os
from datetime import datetime

# The URL provided by the Tor Project to get a list of all exit node IPs.
TOR_EXIT_LIST_URL = "https://check.torproject.org/torbulkexitlist"

# The location where we'll save our Nginx-formatted blocklist.
# IMPORTANT: Nginx needs read permissions for this file.
NGINX_BLOCKLIST_FILE = "path/to/your/nginx/conf.d/blocked_tor_ips.conf"

def fetch_tor_exit_nodes():
    """Fetches the list of Tor exit node IPs."""
    print("Fetching latest Tor exit node list...")
    try:
        response = requests.get(TOR_EXIT_LIST_URL)
        # Raise an exception for bad status codes (4xx or 5xx)
        response.raise_for_status()
        # The list is just a text file with one IP per line.
        ip_list = response.text.strip().split('\n')
        print(f"Successfully fetched {len(ip_list)} IP addresses.")
        return ip_list
    except requests.exceptions.RequestException as e:
        print(f"Error: Could not fetch the Tor exit list. {e}")
        return None

def generate_nginx_config(ip_list):
    """Formats the IP list into Nginx 'deny' rules."""
    if not ip_list:
        return ""
    
    # Header for the config file for clarity
    header = f"# Tor Exit Node Blocklist\n# Updated: {datetime.utcnow().isoformat()}Z\n\n"
    
    # Create a "deny" rule for each IP address
    rules = [f"deny {ip};\n" for ip in ip_list]
    
    return header + "".join(rules)

def main():
    """Main function to run the script."""
    ip_addresses = fetch_tor_exit_nodes()

    if ip_addresses is None:
        print("Aborting script due to fetch failure.")
        return # Gracefully exit if we couldn't get the IPs

    nginx_config_content = generate_nginx_config(ip_addresses)

    if not nginx_config_content:
        print("No content to write. Exiting.")
        return

    try:
        with open(NGINX_BLOCKLIST_FILE, 'w') as f:
            f.write(nginx_config_content)
        print(f"Successfully wrote blocklist to {NGINX_BLOCKLIST_FILE}")
    except IOError as e:
        print(f"Error: Could not write to Nginx config file. Check permissions. {e}")
        return

if __name__ == "__main__":
    main()

Pro Tip: Notice the `NGINX_BLOCKLIST_FILE` variable. Don’t hardcode this path directly in production scripts. In my setups, I typically pull this from an environment variable or a `config.env` file. It makes the script much more portable between staging and production environments.

Step 3: Integrate with Nginx

Now that our script generates the `blocked_tor_ips.conf` file, we need to tell Nginx to actually use it. This is incredibly straightforward. Open your main Nginx configuration file (often `nginx.conf`) and, inside the `http` block, add the following line:


# Inside your http { ... } block

include path/to/your/nginx/conf.d/blocked_tor_ips.conf;

By placing it in the `http` block, you apply the blocklist to all `server` blocks (all your websites). If you only want to protect a specific site, you can place the `include` line inside that site’s `server` block instead.

After adding this line, test your Nginx configuration and then reload the service to apply the changes. Remember, you’ll need to do this every time the script updates the file.

Step 4: Automate with a Cron Job

Manually running a script is no better than manually blocking IPs. Let’s automate it. We can use a cron job to run our Python script on a schedule. I find that updating the list once a week is a good balance.

You can set up a new cron job to execute the script. Here’s an example that runs it at 2:00 AM every Monday:


0 2 * * 1 python3 script.py

In a real-world cron job, you should use absolute paths for both the `python3` executable (from your virtual environment) and the `script.py`. You’d also want to add a second command to reload Nginx gracefully after the script succeeds, ensuring the new blocklist is applied immediately.

Common Pitfalls (Where I Usually Mess Up)

  • File Permissions: This is the number one issue. The user running the cron job needs write permissions for `blocked_tor_ips.conf`. The user the Nginx process runs as (often `www-data` or `nginx`) needs read permissions for it. Get this wrong, and either the script fails or Nginx can’t load the config.
  • Forgetting to Reload Nginx: The script can run perfectly, but if you don’t reload Nginx, the changes won’t take effect. Make sure your automation includes a reload step. A safe way is `nginx -s reload`.
  • Blocking Legitimate Users: This is a policy decision, not a technical one. Some people use Tor for legitimate privacy reasons. By blocking the entire network, you might be blocking valid users. You have to weigh the security benefits against this potential downside for your specific application.

Conclusion

And that’s it. With one Python script and a single line in your Nginx config, you’ve created an automated, self-updating firewall rule to block a common source of malicious traffic. This is a perfect example of the DevOps mindset: identify a repetitive, manual task and automate it to improve both efficiency and security.

Now you can spend less time hunting through logs and more time building great things.

All the best,
Darian Vance

Darian Vance - Lead Cloud Architect

Darian Vance

Lead Cloud Architect & DevOps Strategist

With over 12 years in system architecture and automation, Darian specializes in simplifying complex cloud infrastructures. An advocate for open-source solutions, he founded TechResolve to provide engineers with actionable, battle-tested troubleshooting guides and robust software alternatives.


🤖 Frequently Asked Questions

âť“ How can I automatically block Tor exit nodes on my Nginx server?

You can automatically block Tor exit nodes by using a Python script to fetch the latest IP list from the Tor Project, format these IPs into Nginx `deny` rules, write them to a dedicated Nginx configuration file, and then use a cron job to regularly run the script and reload Nginx.

âť“ How does this automated Nginx blocking compare to alternatives?

This method offers a lightweight, ‘set it and forget it’ solution that directly leverages Nginx’s native capabilities for IP blocking, saving significant manual effort compared to reactive IP blocking. It’s a script-based approach that avoids the overhead of more complex Web Application Firewalls (WAFs) if the primary goal is specifically Tor exit node blocking.

âť“ What are common implementation pitfalls when blocking Tor exit nodes with this method?

Common pitfalls include incorrect file permissions (the cron user needs write access to the blocklist file, and the Nginx user needs read access), forgetting to reload Nginx after the blocklist file is updated, and the policy decision of potentially blocking legitimate users who utilize Tor for privacy reasons.

Leave a Reply

Discover more from TechResolve - SaaS Troubleshooting & Software Alternatives

Subscribe now to keep reading and get access to the full archive.

Continue reading