Build Your Own Wireguard VPN Server with Pi-Hole for DNS Level Ad Blocking

Recently, a friend made me aware of an alternative to OpenVPN named Wireguard. It’s designed to be extremely lightweight, with a small source code footprint which makes it easily auditable. A whitepaper defining the protocol has been produced and is available for review.

Wireguard uses UDP for communication and functions by routing some, or all, traffic through a virtual network interface, allowing for split tunnelling if desired. Traffic is encrypted and unencrypted using private/public key pairs, where each peer has the public key of the other(s). Hence, peers which are part of the same VPN are able to communicate with each other and roam between networks without much difficulty.

WireGuard aims to be as easy to configure and deploy as SSH. A VPN connection is made simply by exchanging very simple public keys – exactly like exchanging SSH keys – and all the rest is transparently handled by WireGuard. It is even capable of roaming between IP addresses, just like Mosh. There is no need to manage connections, be concerned about state, manage daemons, or worry about what’s under the hood. WireGuard presents an extremely basic yet powerful interface.

The main reason this caught my interest is, due to its design, Wireguard is lightning fast, whereas I’ve been experiencing all sorts of issues (speed being just one) with my current OpenVPN solution. Wireguard is also a lot more stealthy than other VPNs; it’s designed to only send traffic when two peers are talking. As a result, I wanted to try building a Wireguard server of my own. Plus, it’s a good exercise to get a better understanding of VPNs and iptables.

I’ve also wanted to try Pi-Hole since I first heard about it some time ago. Pi-Hole is a DNS black hole which effectively blocks the majority of ads on the web, reducing average bandwidth usage and page load times across the board. Since implementing Pi-Hole, I’ve discovered that almost a full third of our household DNS requests were to known (and now blocked) advertising domains.

Having a robust VPN solution is fast becoming a necessity in any network, personal or otherwise. Having the ability to securely use the Internet in a variety of situations and locations makes one a better Internet citizen. If we’re able to improve our browsing experience at the same time, then why wouldn’t we jump at the opportunity?

Roll a VPS Server

The first thing to do is to create a home for Wireguard and Pi-Hole. Given that we’ve been over the process for creating a new VPS in previous posts, I won’t belabour the point here. Suffice to say, I created an Ubuntu 18.10 x64 server, 1 CPU, 1024MB of RAM.

Install Wireguard

All of the commands to get a base install of Wireguard up and running are available on the Wireguard website, but the quick setup guide they provide is a bit lean, so I’ve collated here additional configurations which I found useful. Scripts for the entire process (including Wireguard, Unbound, and Pi-Hole) can be found here. Once the server has booted:

SSH into the server:

ssh <username>@<server_address>

Add the Wireguard repository to the Ubuntu VM:

sudo add-apt-repository ppa:wireguard/wireguard -y

Update/upgrade the system to refresh the repository:

sudo apt update -y && apt upgrade -y

Install Wireguard:

sudo apt install wireguard -y

Assuming all went well, congrats! Wireguard is now installed on the server.

Server Configuration

Next, it’s necessary to generate public/private key pairs for the peers. Create a new directory for the Wireguard keys (making an actual file isn’t necessary, but the output of the commands is important):

mkdir wg_config

Move into that folder:

cd wg_config

Set the permissions of the files we’re about to create to read, write, executable by the owner only:

umask 077

Create the public/private key pair for the server:

wg genkey | tee privatekey | wg pubkey > publickey

Check the contents of the key files:

cat privatekey && cat publickey

You should end up with something that looks like this:

NB: Nevermind that my privatekey and IP are in plaintext, all of these have been changed multiple times during the writing process, as have all the other details which might show up.

You can follow the same process for creating the public/private key pairs for the VPN client(s) as well. Depending on the platform, this step may not be necessary. We’ll look at creating an OS X client later as an example, where the client automatically generates the keys for us.

Once the keys have been created, the Wireguard interface configuration file can be created. With your favourite text editor:

sudo vim /etc/wireguard/wg0.conf

Copy paste the following into the file:

PrivateKey = <server private key>
Address =
ListenPort = 55000
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o ens3 -j MASQUERADE; ip6tables -A FORWARD -i wg0 -j ACCEPT; ip6tables -t nat -A POSTROUTING -o ens3 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o ens3 -j MASQUERADE; ip6tables -D FORWARD -i wg0 -j ACCEPT; ip6tables -t nat -D POSTROUTING -o ens3 -j MASQUERADE
SaveConfig = true

PublicKey = <client public key>
PresharedKey = <psk>
AllowedIPs =
Endpoint = <client_public_ip>:<high_port>

The bold items are the settings which can or should be modified to suit each implementation. Definitions:

PrivateKey = <server private key>: This will be the private key contained within the privatekey file created on the server earlier

Address = The address and address space of the Wireguard server within the VPN

ListenPort = 55000: The port on which the VPN will listen for incoming traffic. This can be any non-common port

PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o ens3 -j MASQUERADE; ip6tables -A FORWARD -i wg0 -j ACCEPT; ip6tables -t nat -A POSTROUTING -o ens3 -j MASQUERADE: an iptables rule allowing VPN traffic to flow via forwarding and NAT once a client/server connection has been established. ens3 is the interface name and may be different on other servers

PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o ens3 -j MASQUERADE; ip6tables -D FORWARD -i wg0 -j ACCEPT; ip6tables -t nat -D POSTROUTING -o ens3 -j MASQUERADE
SaveConfig = true:
removes the above iptables rule upon VPN connection tear-down. ens3 is the interface name and may be different on other servers

SaveConfig = true: Saves the config upon termination of a connection

PublicKey = <client public key>: The public key of the client which will connect to the VPN server. In order to add more clients, copy and paste the [Peer] section and modify the settings to match each client as needed

PresharedKey: Similar to a WPA2 PSK, this is a shared secret which adds another layer of security to VPN tunnels between two peers

AllowedIPs = The IP addresses which are allowed to send and receive traffic through the VPN

Endpoint: This is optional, and is updated automatically every time a peer checks in with the server from a different IP. In my testing, I found this to be a required option, without it my peers wouldn’t communicate

The following lines can also be added to force the VPN to drop any packets which come through unencrypted, or which don’t pass through the tunnel at all (i.e. packets transmitted on the non-Wiregaurd interfaces):

PostUp = iptables -I OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT 
PreDown = iptables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT

With the configuration file created, the correct permissions should be set (make root the owner):

sudo chown -v root:root /etc/wireguard/wg0.conf
sudo chmod -v 600 /etc/wireguard/wg0.conf

Resolvconf will also need to be installed for Pi-Hole to function correctly:

sudo apt install resolvconf -y

At this point, all of the server configuration should be complete, at least for this server and one peer. We can bring the wg0 interface up and make it persistent as follows:

sudo wg-quick up wg0
sudo systemctl enable wg-quick@wg0.service

IP forwarding needs to be enabled to allow traffic to pass through the tunnel:

sed -i 's/#net.ipv4.ip_forward=1/net.ipv4.ip_forward=1/g' /etc/sysctl.conf

After the above change is made, a reboot is required, unless the following commands are run:

sudo sysctl -p
sudo echo 1 > /proc/sys/net/ipv4/ip_forward

Assuming all of the above were successful, we can check that the interface has been created and is running, ready for clients to connect:

ifconfig wg0

That will do for the server configuration at this stage. Let’s move on to configuring the client(s).

Client Configuration

On the client, similar setup steps should be followed. Most likely you’ll download a client (OS X, Android, etc.), which will generate the client keys automatically. In the Wireguard client on OS X, we click the Add button to add a new tunnel:

This should present the following (note the public and private keys automatically generated):

Add the tunnel details as below:

PrivateKey = <client private key>
Address =

PublicKey = <server public key>
PresharedKey: <psk>
AllowedIPs =, ::/0
Endpoint = <server ip>:55000
PersistentKeepalive = 21

These settings mirror the configuration on the Wireguard server. The DNS server address should match the Wireguard server private VPN address in order to prevent DNS leaks. PresharedKey will match that supplied in the server configuration for this peer (PSK is a per-client setting). AllowedIPs allows for split tunnelling, setting this to means all traffic will be sent through the VPN. KeepAlive is how often the VPN should beacon in order to keep the connection alive, which is an optional setting and is mostly used for peers behind NAT.

Configure the Firewall

Although some of the firewall configuration is done during the standing-up and tearing-down of the VPN connection, some additional iptables assembly is required on the Linux server:

# track the VPN connection
sudo iptables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
sudo iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

# identify and allow traffic on the VPN listening port
sudo iptables -A INPUT -p udp -m udp --dport 55000 -m conntrack --ctstate NEW -j ACCEPT

# allow tcp/udp recursive DNS
sudo iptables -A INPUT -s -p tcp -m tcp --dport 53 -m conntrack --ctstate NEW -j ACCEPT
sudo iptables -A INPUT -s -p udp -m udp --dport 53 -m conntrack --ctstate NEW -j ACCEPT

# install persistent IP tables without the need for user interaction
echo iptables-persistent iptables-persistent/autosave_v4 boolean true | sudo debconf-set-selections
echo iptables-persistent iptables-persistent/autosave_v6 boolean true | sudo debconf-set-selections

# make the above rules persistent
sudo apt install iptables-persistent -y
sudo systemctl enable netfilter-persistent
sudo netfilter-persistent save

It’s now time to install and configure our Pi-Hole recursive DNS.

Pi-Hole Installation

Install Pi-Hole:

curl -sSL | bash

Click OK on the initial installation screen:

Consider donating to the project (it’s a really good cause), click OK:

Click OK on the static IP information page. Select the Wireguard interface (wg0 if you followed the instructions above to the letter), click OK:

Select the upstream DNS provider of your choice (I like Quad9 for their ability to block malicious activity and domains, but we’ll be using our own server, so this setting is moot). Click OK:

Select the block lists you want to use, I chose all available. Click OK:

Select IPv4, IPv6, or both, and click OK:

When asked for a static IP address, provide the IP address you assigned to this server inside the VPN. use this same IP as the default gateway, then click OK:

If you want to be able to administer the server via a web interface, select On, then click OK:

Install the web server (or don’t), and click OK:

Log queries, click OK:

FTL is an interface for accessing the Pi-Hole logs. If you want full information to be recorded, select Show Everything, then click OK:

When the installation is complete, save the administrator password and reboot. Pi-Hole is now mostly configured, but will need Unbound configured for DNSSEC and additional security.

Install and Configure Unbound DNS

Install Unbound:

sudo apt install unbound unbound-host -y

Download a list of root DNS servers:

curl -o /var/lib/unbound/root.hints

Delete any contents already existing in the /etc/unbound/unbound.conf.d/pi-hole.conf file and add the following:

# if no logfile is specified, syslog is used
# logfile: "/var/log/unbound/unbound.log"
verbosity: 1
port: 5353

do-ip4: yes
do-udp: yes
do-tcp: yes

# may be set to yes if you have IPv6 connectivity
do-ip6: no

# use this only when you downloaded the list of primary root servers
root-hints: "/var/lib/unbound/root.hints"

# respond to DNS requests on all interfaces
max-udp-size: 3072

# IPs authorised to access the DNS Server
access-control: refuse
access-control: allow
access-control: allow

# hide DNS Server info
hide-identity: yes
hide-version: yes

# limit DNS fraud and use DNSSEC
harden-glue: yes
harden-dnssec-stripped: yes
harden-referral-path: yes

# add an unwanted reply threshold to clean the cache and avoid, when possible, DNS poisoning
unwanted-reply-threshold: 10000000

# have the validator print validation failures to the log val-log-level: 1
# don't use Capitalisation randomisation as it known to cause DNSSEC issues sometimes
# see for further details
use-caps-for-id: no

# reduce EDNS reassembly buffer size
# suggested by the unbound man page to reduce fragmentation reassembly problems
edns-buffer-size: 1472

# TTL bounds for cache
cache-min-ttl: 3600
cache-max-ttl: 86400

# perform prefetching of close to expired message cache entries
# this only applies to domains that have been frequently queried
prefetch: yes
prefetch-key: yes
# one thread should be sufficient, can be increased on beefy machines
num-threads: 1
# ensure kernel buffer is large enough to not lose messages in traffic spikes
so-rcvbuf: 1m

# ensure privacy of local IP ranges
private-address: fd00::/8
private-address: fe80::/10


Once the VM has restarted, test to make sure the above configuration is working. The following ensures DNS is functioning:

dig @ -p 5353

This command should return SERVFAIL to indicate DNSSEC is working:

dig @ -p 5353

This command should return NOERROR, also indicating that DNSSEC is indeed working:

dig @ -p 5353

Configure Pi-Hole

Finally, we need to set up Pi-Hole to refer to it’s own recursive DNS server on the VM. Log into Pi-Hole using the admin password you saved earlier, at the address http://<server_ip>/admin. Then, click Settings on the left, and DNS at the top of the page:

Set the upstream DNS server to Make sure Pi-Hole is configured to only listen for requests on the Wireguard interface, otherwise you open up your server to being used for DNS amplification attacks and other problems.

Connect to the VPN

At this point, your server and client should be completely configured. Hence, a connection between the two can now be established and the VPN can be tested. Check your public IP first by using a site such as Then, once connected to the VPN, check it again. The new public IP should match that of your VPS.

That’s it. If you find a more succinct method (or have discovered any issues with these instructions), be sure to let me know. Otherwise, enjoy your new secure, ad-free browsing experience!

Pages to Test Ad Blocking Efficacy


6 thoughts on “Build Your Own Wireguard VPN Server with Pi-Hole for DNS Level Ad Blocking

  1. I followed your tutorial to the letter and get this:

    dig @ -p 5353

    ; <> DiG 9.11.3-1ubuntu1.1-Ubuntu <> @ -p 5353
    ;; global options: +cmd
    ;; connection timed out; no servers could be reached

  2. Hi, I have followed this guide, however, on installing pihole I get DNS Service is not running, and finally DNS resolution is not available. Any suggestions?

  3. This guide is excellent. Thank you.

    How can manage what traffic Wireguard routes? For instance, how would I limit the set up to only DNS traffic or expose a local subnet?

    e.g. To limit OpenVPN to only DNS traffic I would have only the following push directive in the server.conf:
    push “dhcp-option DNS “

  4. How can i add the firewall rule to the server ? I mean which code should be used to open the file where these codes should be added?

  5. A really good article

    Im using this solution (except for the dns part if pihole, which i use quad 9 dns for less configuration)

    But Sometimes when i connect a client to the vpn, i still see the public ip (with some checking IP websites, but not all) instead of the vps ip, i need to reconnect again (Sometimes 2 – 3 Times)

    I did the webrtc configuration, and wireguard is correctly routing trafic

    Do you know where it Comes from ?

Leave a Reply

Your email address will not be published. Required fields are marked *