Tor-WLAN with Raspberry Pi 3

Thoughts upfront

The following is not for a high-level of anonymity and privacy; if anonymity is really crucial please look for more secure solutions like Tails or Whonix instead. Using Tor will effectively only hide your IP address, i.e. no-one can see your traffic as it leaves your location, however, starting from the Tor Exit the IP traffic looks the same as without Tor – just coming from the Tor-Exit and not from your IP address directly. Any private data included in your traffic itself will still be visible (to everyone if not encrypted, i.e. using http and the website provider if using https encrypted traffic). Keep in mind that although many sites (seem) to use https they are in fact hosted by companies like Cloudflare which terminated the https tunnel and have full access to everything sent to the website. Also your specific browser can be identified easily (although not related to you in person) via so-called fingerprinting principles.

What you will need: Raspberry Pi 3 (comes with WLAN included), a LAN cable, a SD card and a computer to prepare the SD card and then configure the Raspberry Pi. How to setup the Raspberry Pi as a mini-server (i.e. headless, without a display) is described in a separate article and is a prerequisite for the following.

Enable WLAN (Access Point with DHCP)

First, we shutdown the WLAN interface while we configure it in the next steps:

sudo ifdown wlan0

Disable the DHCP Client on WLAN

Normally the dhcpcd daemon (DHCP client) will search the network for a DHCP server to assign a IP address to wlan0. This is disabled by editing the configuration file:

sudo nano /etc/dhcpcd.conf

We tell the DHCP client that on the wlan0 interface we have a static IP address and it should not configure anything else one it. This is achieved by adding at the very end of the config file:

interface wlan0
    static ip_address=
    nohook wpa_supplicant

and then restart the service so the new config is loaded:

sudo systemctl restart dhcpcd

Enable the Access-Point Server

Install the WiFi Access-Point server:

sudo apt install -y hostapd

Create a new config file for the access point:

sudo nano /etc/hostapd/hostapd.conf

Paste the below – and change the country code, the WLAN name (SSID) and the WLAN password before saving the file:


The channel could be set to either 1, 6 or 11 depending on other wireless networks in your area. If you don’t know just take one by chance. Next, point the server to the right config file:

sudo nano /etc/default/hostapd

And then ensure the service is started:

sudo service hostapd start

Enable the DHCP Server

Start out by installing dnsmasq with the following command

sudo apt install -y dnsmasq

and then make a backup of the initial standard configuration for later reference before enabling the new config:

sudo mv /etc/dnsmasq.conf /etc/dnsmasq.conf.backup

We will put the DHCP configuration in the file dnsmasq.dhcp.conf and edit it with:

sudo nano /etc/dnsmasq.d/dnsmasq.dhcp.conf

The new configuration should be something similar to the following, however with the ip address information and things like the MAC addresses changed to your local setup:

# Configuration file for dnsmasq (DHCP part)

# Extra logging for DHCP: log all the options sent to DHCP clients and the tags
# used to determine them.

# Suppress logging of the routine operation of these protocols. Errors and problems
# will still be logged. quiet-dhcp and quiet-dhcp6 are over-ridden by log-dhcp.

# This is our local network to be served as DHCP on wlan0

# This is the only DHCP-server for wlan0

# This is your local domain name. It will tell the DHCP server which
# host to give out IP addresses for. 

# Set the Gateway IP address

# Set the DNS server

# Set the NTP time server address

# adapted for a typical dnsmasq installation where the host running
# dnsmasq is also the host running samba.
# you may want to uncomment some or all of them if you use
# Windows clients and Samba.
dhcp-option=19,0           # option ip-forwarding off for clients
dhcp-option=44,     # set netbios-over-TCP/IP nameserver(s) aka WINS server(s)
dhcp-option=45,     # netbios datagram distribution server
dhcp-option=46,8           # netbios node type

# Always allocate the same DHCP IP address based on the MAC address
dhcp-host=00:25:a5:34:af:e7, Ethernet-Bridge,,  infinite
dhcp-host=84:cf:bf:89:9b:fd, Mobile,, infinite

To test if the config for dnsmasq is free of errors check the syntax with:

sudo dnsmasq --test

DNS Server and Cache

We put the DNS server configuration in a separate file (both DHCP and DNS is the same dnsmasq software and the config could well be in one file; but it’s a bit easier to read that way):

sudo nano /etc/dnsmasq.d/dnsmasq.dns.conf

and paste in the following

# Configuration file for dnsmasq (DNS part)

# For debugging, log each DNS query as it passes through dnsmasq.

# Listen on these IP addresses for DNS requests (always include localhost):

# Where to send DNS request to which can't be resolved locally.
# Here we use the local Tor service listening on port 5300 (see /etc/tor/torrc)

# The bind-interfaces directive instructs dnsmasq to bind only to
# the network interface specified in the listen-address directive.

# The no-hosts directive instructs dnsmasq not to read hostnames
# from /etc/hosts.

# Read hostname information from this file in addition

# Never forward plain names (without a dot or domain part) upstream

# Never forward addresses in the non-routed address spaces.

# If you don't want dnsmasq to read /etc/resolv.conf or any other
# file, getting its servers from this file instead, then uncomment this.

# If you don't want dnsmasq to poll /etc/resolv.conf or other resolv
# files for changes and re-read them then uncomment this.

# Set this if you want to have a domain
# automatically added to simple names in a hosts-file.

# Number of hostnames being cached in RAM for DNS lookups

# Do not cache negative responses

# Resolve these domains locally only, don't send upstream

# Some simple domain blocking (doesn't block the IP, just domain-name)

To resolve the hostname of this server we need to provide it in a separate host-file:

sudo nano /etc/dnsmasq.hosts

and just list the local hostname and its IP address on the WLAN:	MyTorGateway

so can use its name on other devices on the WLAN (instead of the IP address).

To test if the above config for dnsmasq is free of errors run:

sudo dnsmasq --test

If everything is fine, start the dnsmasq service and check its status with:

sudo service dnsmasq start
sudo service dnsmasq status

To see the listening ports, check with:

sudo netstat -tulpn | grep dnsmasq

Enable a Time Server (NTP via chrony)

Since the aim is to route all traffic from the wireless network through Tor we need to provide the time to the WLAN since this can’t be done over Tor. The best ntp server for our purpose seems to be chrony. It’s installed via:

sudo apt install -y chrony

and then we configure it (after saving the initial config for later reference):

sudo mv /etc/chrony/chrony.conf /etc/chrony/chrony.conf.default
sudo nano /etc/chrony/chrony.conf

and paste in the following:

# Welcome to the chrony configuration file. See chrony.conf(5) for more
# information about usuable directives.

# The time server where we get our time from; here the internet router
server iburst iburst

# Time server for the following subnet

# Listen on this interface for client ntp requests

# This directive specify the file into which chronyd will store the rate
# information.
driftfile /var/lib/chrony/chrony.drift

# Uncomment the following line to turn logging on.
 tracking measurements statistics

# Log files location.
logdir /var/log/chrony

# The hardware clock (rtc) is always UTC 

# This directive enables kernel synchronisation of the real-time clock. 

# Step the system clock instead of slewing it if the adjustment is larger than
# one second, but only in the first three clock updates.
makestep 1 3

If the devices on the WLAN don’t pick up time after a few minutes it might be necessary to list the ntp server in the /etc/ntp.conf (or similar) config files of those devices.

Data Forwarding (WLAN ↔ LAN)

Only in case you want to access servers on your LAN (the one directly attached to the internet) from the Raspberry WLAN you need to allow IP4 forwarding on the Raspberry:

sudo nano /etc/sysctl.conf

Set the forwarding option to 1:


and then reload the configuration:

sudo sysctl -p

The WLAN is now setup and we only need to enable the interface:

sudo ifconfig wlan0 up
sudo reboot

Now you should test it – you should be able to connect to the new WLAN just defined and if IP4 forwarding was enabled you should have access to the Internet (no Tor yet though).

Install and configure Tor

Getting Tor up and running is really easy (including the recommended package to keep the key updated):

sudo apt install -y tor

Keep the initial config file for reference before changing it:

sudo mv /etc/tor/torrc /etc/tor/torrc.default
sudo nano /etc/tor/torrc
Log notice file /var/log/tor/tor.log
AvoidDiskWrites 1

ExitRelay 0
ExitPolicy reject *:*


AutomapHostsOnResolve 1
AutomapHostsSuffixes .onion,.exit

Check that Tor is happy with the config file and there are no issues with it:

sudo -u debian-tor tor --verify-config

The last line should say that everything looks fine. Then reload the new Tor config:

sudo systemctl reload tor.service

Check the logs for what Tor does and if it complains about anything – the following commands might be useful to check for any errors:

sudo systemctl status tor.service
sudo cat /var/log/tor/tor.log
journalctl | grep 'Tor'

In case of issues with tor one could increase the level of logging temporarily – in the tor config file /etc/tor/torrc replace the ‚log …‘ configuration with debug, info, notice, warn, or err. For example a ‚log info…‘ statement will provide more details. In the long run the log-level of ’notice‘ should be the best choice.

Enable the Firewall

First install the firewall frontend and enable the firewall:

sudo apt install nftables -y
sudo systemctl enable nftables.service

Enable the following firewall rules, starting with a config file in your home directory

nano ~/nftables.conf

and paste in

#!/usr/sbin/nft -f

flush ruleset

table ip filter {
	chain INPUT {
		type filter hook input priority 0; policy drop;

		iifname "lo" accept

		ct state related,established accept
		ct state invalid drop

		ip saddr {,} tcp dport 22 accept
		ip saddr icmp type echo-request counter packets 0 bytes 0 accept
		ip saddr icmp type echo-request counter packets 0 bytes 0 accept
		udp dport 1194 counter packets 0 bytes 0 accept
		iifname "wlan0" udp dport {123, 53, 67} accept
		iifname "tun*" udp dport {123, 53, 67} accept
		iifname "wlan0" tcp dport 9040 accept
		iifname "tun*" tcp dport 9040 accept

	chain FORWARD {
		type filter hook forward priority 0; policy drop;
		ct state invalid drop
		ct state related,established accept
		iifname "wlan0" ip daddr accept

	chain OUTPUT {
		type filter hook output priority 0; policy drop;
		oifname "lo" accept
		ct state invalid drop
		ct state related,established accept
		skuid "debian-tor" accept
		udp dport 123 accept
		ip daddr accept
		ip daddr {,,} accept
table ip nat {
		type nat hook prerouting priority -100; policy accept;
		iifname "wlan0" udp dport domain redirect to :53
		iifname "wlan0" ip daddr {,} accept
		iifname "wlan0" tcp flags & (fin|syn|rst|ack) == syn redirect to :9040
		iifname "tun*" udp dport 53 redirect to :53
		iifname "tun*" tcp flags & (fin|syn|rst|ack) == syn redirect to :9040

	chain INPUT {
		type nat hook input priority 100; policy accept;

		type nat hook postrouting priority 100; policy accept;
		oifname "eth0" ip saddr masquerade 

	chain OUTPUT {
		type nat hook output priority -100; policy accept;
		skuid "debian-tor" accept
		ip daddr {,} accept
		tcp flags & (fin|syn|rst|ack) == syn redirect to :9040

table ip6 filter {
        chain INPUT {
                type filter hook input priority 0; policy drop;
                iifname "lo" counter packets 5 bytes 245 accept

        chain FORWARD {
                type filter hook forward priority 0; policy drop;

        chain OUTPUT {
                type filter hook output priority 0; policy drop;
                oifname "lo" counter packets 5 bytes 245 accept
                counter packets 40 bytes 3241 reject

and activate these firewall rules with

sudo nft -f nftables.conf

In case something goes horribly wrong (e.g. you lock ssh sessions) you can hard reboot the server and will start without the firewall rules.

Note that nft uses its own matching of service names to port numbers – to see the list simply type in:

nft describe tcp dport

Once you’re happy with them working make them permanent with copying them to the standard place (enabled on reboot):

Note on Firefox configuration

If you’re using firefox as browser you also need to change its config as by standard firefox is blocking access to services residing on tor (onion services). To do so, type


in the field at the top (where you would type in e.g. a web-address usually) then search for „onion“ at the top and change the value for network.dns.blockDotOnion to „false“ by a double-click on it. Btw, Chrome and Edge aren’t blocking tor by default and they will work right away with the standard configuration. Test access to onion-services by browsing to e.g. „https://protonirockerxow.onion“.

If you don’t use WebRTC services then it’s strongly recommended to disable media.peerconnection.enabled  in about:config. Otherwise your local IP address will be visible to everyone on the internet (also while connected via Tor).

It might be good to also minimize the tracking via fingerprinting. One example is to enable privacy.resistfingerprinting by setting to true. Also one should disable plugin.expose_full_path and dom.battery.enabled by setting them both to false.

Optional but highly recommended is to install the „Privacy Pass“ Firefox add-on; it helps to avoid many of the annoying challenges one has to complete when using Tor. Additionally, and also optional it might be wise to install the „uBlock Origin“ add-on as well to avoid advertisements, malware, etc. which would otherwise cause additional traffic and would increases tracking capabilities.

Additional recommended add-ons for Firefox are: „Cloud Firewall“, „HTTPS Everywhere“, and „Privacy Badger“.

Some Improvements and Hardening

IP Forwarding WLAN ↔ LAN

If no connection between WLAN and LAN is required one should remove IPv4 forwarding – this can be done in two ways (one is sufficient, two are safer):

sudo nano /etc/sysctl.conf
sudo sysctl -p

or remove the rule for the forwarding filter rule of ferm.

Add the official Tor Repository

We add the official repository on the Tor network of the tor-project for Debian (as outlined at

sudo nano /etc/apt/sources.list.d/

and put in the following:

# Tor onion-site replacement for:
# deb stretch main

deb http://sdscoq7snqtznauu.onion/ stretch main

However, Debian is not supporting anonymous and safe software repos by standard, so we need to enable it first – create a new apt config file:

sudo nano /etc/apt/apt.conf.d/80onion-repos

and put in just one line:

Acquire::BlockDotOnion "false";

Now finally update and upgrade the system with:

sudo apt update
sudo apt upgrade -y

Automatic updates for Tor

It’s also recommended to get the tor software upgraded automatically. Assuming that the package ‚unattended-upgrades‘ is installed already, we simply need to add the updates from the torproject to the config:

sudo nano /etc/apt/apt.conf.d/50unattended-upgrades

and add the line with the TorProject repository:


Sidenote: information about the ‚origin‘ and other parameters can be found in the files /var/lib/apt/lists/*_InRelease.

Block many sites (adservers)

If you want to have a much more extensive ad-blocking done by dnsmasq one could just download the latest quite complete adserver list from (formated to be used with dnsmasq as plain text file) and save it as e.g. dnsmasq.adservers.conf in the folder /etc/dnsmasq.d/.

This can be even automated with a little script like the following – it must be run with root priviliges (e.g. with sudo) as it needs to install the new adblocking file in /etc:


if [[ $EUID -ne 0 ]]; then
   echo "This script must be run as root (use sudo)"
   exit 1

curl -o adservers ''

# Do some work on the file:
# Now remove MS-DOS carriage returns, replace with (much faster),
# clean up trailing blanks,
# Pass all this through sort with the unique flag to remove duplicates and save the result

sed -e 's/\r//' -e 's/127\.0\.0\.1/0\.0\.0\.0/' -e 's/[ \t]*$//' < adservers | sort -u > /etc/dnsmasq.d/

rm adservers

# Seems like the dnsmasq configtest doesn't work (always returns success)
if ! /usr/sbin/dnsmasq --test; then
    printf '%s\n' 'dnsmasq configtest failed!' >&2
    rm /etc/dnsmasq.d/
    exit 1

mv --force /etc/dnsmasq.d/ /etc/dnsmasq.d/dnsmasq.adservers.conf
/usr/sbin/service dnsmasq reload

Save this file in the /root folder (home folder for root) and add it as a cron-job for root:

sudo crontab -e

adding at the end something similar to:

15 04 * * * /root/ > /root/adservers.log 2>&1

to run it each night at 4:15. The only thing which doesn’t seem to work is to check the dnsmasq config to be valid (the check always returns a successful check).

Keep in mind though that is only blocking DNS lookups, not IP addresses. Any direct connection to an IP address is not blocked with the above (that requires a blocking within the netfilter part; ipset is the command to look for to achieve something on the IP level).

Backup your Server

Once everything is running fine and stable it’s a good idea to create a full backup of the SD-card. Especially, as these cards are known to get broken quite easily; don’t be supprised if after one or two years the Raspberry Pi gives up, this is quite normal.

Create a Backup

To start out, shut down the Raspberry with sudo shutdown now and take out the SD-card. It’s easiest to store the backup on your linux laptop. Before plugging in the SD-card in your laptop run df -h on your laptop terminal then plug in the card and run df -h again. The difference tells you the device path of the SD-card; typically it will be something like /dev/sda1 or so. To address the whole SD-card you need to remove the last part (probably just a number, maybe p1 or so as well).

Now simply run the following to create a full one-by-one backup (image) of the card:

sudo dd bs=1M if=/dev/sda of=~/sd-card-backup.img

Note, that this will take some time (around 10 minutes for a 8 GB card) so be patient.

Restore a Backup

Unmount all the partitions of the SD-card (including the numbers)

sudo umount /dev/sda1
sudo umount /dev/sda2

and restore the backup with:

sudo dd bs=1M if=~/SD-card-backup.img of=/dev/sda

which again can take quite some time (just wait for the job to complete). To ensure everything is written out, type sudo sync .


Ein Gedanke zu “Tor-WLAN with Raspberry Pi 3”

Kommentare sind geschlossen.