HOW TO SET UP A WIREGUARD VPN SERVER WITH PIHOLE
2022-08-09
Introduction
In this guide we will see how to set up a VPN server with advertisement blocking capabilities using Debian, Wireguard and PiHole. To follow this guide, make sure to have an updated installation of Debian; at the time of writing the latest version available is Debian 12, but any newer version should also work. For the rest of the tutorial we will be using theIPFilter
firewall,
but you can use any other netfilter frontend of your choice.
Installation
Let us start by installingwireguard-tools
:
marco@vpnnode:~$ sudo apt install wireguard
The version of NFTables I am currently using is:
marco@vpnnode:~$ sudo nft -v
nftables v1.0.6 (Lester Gooch #5)
Configure Wireguard(Server)
Let us now start configuring the wireguard server by generating the keypair and the configuration file; to do so, we will create a reserved directory:
root@vpnnode:~# mkdir -p /etc/wireguard/
root@vpnnode:~# cd /etc/wireguard/
Once inside it, we can generate a new keypair with the
wg(8)
utility:
root@vpnnode:/etc/wireguard# wg genkey | tee privkey | wg pubkey > pubkey
root@vpnnode:/etc/wireguard# ls -lh
total 8.0K
-rw-r--r-- 1 root root 45 Aug 6 08:46 privkey
-rw-r--r-- 1 root root 45 Aug 6 08:46 pubkey
root@vpnnode:/etc/wireguard# cat privkey
+LoX/Rrh2VR6nFiExOweXR37HluHdOhjBiFu7jqK7mo=
at this point, copy the content of privkey file and create a new file called
wg0.conf
where:
-
PrivateKey
is equal toprivkey
; -
Address
is the CIDR mask of the VPN(i.e. from192.168.2.1
to192.168.2.254
); -
ListenPort
is the UDP port where the Wireguard server will listen to.
root@vpnnode:/etc/wireguard# cat wg0.conf
[Interface]
PrivateKey = +LoX/Rrh2VR6nFiExOweXR37HluHdOhjBiFu7jqK7mo=
Address = 192.168.2.1/24
ListenPort = 48965
Configure the firewall
The next step is to configure the firewall to accept incoming packets from the chosen UDP port and to create a NAT for the Wireguard interface(wg0
). To keep things as simple as possible,
I will be using nftables but you can accomplish the same
result with pretty much any netfilter frontend.
On Debian, an empty nftables ruleset looks like this:
flush ruleset
table inet filter {
chain input {
type filter hook input priority filter;
}
chain forward {
type filter hook forward priority filter;
}
chain output {
type filter hook output priority filter;
}
}
And, if you don't have any additional firewall rule, you will just need the following configuration:
# Wireguard NAT
add table wireguard-nat
table ip wireguard-nat {
chain prerouting {
type nat hook prerouting priority -100; policy accept;
}
chain postrouting {
type nat hook postrouting priority 100; policy accept;
oifname "ens3" masquerade # Be sure to replace 'ens3' with your network interface
}
}
However, if you are planning to filter the input chain by dropping all packets and allowing only a specific set of ports, be
sure to allow inbound traffic for the selected UDP port using the following rule:
chain input {
type filter hook input priority filter; policy drop; # Drop all packets by default
iifname "lo" accept comment "Accept loopback interface" # Accept traffic from loopback device
# Accept established and related packets
ct state established,related counter accept comment "Accept established and related packets"
ct state invalid counter drop comment "Drop invalid packets"
# Allow ICMP requests
icmp type echo-request counter accept comment "Accept incoming ICMP"
# Allow inbound HTTP and HTTPS traffic
tcp dport { 80, 443 } counter accept comment "Accept incoming services"
# Allow inbound UDP traffic for Wireguard
udp dport { 48965 } counter accept comment "Accept Wireguard packets"
}
However, if you are planning to filter the input chain by dropping all packets and allowing only a specific set of ports, be
sure to allow inbound traffic for the selected UDP port using the following rule:
chain input {
type filter hook input priority filter; policy drop; # Drop all packets by default
iifname "lo" accept comment "Accept loopback interface" # Accept traffic from loopback device
# Accept established and related packets
ct state established,related counter accept comment "Accept established and related packets"
ct state invalid counter drop comment "Drop invalid packets"
# Allow ICMP requests
icmp type echo-request counter accept comment "Accept incoming ICMP"
# Allow inbound HTTP and HTTPS traffic
tcp dport { 80, 443 } counter accept comment "Accept incoming services"
# Allow inbound UDP traffic for Wireguard
udp dport { 48965 } counter accept comment "Accept Wireguard packets"
}
After that, we can reload the ruleset by issuing the following command:
$> sudo systemctl restart nftables
Routing is complete, the last thing to do is to enable ip forwarding
Enable IP forwarding
In order to route packets between VPN's clients and a remote host, we need to enable the ip forwarding feature. To do so, type the following command:
root@vpnnode:/etc/wireguard# sysctl -w net.ipv4.ip_forward=1
net.ipv4.ip_forward = 1
and to make it permanent, edit the
/etc/sysctl.d/99-sysctl.conf
file and uncomment the following line:
# Uncomment the next line to enable packet forwarding for IPv4
net.ipv4.ip_forward=1
Configure Wireguard(Client)
Wireguard can be installed in a wide spectrum of operating system, in this guide I will not cover the installation process; in order to install the wireguard client for your computer/table/phone, please refer to this page. After that, open up the configuration file and add the following content:
[Interface]
PrivateKey = ni16f/oyWn8G0rdsJ7YGyytjXvJSfaNzhzFSG5Bv4Gg= # <-- client private key
Address = 192.168.2.2/24
DNS = 192.168.2.1
[Peer]
PublicKey = 4wzgj/0u53Jiheq8DjwQ9GRnvnzv0qcsisKARdnrr1c= # <-- server public key
PresharedKey = PW21sz8kl+nY8WRNJEypkqWJGLARSX2A5KjbPfaEUp0= # <-- wg genpsk
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = <SERVER_IP_ADDRESS>:48965
PersistentKeepalive = 15
Be sure to replace the following fields according to your needs:
-
PrivateKey
: replace it with client's private key(you can generate a new keypair usingwg genkey | tee privkey | wg pubkey > pubkey
command if you do not use a graphical client); -
PublicKey
: replace with server's public key(i.e./etc/wireguard/pubkey
file on the VPN server); -
PresharedKey
: you can generate a preshared key withwg genpsk
(this field is optional); -
Endpoint
: the IP address of your server with the Wireguard UDP port.
Back to the server
Open the/etc/wireguard/wg0.conf
file and add the following entry at the end:
[Interface]
PrivateKey = +LoX/Rrh2VR6nFiExOweXR37HluHdOhjBiFu7jqK7mo=
Address = 192.168.2.1/24
ListenPort = 48965
# Add this
[Peer]
PublicKey = 1+54fGF/zZlVTxDiJ3rlmrH65+5K1NMFKwxlniA/2js= # <-- Client public key
PresharedKey = PW21sz8kl+nY8WRNJEypkqWJGLARSX2A5KjbPfaEUp0=
AllowedIPs = 192.168.2.2/32
where:
-
PublicKey
is the public key of the client; -
PresharedKey
is the preshared key previously generated on the client configuration; -
AllowedIPs
is the client's IP address.
Finally, let us start the Wireguard server:
root@vpnnode:/etc/wireguard# systemctl enable wg-quick@wg0 --now
root@vpnnode:/etc/wireguard# wg
interface: wg0
public key: 4wzgj/0u53Jiheq8DjwQ9GRnvnzv0qcsisKARdnrr1c=
private key: (hidden)
listening port: 48965
peer: 1+54fGF/zZlVTxDiJ3rlmrH65+5K1NMFKwxlniA/2js=
preshared key: (hidden)
allowed ips: 192.168.2.2/32
You can now enable the VPN connection from your client. Right now, you will only be able to ping the
VPN gateway(192.168.2.1
) without being able to access the internet. This is normal because the DNS
server we have specified in the client's configuration file(i.e.
192.168.2.1
) is not yet active.
We will fix this in a second by installing PiHole.
Configuring PiHole
PiHole is an internet tracking blocking system which acts as a DNS sinkhole. It is designed primarily for embedded devices such as the RaspberryPi, but it can be easily installed on any other Linux operating system. Since PiHole makes use of many different daemons(such as a DNS server, lighttpd and the AdminLTE dashboard), we will install it using Docker. This approach allows us to avoid manual configuration and simplify update operations.Installing Docker on Debian is outside the scope of this guide, so please refer to the official documentation.
After that, set up a password for the PiHole dashboard(i.e. the password needed to log in to the web interface) and configure the docker container with the following
docker-compose.yml
file:
version: "3"
# More info at https://github.com/pi-hole/docker-pi-hole/ and https://docs.pi-hole.net/
services:
pihole:
container_name: pihole
image: pihole/pihole:latest
# For DHCP it is recommended to remove these ports and instead add: network_mode: "host"
ports:
- "192.168.2.1:53:53/tcp"
- "192.168.2.1:53:53/udp"
- "192.168.2.1:8888:80/tcp"
dns:
- 127.0.0.1
- 1.1.1.1
environment:
TZ: 'Europe/Rome'
WEBPASSWORD: 'BADPW'
# Volumes store your data between container upgrades
volumes:
- './etc-pihole:/etc/pihole'
- './etc-dnsmasq.d:/etc/dnsmasq.d'
restart: unless-stopped
Launch it with docker-compose up -d
, you should see its status in the docker logs:
marco@vpnnode:~$ docker-compose ps
docker-compose ps
Name Command State Ports
-------------------------------------------------------------------------------------------------------
pihole /s6-init Up (healthy) 0.0.0.0:53->53/tcp, 0.0.0.0:53->53/udp, 67/udp, 0.0.0.0:8888->80/tcp
Finally, let us configure PiHole to listen to our network interface.
To do so, open up your browser and go to the
admin page(be sure to be connected to the VPN,
PiHole does not expose the port 8888/80 to the external interface). You should see a page like this:
Enter your password and follow these instructions:
- Go to "settings" in the left pane;
- Go to the "DNS" tab;
- Selected the "Respond only on interface eth0" radio button in the "interface settings" section.