A CentOS VPS with Wireguard and PiHole

header_img I’ve evangilized how great I think both Pi-hole and Wireguard are in a previous post of mine, where I explained how useful a combination of the two can be as a somehwat artisinal Virtual Private Server. I personally like this setup because it gives you full access/permissions to a VPN service (at a low cost), something impossible to get with most managed VPN providers out there, while also an extremely easy and agreeable client setup.

Getting CentOS 7 set up as VPS host

Why have I decided to go with CentOS as the host for this? The main factor here is stability. I want a system that I’m not going to have to check for package updates every day, and that I can trust to not break when I do need to upgrade something. I don’t need “bleeding edge”.

I’m also personally very adjusted to doing admin on CentOS, like many other fulltime computer gazers out there. :stuck_out_tongue_winking_eye:

Using Linode, DigitalOcean, etc (choose your provider) its pretty simple to get a CentOS VM set up. For our VPS here, you will most likely want to chose the cheapest plan, as we only need minimal resources. I am currently running a CentOS VM with ~30GB disk space, 1GB RAM, and ~1GBPS network.

Once you’ve got CentOS set up on a VM, remember first to run yum upgrade in order to make sure all packages included are updated to their latest version.

Installing Wireguard on the VM and setting up server configs

The next step is to install Wireguard on CentOS. At the time of writing this, Wireguard is not yet included in one of the mainline repositories for CentOS, so we will need to add their official repo manually first:

[root@vps ~]# curl -Lo /etc/yum.repos.d/wireguard.repo https://copr.fedorainfracloud.org/coprs/jdoss/wireguard/repo/epel-7/jdoss-wireguard-epel-7.repo
[root@vps ~]# yum install epel-release
[root@vps ~]# yum install wireguard-dkms wireguard-tools

Once the packages have finished downloading and installing, we need to create a directory for the Wireguard config files and generate some keys:

[root@vps ~]# mkdir /etc/wireguard
[root@vps ~]# wg set wg0-server listen-port 90210 private-key <(wg genkey)
[root@vps ~]# cd /etc/wireguard
[root@vps ~]# umask 077
[root@vps ~]# wg genkey | tee server-privatekey | wg pubkey > server-publickey #generate server-side keys

Let’s also create the Wireguard server configuration file and adjust it accordingly:

[root@vps ~]# nano /etc/wireguard/wg0.conf

ListenPort = 90210
SaveConfig = false
Address =

Testing the server configuration

Okay, we’ve got the server config set up to where we need it so far. Let’s bring up the Wireguard wg0 interface with the application’s “quick” tool now to see if things will load properly:

[root@vps ~]# wg-quick up wg0
[#] ip link add wg0 type wireguard
[#] wg setconf wg0 /dev/fd/xxx
[#] ip -4 address add dev wg0
[#] ip link set mtu 1420 up dev wg0

Looks like its up! Let’s check the output of wg now as well:

[root@vps ~]# wg
interface: wg0
  public key: ~YOUR_SERVER_PUBLIC_KEY~
  private key: (hidden)
  listening port: 90210

If things look very different than this, we might need to make sure our Wireguard install didn’t go haywire or that we didn’t make any mistakes with the key generation process. If wg-quick up went without errors, we are likely good and can down the service for now:

[root@vps ~]# wg-quick down wg0
[#] ip link delete dev wg0

Installing and configuring for mobile clients

One of the things I absolutely love about Wireguard is their mobile app. No more finicking with OpenVPN configurations! The Wireguard development team has created apps for both Android and iOS systems. The configuration process for a mobile phone as a client to our currently existing server should be relatively the same for both platforms, so let’s set this tunnel config up step by step here for posterity:

Finishing server configuration with new mobile client config

There, we have things set up for the tunnel on our mobile config. Now we can copy the public (“interface”) key from our newly made config on there and paste it to wg0.conf on the server:

[root@vps ~]# nano /etc/wireguard/wg0.conf

ListenPort = 90210
SaveConfig = false
Address =

AllowedIPs =

Looks nice - let’s save the file and restart the wireguard service now with wg-quick up wg0. You might want to set Wireguard up so that it starts automatically with systemd after reboot. If this is what you want, let’s do the following:

[root@vps ~]# systemctl enable wg-quick@wg0
Created symlink from /etc/systemd/system/multi-user.target.wants/wg-quick@wg0.service to /usr/lib/systemd/system/wg-quick@.service.

Traffic forwarding and routing configurations

Now we need to make sure that the traffic from Wireguard devices to this VM is re-routed to the proper interface. Let’s run ip addr show to ensure that eth0 is up and the proper name of the active interface connection. Let’s then pop open wg0.conf again and append the following iptables rules for proper traffic routing:

[root@vps ~]# nano /etc/wireguard/wg0.conf

ListenPort = 90210
SaveConfig = false
Address =
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

Let’s also set ipv4 forwarding at kernel-level. We’ll set this so that it remains persistent for the system even after reboot:

[root@vps ~]# tee -a /etc/sysctl.d/99-sysctl.conf
sysctl -w net.ipv4.ip_forward=1
sysctl -w net.ipv6.conf.all.forwarding=1

At this point you can either reboot the VM or run sysctl -p to apply these changes.

Testing mobile-to-server connection

Finally, time to test from our phone. Let’s open up the Wireguard app and toggle on the connection we created earlier. Once toggled, let’s go back to our VM shell and run the following command to check the status of the wg0 interface:

[root@vps ~]# wg
interface: wg0
  public key: ~YOUR_SERVER_PUBLIC_KEY~
  private key: (hidden)
  listening port: 90210

  endpoint: ~IP~:~PORT~
  allowed ips:
  latest handshake: 3 seconds ago
  transfer: 1.43 KiB received, 92 B sent

Awesome, we can see a handshake as well as packets being received! As long as we see things going through this way, and we ensure that traffic is properly tunneling to the VM via Wireguard, we can now begin to start setting up the VM for a Pi-hole installation.

Preparing CentOS for Pi-hole installation

Before we go and install Pi-hole to our CentOS host, let’s first take note of some of our network details - particularly the IP for the wg0 interface:

[root@vps ~]# ip a show dev wg0
4: wg0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000
    inet scope global wg0
       valid_lft forever preferred_lft forever
    inet6 ~REDACTED~/64 scope link flags 800
       valid_lft forever preferred_lft forever

Let’s remember this output portion. In CentOS we also need to ensure that there is a proper network configuration file for the Wireguard interface created in the /etc/sysconfig/network-scripts/ directory, in order for the Pi-hole installer to properly configure on this particular distro. Let’s just create a blank network configuration file in that directory for the installer to write to:

[root@vps ~]# touch /etc/sysconfig/network-scripts/ifcfg-wg0

Another slightly CentOS-specific thing we need to take into consideration is working with SElinux, which comes installed by default on the OS. According to Pi-hole developers, SElinux currently causes some issues with parts of Pi-hole. This means that having it in its default “enforcing” mode will cause issues here. This does not mean that we should completely disable SElinux - in fact, this is almost always an improper approach to torubleshooting issues with SElinux.

Instead, let’s set the mode on SElinux here from “enforcing” to “permissive”. This will not actively block things flagged up in your local SElinux policy, but instead will still log any instances of policy violations:

[root@vps ~]# cd /etc/sysconfig/
[root@vps ~]# sudo nano selinux
    # This file controls the state of SELinux on the system.
    # SELINUX= can take one of these three values:
    #     enforcing - SELinux security policy is enforced.
    #     permissive - SELinux prints warnings instead of enforcing.
    #     disabled - No SELinux policy is loaded.
    # SELINUXTYPE= can take one of three values:
    #     targeted - Targeted processes are protected,
    #     minimum - Modification of targeted policy. Only selected processes are protected.
    #     mls - Multi Level Security protection.

Let’s save that file and then reboot in order to allow the changes to go through. Once we’ve started back up we can check the SElinux status:

[root@vps ~]# sestatus
SELinux status:                 enabled
SELinuxfs mount:                /sys/fs/selinux
SELinux root directory:         /etc/selinux
Loaded policy name:             targeted
Current mode:                   permissive
Mode from config file:          permissive
Policy MLS status:              enabled
Policy deny_unknown status:     allowed
Max kernel policy version:      31

Looks set now. I encourage people to take a look at these logs from time to time. The live in /var/log/audit/audit.log on CentOS, so you can simply run cat /var/log/audit/audit.log | grep selinux at your convenience.

During the Pi-hole install, we will also need to provide wg0 as an interface name including your default gateway IP address such as 192.168.x.x - this will be different on every server, so let’s get our’s now by checking ip r | grep default and keep the output handy. After saving these details, its time to actually start installing Pi-hole.

Installing Pi-hole

If you don’t have wget yet, please install it now as we will need it for getting the installer:

[root@vps ~]# wget -O basic-install.sh https://install.pi-hole.net
[root@vps ~]# bash basic-install.sh

Let’s follow these steps through the installation process:

When the installation is done, we’re just about set! Let’s make sure that things look normal by checking DNS results from a known advertising domain that is on one of the default blocklists after Pi-hole install:

[root@vps ~]# host track.adtrue.com
Using domain server:

track.adtrue.com has address
track.adtrue.com has IPv6 address ::

Looks like we’re blocking it successfully, based on the 0’d address there. Let’s also check out Pi-hole log from the VM while we browse from our mobile, so that we can see that things are coming through properly and also being affectively blocked:

[root@vps ~]# pihole -t
  [i] Press Ctrl-C to exit
21:10:34 dnsmasq[]: query[A] settings.crashlytics.com from
21:10:34 dnsmasq[]: /etc/pihole/gravity.list settings.crashlytics.com is
21:10:35 dnsmasq[]: query[A] analytics.twitter.com from
21:10:35 dnsmasq[]: /etc/pihole/gravity.list analytics.twitter.com is
21:10:35 dnsmasq[]: query[A] e.crashlytics.com from
21:10:35 dnsmasq[]: /etc/pihole/gravity.list e.crashlytics.com is
21:10:37 dnsmasq[]: query[A] reports.crashlytics.com from
21:10:37 dnsmasq[]: /etc/pihole/gravity.list reports.crashlytics.com is
21:10:51 dnsmasq[]: query[A] e.crashlytics.com from
21:10:51 dnsmasq[]: /etc/pihole/gravity.list e.crashlytics.com is
21:10:58 dnsmasq[]: query[A] reports.crashlytics.com from
21:10:58 dnsmasq[]: /etc/pihole/gravity.list reports.crashlytics.com is
21:11:06 dnsmasq[]: query[A] e.crashlytics.com from
21:11:06 dnsmasq[]: /etc/pihole/gravity.list e.crashlytics.com is
21:11:20 dnsmasq[]: query[A] reports.crashlytics.com from
21:11:20 dnsmasq[]: /etc/pihole/gravity.list reports.crashlytics.com is
21:11:33 dnsmasq[]: query[A] e.crashlytics.com from
21:11:33 dnsmasq[]: /etc/pihole/gravity.list e.crashlytics.com is
21:11:35 dnsmasq[]: query[A] connectivitycheck.gstatic.com from

This output has been slightly edited here for privacy’s sake, but you get the point. You might be surprised how often some of your mobile apps phone to ad domains (or you might not be surprised..).

We can check the Pi-hole CLI dashboard for more high-level infomration with the pihole -c command on the VM.

Adding Pi-hole blocklists

At this point you can feel free to add any community-built ad/tracking blocklists to your Pi-hole config. The easiest way to do this from the VM is by adding each URL as a line to the /etc/pihole/adlists.list file and then updating Gravity with pihole -g.

I recommend seaching for lists you want on sites like FilterLists. Feel free to add my list as well, if you’d like.

Final considerations

So now you have your own personal VPS to tunnel into on the road, with DNS-level ad and tracker blocking as well. Neat! However, as quick painless as this has been, please do take the following into consideration as you continue to use this setup:

Happy browsing!