Instructions for Installing Pi-hole + Wireguard VPN on a Home Network in 2025

Date: 2025-1-15

Last Edited: 2025-1-15

In this post I'm going to post some updated instructions for setting up a Pi-hole and Wireguard VPN server for a home network. Since last time I posted instructions here , Raspberry Pi has come out with their own operating system and so I wanted to write a new post.

First, here is the list of items I purchased:

Flashing the SD Card

Picture of the Pi Imager Screen

Although the SD card comes preloaded with Raspberry Pi OS, I always like to flash it myself just to make sure I know what software I'm running. To do this, I downloaded the Raspberry Pi Imager . I then selected the Pi Model 4, the 64 bit version of Raspberry Pi OS, and the SD card. Finally, I clicked Next, then Edit Settings and set up a password for the default pi user. Finally I clicked Yes to start writing the image.

After the image has been flashed you can insert the SD card into your Pi and mount it in the case. Note that there are no screws to secure the Pi board to the case, it does so simply by pressing hard so that the plastic posts snap into the pi board.

Then, you can plug the Raspberry Pi in and connect it to your network with an ethernet cable. You should then be able to see on your router's attached devices page what IP address it has been assigned. For the purposes of this guide I'll assume it's at 10.0.0.2.

Logging in for the First Time

You should now be able to SSH into your Pi. From either a Linux command line or from a Windows powershell:

$ ssh pi@10.0.0.2

Next, we should update all the software and install a text editor like vim:

$ sudo apt-get update
$ sudo apt-get upgrade
$ sudo apt-get install vim

Setting the Pi-hole to have a Static IP Address

First, we need to make sure that the DHCP server reserves this IP address for the Pi. To do so if you are using your router as your DHCP server you need to navigate to the router admin page and set up a static reservation for the MAC address of the pi.

Next, we can set the ip address in network manager to be static (here I assume we want an IP of 10.0.0.2 and our router's ip address is 10.0.0.1):

$ nmcli con show
$ nmcli con show "Wired connection 1" | tee original_network_settings.txt
$ sudo nmcli con mod "Wired connection 1" ipv4.method manual ipv4.addr 10.0.0.2/24
$ sudo nmcli con mod "Wired connection 1" ipv4.gateway 10.0.0.1 ipv4.dns 8.8.8.8
$ sudo nmcli device reapply eth0
$ sudo reboot

We reboot at the end to make sure everything worked and we can SSH back in.

Installing Pi-hole

After we SSH back in, we can install pihole with the following command:

$ curl -sSL https://install.pi-hole.net | bash

After following the instructions to set everything up you can change the admin password by running:

$ pihole -a -p

You should now be able to navigate to your pihole admin interface by going to 10.0.0.2/admin .

Picture of the Pi-hole Web Interface

Setting the Pi-hole to be the DNS (and optionally DHCP Server)

If you just want to set up your Raspberry Pi to be the DNS server you can navigate to your router's admin page and add your Raspberry Pi's IP address as the DNS server and skip the rest of this section. This setting is usually found in a section called LAN.

If you also want to set the Raspberry Pi to be a DHCP server continue reading this section. You might want to do this if you have a Netgear router for example which does not allow you to disable DNS proxy. Netgear routers will allow you to set the Pi-hole as the DNS server but do not pass that address on to connected clients. Instead, clients send their DNS queries to the Netgear router who proxies those requests to the Pi-hole. The disadvantage of this is that the Pi-hole sees all DNS requests coming from a single client and so you can't see information on a per client basis. If you have an Asus router for example, I would probably skip setting up the Raspberry Pi as a DHCP server since it already passes on the Pi IP address to DHCP clients.

To make the Raspberry Pi be your DHCP server you should first copy any static lease reservations from your router's page to the Pi. To set up static reservations you can click on Settings and then click on the DHCP tab from the Pi-hole homepage or go to http://10.0.0.2/admin/settings.php?tab=piholedhcp and go to the DHCP tab. At the bottom you can enter MAC address and IP pairs to reserve them. If you don't know what a static lease reservation is you can just skip this since you probably don't have any set up.

Finally make sure no one is using the internet since this next step may cause some havoc. You now need to go to your router's settings page and disable DHCP. Then, navigate to the pi DHCP page at http://10.0.0.2/admin/settings.php?tab=piholedhcp and check the button "DHCP server enabled". I like to set the range of IP addresses it hands out to be from 10.0.0.100 to 10.0.0.254, and keep all the addresses under 100 to be static. You also need to set your router IP address.

Picture of the Pi-hole DHCP Settings

Now, you should reboot all your devices on the network and they should get their DHCP IP address from the Pi. The way this works without setting anything on the router is that as devices connect to the network they broadcast a DHCPDISCOVER message to the IP address 255.255.255.255 or 10.0.0.255 which gets delivered to all clients on the network. The Pi, since you have enabled the DHCP server responds and assigns the client an IP address.

ICloud Private Relay

Many Apple devices use a feature called ICloud Private Relay which routes internet and DNS traffic through Apple's relays before going to your device. If you set up the Pi-hole, then your Apple devices will start complaining about not being able to connect to this service and you have a choice to make. You can either tell Apple on your device that you don't care, or you can disable the Pi-hole from blocking these services. To do the latter you need to edit the file /etc/pihole/pihole-FTL.conf as suggested here .

Domain Management

Picture of the Pi-hole Domain Settings Page

From the admin web interface you can blacklist and whitelist domains. For example, I have the following two domains whitelisted:

in order for Youtube to remember which videos I've watched.

Wireguard

Note: most of these instructions came from this guide , and I just slightly modified them. So if you run into any issues, check out that post and see if it helps.

To install wireguard:

$ sudo apt-get install wireguard

Before moving on, it's a good idea to read the conceptual overview to roughly understand how wireguard works. Next we create the public/private keypair for our server. Note we will do all of this as root, so the first command is to switch to root.

$ sudo su -
# wg genkey | tee server_privatekey | wg pubkey > server_publickey
# chmod go= server_privatekey server_publickey
# wg genkey | tee client_privatekey | wg pubkey > client_publickey
# chmod go= client_privatekey client_publickey

Next, we will create the wireguard server configuration file. Open a text editor and create the file wg0.conf:

$ vim wg0.conf

and add to it:

[interface]
PrivateKey = [server private key goes here]
Address = 10.8.0.1/24
ListenPort = 51820
PostUp = iptables -I INPUT -i wg0 -j ACCEPT
PostUp = iptables -t nat -I POSTROUTING -o eth0 -j MASQUERADE
PreDown = iptables -D INPUT -i wg0 -j ACCEPT
PreDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

[Peer]
PublicKey = [client public key goes here]
AllowedIPs = 10.8.0.2/32

The private key line just specifies the private key that is used to decrypt incoming packets on this interface. The address specifies the internal IP address wireguard is going to use for this server. You can use any of the IP addresses specified in RFC 1918 . The listen port determines what port wireguard is going to listen on for incoming UDP packets. The final rules tell wireguard to update the iptable rules when it starts up.

For the [peer] section the public key tells Wireguard how to encrypt the packets sent back to the client and the list of allowed ips tells wireguard two things:

Now, we need to create the client's wireguard configuration file. This can be done on the laptop you want to connect it to or if you want to connect from your Android phone you can just create it on the raspberry pi and use a tool to create a QR code of the settings to transfer it to your phone. In any case, create a file called client.conf:

[Interface]
Address = 10.8.0.2/24
PrivateKey = [client private key]
# IP address of Pihole
DNS = 10.0.0.2

[Peer]
PublicKey = [server public key goes here]
# This is your public IP address. From your home network you can figure this
# out by visiting whatsmyip.org
Endpoint = [your public ip address]:51820
# Here we can set the allowed ips to 0.0.0.0/0 to route *all* traffic through
# the pihole, or 10.0.0.0/24 if you only want traffic directed to devices on
# your local LAN to go through the pihole.
AllowedIPs = 0.0.0.0/0

Now, to transfer this to the wireguard phone app for example, you can run:

$ apt-get install qrencode
$ qrencode -t ansiutf8 -r client.conf

We can now copy these files to /etc/wireguard/ and set up the wireguard service to start automatically.

$ cp wg0.conf /etc/wireguard/
$ apt-get install iptables
$ sudo systemctl enable wg-quick@wg0.service
$ sudo systemctl start wg-quick@wg0.service

Now you can try to connect from your device. If you do so you can see whether you connected by running:

$ wg show

on your Raspberry Pi and you should see a line called "latest handshake" that appears.

Port forwarding from router

Now, you need to log into your router and update your port forwarding rules to make sure that all UDP packets are forwarded from the router on port 51820 to the pi at 10.0.0.2.

We need to enable port forwarding between interfaces if you want to use the VPN from outside your local network. To do this, uncomment the following line from /etc/sysctl.conf:

net.ipv4.ip_forward=1

and then run:

# sysctl -p

IPTables Rules

By default, the Raspberry Pi OS doesn't have restrictive IPTables rules, so I like to add some.

$ apt-get install iptables-persistent

and just select all the defaults. Then we create two files rules.v4 and rules.v6 . Here is rules.v4:

*filter
:INPUT DROP [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
# Accept any packets which are associated with a connection
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# Accept new udp packets on port 51820 for the Wireguard VPN
-A INPUT -p udp --dport 51820 -j ACCEPT
# Accept new tcp packets on port 80 for the web interface on the local lan
-A INPUT -s 10.0.0.0/24 -p tcp --dport 80 -m conntrack --ctstate NEW -j ACCEPT
# Accept new tcp packets on port 22 for SSH on the local lan
-A INPUT -s 10.0.0.0/24 -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT
# Accept packets on port 53 for DNS on the local lan
-A INPUT -s 10.0.0.0/24 -p udp --dport 53 -j ACCEPT
-A INPUT -s 10.0.0.0/24 -p tcp --dport 53 -j ACCEPT
# Accept broadcast packets on port 67 for DHCP
-A INPUT -s 0.0.0.0 -p udp --dport 67 -j ACCEPT
# Accept anything from localhost
-A INPUT -i lo -j ACCEPT
COMMIT
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
COMMIT

And, here is rules.v6:

*filter
:INPUT DROP [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
# Accept any packets which are associated with a connection
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# Accept anything from localhost
-A INPUT -i lo -j ACCEPT
-A INPUT -p ipv6-icmp -j ACCEPT
COMMIT
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
COMMIT

Next, we copy these to /etc/iptables/ and load them. Note that we stop the wireguard service first to avoid it complaining when we modify the iptables rules underneath it.

# cp rules.v{4,6} /etc/iptables/
# systemctl stop wg-quick@wg0.service
# iptables-restore /etc/iptables/rules.v4
# ip6tables-restore /etc/iptables/rules.v6
# systemctl start wg-quick@wg0.service