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
#pool 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.
#log 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.

Apply netfilter via ferm (iptables)

Tor now already works in principle, however would need to be explicitely accessed on port 9040. So we need to re-route all traffic coming from the WLAN to Tor which is forced as follows:

Next we install the ferm package which allows to handle iptables-rules much easier:

sudo apt install -y ferm

and answer the question if ferm should be enabled during boot with Yes. Keep the initial default ferm.config file for later reference before changing it:

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

Copy the following into the ferm config file – check the networks and ip addresses so they fit your local setup:

# Linux NetFilter ferm-rules for a Tor client service provided on wlan0 with the internet gateway on eth0.
# Apply the rules via: "sudo ferm --flush ferm.conf && sudo ferm --interactive ferm.conf".
# For details on the ferm tool check

@def $TOR_USER = debian-tor;
@def $LOC_HOST =;
@def $LAN_PRIV =;      # The ISP router LAN network (on eth0)
@def $WLAN_NET =;      # The WLAN AP network (on wlan0)
@def $VIRT_NET =;         # Network used for onion sites
@def $OPEN_VPN =;           # Network used for OpenVPN
@def $BROADCAST =;	# Needed by dnsmask

domain ip {
    table nat {
        chain PREROUTING {
            policy ACCEPT;
            interface wlan0 {
                protocol udp dport domain REDIRECT to-ports 53;
                daddr ($LAN_PRIV $WLAN_NET) ACCEPT;   ### Required for local ssh, http access etc.
                protocol tcp tcp-flags (FIN SYN RST ACK) SYN REDIRECT to-ports 9040;
            interface tun+ {
                protocol udp dport domain REDIRECT to-ports 53;
                protocol tcp tcp-flags (FIN SYN RST ACK) SYN REDIRECT to-ports 9040;
        chain OUTPUT {
            policy ACCEPT;
            mod owner uid-owner $TOR_USER ACCEPT;
            daddr ($LAN_PRIV $WLAN_NET) ACCEPT;
            protocol tcp tcp-flags (FIN SYN RST ACK) SYN REDIRECT to-ports 9040;
        chain POSTROUTING {
            outerface eth0 saddr $WLAN_NET MASQUERADE;   ### Some devices reject packets from other networks

    table filter {
        chain INPUT {
            policy DROP;
            interface lo ACCEPT;
            mod state state INVALID DROP;
            mod state state (ESTABLISHED RELATED) ACCEPT;
            saddr ($LAN_PRIV $WLAN_NET) protocol tcp dport (ssh http https) ACCEPT;
            saddr ($LAN_PRIV $WLAN_NET) protocol icmp icmp-type echo-request ACCEPT;
            protocol udp dport openvpn ACCEPT;
            interface (wlan0 tun+) {
                protocol udp dport (ntp domain bootps) ACCEPT;
                #protocol udp dport 5300   ACCEPT;
                protocol tcp dport 9040 ACCEPT;
        chain FORWARD {
            policy DROP;
            mod state state INVALID DROP;
            mod state state (ESTABLISHED RELATED) ACCEPT;
            #daddr ($LAN_PRIV $WLAN_NET) ACCEPT;   ### Allow traffic between the two LANs
            interface wlan0 daddr $LAN_PRIV ACCEPT;   ### Allow connections from WLAN to ISP LAN
        chain OUTPUT {
            policy DROP;
            outerface lo ACCEPT;
            mod state state INVALID DROP;
            mod state state (ESTABLISHED RELATED) ACCEPT;
            mod owner uid-owner $TOR_USER ACCEPT;
            protocol udp dport ntp ACCEPT;            ### Allow ntp as client (eth0) and server (wlan0)

domain ip6 {
    table filter {
        chain INPUT {
            policy DROP;
            interface lo ACCEPT;
        chain FORWARD policy DROP;
        chain OUTPUT {
            policy DROP;
            outerface lo ACCEPT;
            #LOG log-prefix "Dropped outbound IPv6 packet: " log-level debug log-uid;
            REJECT reject-with icmp6-port-unreachable;

Load the new netfilter rules:

sudo ferm --flush /etc/ferm/ferm.conf
sudo ferm --interactive /etc/ferm/ferm.conf

Confirm this with „yes“ when asked for it. To check if it was applied fine, check it via:

sudo import-ferm | less

This should print out the current netfilter rules (it looks different though as e.g. some ip addresses got split up into separate rules but it’s all fine if a list of rules is shown).

It is always good to do a reboot and see if everything is started and loaded as it should be:

sudo reboot

Log in to the Raspberry again and test it with the following:

curl ; echo

which should give you an output stating that you’re using Tor now.

In case the netfilter rules are not applied check the following file that the init script is enabled:

# Enable the ferm init script? (i.e. run on bootup)

and try again with another reboot.

To test that the WLAN is routing everything through Tor, connect your computer via the new WLAN to it and with a browser visit

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 .


Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

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