Tor-WLAN with Raspberry Pi

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 on 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 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 systemctl start hostapd
sudo systemctl status hostapd
sudo systemctl enable hostapd

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

You can double-check that it worked with:

sudo sysctl net.ipv4.ip_forward

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 *:*

TransPort IsolateClientAddr IsolateClientProtocol IsolateDestAddr IsolateDestPort
TransPort IsolateClientAddr IsolateClientProtocol IsolateDestAddr IsolateDestPort
TransPort IsolateClientAddr IsolateClientProtocol IsolateDestAddr IsolateDestPort

AutomapHostsOnResolve 1
AutomapHostsSuffixes .onion,.exit

ExitNodes 9EAD5B2D3DBD96DBC80DCE423B0C345E920A758D, 85D4088148B1A6954C9BFFFCA010E85E0AA88FF0

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.

If anonymity and privacy is not your major concern but rather useability you might want to specify one or more Exit nodes to be used for all your traffic. This will ensure for example that you can specify the country where you seem to be coming from. To do that, just add to torrc config file something like:

ExitNodes 85D4088148B1A6954C9BFFFCA010E85E0AA88FF0

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

define wireguard_port = 40042

table ip filter {
	chain INPUT {
		type filter hook input priority 0; policy drop;
		iif lo accept
		ct state related,established accept
		ct state invalid drop
		ip saddr {,} tcp dport ssh ct state new accept
		ip saddr {,} icmp type echo-request accept
		udp dport $wireguard_port accept			# might not be needed
		iifname {"wlan0", "wg0"} udp dport domain accept
		iifname {"wlan0", "wg0"} tcp dport domain accept
		iifname {"wlan0", "wg0"} udp dport {ntp, bootps} accept
		iifname {"wlan0", "wg0"} tcp dport 9040 accept

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

	chain OUTPUT {
		type filter hook output priority 0; policy drop;
		oif lo accept
		ct state related,established accept
		ct state invalid drop
		skuid "debian-tor" accept
		udp dport ntp 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" tcp dport domain redirect to :53
		iifname "wlan0" udp dport ntp    redirect to :123
		iifname "wlan0" ip daddr {,} accept
		iifname "wlan0" tcp flags & (fin|syn|rst|ack) == syn redirect to :9040
		iifname "wg0" udp dport domain redirect to :53
		iifname "wg0" 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;
                iif lo counter accept

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

        chain OUTPUT {
                type filter hook output priority 0; policy drop;
                oif lo counter accept
                counter 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.

To list the active rules applied by nft currently just check with:

sudo nft list ruleset

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):

mv ./nftables.conf /etc/nftables.conf

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://protonmailrmez3lotccipshtkleegetolb73fuirgj7r4o4vfu7ozyd.onion“.

If you don’t use WebRTC services then it’s 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).

Just to minimize unnecessary DNS traffic and if you don’t use IPv6, then change in about:config the value for network.dns.disableIPv6 to false.

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“, „Privacy Badger“, „LocalCDN“ and „ClearURLs“.

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
/bin/systemctl reload dnsmasq.service

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).

Remote Access (VPN) via WireGuard

First install the software:

sudo apt install wireguard


sudo mkdir /etc/wireguard
cd /etc/wireguard

Configure the server

First create the keys:

umask 077
wg genkey > private.key
wg pubkey < private.key > public.key

Add a new interface for WireGuard and assign an IP address to it:

sudo ip link add dev wg0 type wireguard
sudo ip address add dev wg0

Configure the new interface:

sudo nano /etc/wireguard/wg0.conf

and add something along the lines of:

Address =
ListenPort = 40042
PrivateKey = xxxxx

# Smartphone
AllowedIPs =

# Laptop
AllowedIPs =

and finally enable it:

sudo ip link set up dev wg0

To check the status you can use the following commands:

sudo ip addr show dev wg0
sudo wg showconf wg0
sudo networkctl status wg0

To permanently enable the wireguard server (e.g. on reboot) run the following command on the shell:

sudo systemctl enable wg-quick@wg0
sudo systemctl start wg-quick@wg0
sudo systemctl status wg-quick@wg0

Configure the client (e.g. smartphone)

Probably the easiest way to configure your wireguard app on the smartphone is to use a QR code – just generate it on the server command line with:

qrencode -t ansiutf8 < /etc/wireguard/client/client.conf

which shots a QR code that can be scanned from within the Wireguard app and configuring it fully right away

To manually install the WireGuard VPN (e.g. on a laptop without the QR code scanning option) create a config file on that device:

nano /etc/wireguard/wg0.conf

with something along the lines of

Address =

Endpoint = <HOST-NAME>:40042
AllowedIPs =, ::/0

To start and stop the VPN use:

sudo wg-quick down wg0
sudo wg-quick up wg0

If there are issues also don’t forget to check that traffic forwarding is allowed by linux and the firewall and that the wireguard traffic is allowed by the firewall (cf. above).