Building a Transparent Tor Proxy with a Raspberry Pi

Building a Transparent Tor Proxy with a Raspberry Pi

Share this post:

Many people have begun to recognize how little privacy is actually available when accessing networks like the Internet. One project that has sought to fix that is The Onion Router, more commonly known as the Tor Project

Tor provides a network stack that anonymizes users while accessing the Internet. The simplest way to make use of Tor is by installing their custom web browser, the Tor Browser. Installation is simple, the browser automatically connects to the Tor network, and you are surfing anonymously within minutes.

However, the Tor Browser has some limitations. For example, it does not provide security for any other applications on your system. Do you use a different browser? A custom email client? What about that chat application you use? Or synchronized file storage like Dropbox or OneDrive? None of that is protected by the Tor Browser. Now, you can install the native Tor client on your machine to fix that, but what if you forget to launch the client? What if you have a hardware device like a VoIP phone, or even an Xbox that just does not support Tor? What about those operating system updates that run in the background? They are not going to use Tor. What do you do? Well, do not worry. There is a solution and all you need is a Raspberry Pi to solve all of those problems.  

I’m going to show you how to build a Transparent Tor Proxy to protect your devices without needing to install or configure software on the end devices. The topology (figure 1) is pretty simple: One interface of the Raspberry Pi will provide a secure trusted network and appear as a standard router. The second interface will connect to an Untrusted network. Finally, the Raspberry Pi will force all trusted traffic over to the Tor network. The end result is a router that guarantees all of our communications are sent out via Tor. 

I also did a webinar on this topic that you can watch on-demand for free. You can watch that webinar here and follow along.

Figure 1

Step 1: Assemble Your Hardware

To get started, we need to gather a few parts. For this build, I chose to go with a Raspberry Pi 4. You could easily use a Raspberry Pi 3 or similar type device as long as it supports Linux. Just be careful to pay attention to network throughput. The Raspberry Pi 3 has a gigabit interface on it, but its real speed is much lower because the CPU and USB bus create a bottleneck. The Raspberry Pi 4 solved that problem so it can actually achieve gigabit speeds.

We also need a second network interface. This is another area where you want to be careful with your selection. I recommend a USB NIC that uses the Realtek RTL8153 chipset. The driver for that chipset is included with the Linux kernel so I know I will not have to mess around with configuring drivers later on.

Here is a list of everything I bought for this project:

Component Cost
Raspberry Pi 4B (4GB) $55.00
32GB Micro SD Card $7.49
USB NIC with RTL8153 Chipset $13.99
USB-C Power Adapter $11.99
Heatsink / Cooling Solution $17.99
Total $106.46

Step 2: Assemble Your Software

The only software we need for this project is Ubuntu Linux from Canonical. It is free and they have a version specifically for the Raspberry Pi that can be downloaded directly from their site (figure 2). You could use Raspberry Pi OS, but it is not built with security in mind. I find it is generally not worth it to try and secure a training OS so you are better off going with something mainstream like Ubuntu.

Install Ubuntu Server
Figure 2

Next, you will want to flash Ubuntu to an SD Card. Most of you are already familiar with this process. If not, I highly recommend you follow this tutorial from Canonical.

Step 3: Connect Your Hardware

Once we have all the pieces, it is time to put them together. Insert the SD Card into the Raspberry Pi’s memory slot. Then connect the USB NIC to one of the USB 3.0 ports (they have a blue tab, not white). You will also want to attach a monitor and keyboard, although this isn’t strictly necessary as Ubuntu has SSH enabled by default, but it is certainly easier as you won’t have to guess the IP address of the unit. Finally, plug in the power and watch it boot up.

Ubuntu uses a system called Cloud-Init to automatically configure itself during the first boot. It will automatically label the onboard NIC as “Eth0” and the USB NIC as “Eth1.” It will also create a default user with the following credentials:

Username: ubuntu
Password: ubuntu

Certainly not the most secure credentials, but it will make you change the password on the first login. If you try to login and it fails that usually means the Cloud-Init scripts haven’t finished running. Wait a few moments and then try again.

Step 4: Update Ubuntu

Updates are fast and furious in the Linux world thanks to thousands of contributors all over the world. Unfortunately, that means our install is almost guaranteed to be out of date on day one. We can fix that pretty easily by performing a quick update using the following command:

Do not be surprised if you see some errors indicating you, “Could not get a lock.” (figure 3). Ubuntu 20.04 has automatic updates enabled by default and they may have started before you ran your update. If that is the case, you have to do my least favorite thing: wait.

"Could not get a lock"
Figure 3

After a few minutes all of the available updates should be installed and you can reboot the unit using the following command:

Step 5: Fixup Name Resolution

One of the biggest enemies to Tor’s anonymization is DNS leaking. To prevent that, we want to make sure we know exactly who is resolving our DNS queries and, preferably, force the queries over the Tor network as well (which we will do later in this tutorial). For now, we need to disable the systemd-resolved which obfuscates our DNS configuration, and replace it with something completely in our control. Here are the basic steps:

First, we need to make sure our Raspberry Pi can always resolve its own name in order to avoid a bunch of log spam for naming errors. We can set our host name and hard code the name lookup like this:

Next, we will disable systemd-resolved and re-enable the good old fashioned resolv.conf file pointing to our DNS server of choice.

Step 6: Configure the Network Adapters

Now for the network. First, we need to disable the automatic configuration coming from Cloud-Init and NetPlan. We can do that easily with one command:

Then, we can configure our network adapters so that Eth0 is our internal network and Eth1 is our public network by editing the NetPlan configuration:

Then add the following lines to the configuration:

When you are finished, the configuration should look like figure 4.

Figure 4

Now, apply the configuration to make it take effect.

Step 7: Install the DHCP Server and Tor

Most routers provide DHCP services on their local network, and we want our Raspberry Pi to behave the same way. To handle this we will install the ISC DHCP Server, which is the default DHCP server in most modern Linux distributions. We also want to connect to Tor so we will need the Tor client installed. Issue the following command to install both:

Step 8: Configure and Enable the DHCP Server

Now it is time to configure our DHCP server. Start by editing the default configuration file.

Then, add the following lines to create a DHCP scope for the network.

The final result should look like figure 5. Notice that we are providing our Raspberry Pi’s IP address as the gateway and the DNS server to force all traffic through the device.

Create a DHCP scope for the network
Figure 5

We are now ready to fire up the DHCP server.

Step 9: Secure the Router with a Firewall

This is an important step that too many people leave out. We are not too worried about traffic on our “trusted” network (eth0), but we absolutely do not want connections coming in our “untrusted” network (eth1). Fortunately, we can use the Uncomplicated Firewall (UFW) to quickly configure that. Before we do that, however, I like to disable IPv6 since 80% of Tor relays don’t support it and it increases our attack surface. To do that, start by editing the UFW configuration.

Then, change the line that says IPV6=yes to IPV6=no as seen in figure 6.

IPV6=yes to IPV6=no
Figure 6

Apply the change by running sudo ufw reload.

Next, we can configure our firewall rules. We want to deny all traffic by default. We also want to restrict incoming connections to Tor, SSH, and DHCP. Issue the following UFW commands for the configuration we want.

You can use the sudo ufw show added command or sudo ufw status command to verify your configuration.

Step 10: Configure Tor to Act as a Proxy

The Tor client installs as a standard client by default. In fact, it is likely already running and connected to the Tor network if you have followed this tutorial step by step. However, the proxy functionality is not enabled by default so we need to get it up and running. Start by editing Tor’s configuration.

Then, add the following lines to the bottom of the file.

The final result should look like figure 7.

Configure Tor to Act as a Proxy
Figure 7

The first line maps the virtual network that Tor uses. is a private network and is the recommended setting from the people at Tor, so don’t change it unless you know what you are doing. The next two lines are designed to intercept DNS lookups for destinations on the Tor network. They don’t really matter to us since we are going to map all traffic to Tor, but the client is happier with them in there. The last two lines are the most important. They create a SOCKS proxy and a DNS proxy for us that are mapped to the Tor network.

Now we just need to reload Tor to have those changes take effect.

If something goes wrong, you can monitor journald to look for errors by typing journalctl -f /usr/sbin/tor in another terminal when starting Tor.

Step 11: Redirect All Incoming Traffic into Tor

This is the big moment. We need to configure the Raspberry Pi to take all incoming traffic from the trusted network (eth0) and route it into Tor. UFW has support for that, but it doesn’t have commands for it. That means we have to manually edit the UFW configuration files which is fun. We need to redirect the traffic prior to any filtering so we are going to edit the “before” rules of UFW.

This configuration file is read and applied in order from top to bottom, so we need to place our configuration right at the top. Add the following lines to the beginning of your file.

The end result should look like figure 8.

Figure 8

Let’s walk through some of these lines real quick.


The first line indicates we are doing NAT, or Network Address Translation. We are using NAT to manipulate the port numbers on our traffic.


The next four lines are generic ACCEPT lines which mean we don’t want to filter anything. We want all traffic to pass.

-A PREROUTING -i eth0 -p tcp -m tcp -d –dport 22 -j ACCEPT

If anything goes wrong, we need to be able to SSH into the Raspberry Pi to fix it. This line makes sure we can do that. Otherwise, our SSH traffic would be sent out to the Tor network and we would be locked out of our router.

-A PREROUTING -i eth0 -p udp -m udp –dport 53 -j REDIRECT –to-ports 5353

We want to make sure that all DNS traffic is being handled securely through our device. This line intercepts anything using UDP port 53 (DNS) and redirects it to us. This way if an application chooses to ignore our DNS server (like Windows Updates) we still grab it and send it to our servers.

-A PREROUTING -i eth0 -p tcp -m tcp –tcp-flags FIN,SYN,RST,ACK SYN -j REDIRECT –to-ports 9040

The last line is the most important. This is what makes the transparent proxy work. It grabs any other TCP traffic and forwards it to port 9040 where our Tor proxy is ready and waiting.

We can apply the changes by running sudo ufw reload. It may not seem like much happened, but we just finished our configuration. The Raspberry Pi is now acting as a router and is connected to Tor. The transparent proxy is up and running and waiting for traffic to come in. Which brings us to…

Step 12: Testing it Out

Testing the transparent proxy is really easy. Just connect a client to the eth0 interface. It should get an IP address from DHCP and be able to access the Internet. Next, open a web browser and point it to Hopefully, you see a successful test like in figure 9.

Figure 9

Notice how you did not have to tell the machine about the proxy? It is invisible to the client machine. All network traffic is being routed over Tor regardless of whether it supports it or not!

Other Considerations


At this point we are all done. However, there are a few special considerations I wanted to mention. You might be interested in configuring your Raspberry Pi as a WiFi Access Point to provide Tor access. You can certainly do that, but I do not recommend it. WiFi networks, by definition, are not bounded. You are exposing part of your network to other people which somewhat lessens the effectiveness of building a transparent proxy in the first place. However, I can certainly see where it would be useful. Consider devices like the Nintendo Switch that do not have physical network adapters. Wouldn’t it be nice to have them pass over Tor as well? If you are interested in that, there are a number of tutorials on configuring a WiFi AP on Ubuntu. Then you would just change out eth0 in my steps for whatever your wireless card’s name is.

Captured Portal

Many hotels utilize a “captured portal” system where you must authenticate through a web browser before getting Internet access. If you run into that, you will need to find a way to get your Raspberry Pi to authenticate. The easiest way to do this is to install a GUI like GNOME on the Raspberry pi. That way you can use a local browser on the Raspberry Pi to authenticate and then switch to your other clients from there.