Running pihole on a VPS securely2022-09-18
I've been running pihole on my NAS for a while. My router is configured to use the NAS as its primary DNS, meaning all the devices on my network get configured to use the pihole resolver. Running it on my NAS server is not ideal though, because when I do maintenance on the NAS, all the devices on the network start having issues because the DNS resolver is down. I've tried providing a secondary DNS resolver to my router for cases like this, but it ends up making pihole useless because ads are able to get through on the devices even when the primary resolver is up.
So I decided to try to host pihole on an inexpensive VPS. The only issue is that it's not recommended to host a publicly accessible DNS resolver. It can be used in DNS amplification attacks. The plan to avoid this issue is to use a firewall which only allows IPs, and IP ranges I have vetted to access pihole. My home IP address is for the most part static, it hasn't changed it years as far as I can tell. This might not be a good solution if your home IP address changes often.
My VPS is running Debian 11 with a couple of services like my website, my blog, Gitea, and others. It already has
docker installed on it. I installed pihole using a systemd unit file which simply spawns
# /etc/systemd/system/pihole.service [Unit] After=docker.service Description=Pihole [Service] Type=simple Restart=always ExecStart=/usr/bin/docker run --rm --name=pihole \ -p 53:53/tcp -p 53:53/udp \ -p 50010:80 \ -v "/root/pihole/etc-pihole:/etc/pihole" \ -v "/root/pihole/etc-dnsmasq.d:/etc/dnsmasq.d" \ --dns 188.8.131.52 --dns=184.108.40.206 \ --hostname pihole.sbstp.ca \ -e TZ="America/Toronto" \ -e WEBPASSWORD="<secret>" \ -e CORS_HOSTS="pihole.sbstp.ca" \ -e PIHOLE_DNS_="220.127.116.11;18.104.22.168" \ docker.io/pihole/pihole:latest [Install] WantedBy=docker.service
systemctl enable pihole.service and
systemctl start pihole.service. Port 50010 is for the pihole WebUI,
nginx server has a proxy pass to it when the hostname
pihole.sbstp.ca is used.
The next step is to secure access to the resolver. I decided to use
apt-get install ufw) for this task, but any
firewall would do. Some VPS providers have built-in firewalls that can be used. Still, I prefer to control everything at
the OS level.
ufw is a frontend for iptables and it simplifies things a lot. It's a nice project.
There is one issue though,
docker both play with iptable rules, and create imcompatible rules. Luckily
though, there's a project called
ufw-docker which has instructions on how
to setup ufw properly.
The TL;DR is that you append the text below to
/etc/ufw/after.rules, which makes
ufw get along.
# BEGIN UFW AND DOCKER *filter :ufw-user-forward - [0:0] :ufw-docker-logging-deny - [0:0] :DOCKER-USER - [0:0] -A DOCKER-USER -j ufw-user-forward -A DOCKER-USER -j RETURN -s 10.0.0.0/8 -A DOCKER-USER -j RETURN -s 172.16.0.0/12 -A DOCKER-USER -j RETURN -s 192.168.0.0/16 -A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN -A DOCKER-USER -j ufw-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16 -A DOCKER-USER -j ufw-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8 -A DOCKER-USER -j ufw-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12 -A DOCKER-USER -j ufw-docker-logging-deny -p udp -m udp --dport 0:32767 -d 192.168.0.0/16 -A DOCKER-USER -j ufw-docker-logging-deny -p udp -m udp --dport 0:32767 -d 10.0.0.0/8 -A DOCKER-USER -j ufw-docker-logging-deny -p udp -m udp --dport 0:32767 -d 172.16.0.0/12 -A DOCKER-USER -j RETURN -A ufw-docker-logging-deny -m limit --limit 3/min --limit-burst 10 -j LOG --log-prefix "[UFW DOCKER BLOCK] " -A ufw-docker-logging-deny -j DROP COMMIT # END UFW AND DOCKER
After this you should restart
systemctl restart ufw.
Now you can setup
- Allow ssh
ufw allow ssh(shortcut for
- Allow other ports such as 80, 443, etc.
ufw allow 443/tcp.
- Allow udp packets coming from your home IP to be forwarded to the docker container
ufw route allow proto udp from <HOME_IP> to any port 53. Note that this is using the internal docker port, not the external one. See
ufw-dockerfor more details.
- Check status,
You can also allow friends & family to access the resolver by running the
ufw route allow proto udp from <HOME_IP> to any port 53 command again and changing the IP to theirs.
You can test that everything works well by using
apt-get install bind9-utils).
dig @<VPS IP> google.com
resolves google.com using your DNS resolver. Make sure to run the
dig command from within the network you want to run
Now that all that setup is done, you can set the VPS' IP to be the only DNS resolver in your router, and that should spread to all your devices in a few hours.