Skip to content

Firewall Configuration

(optional) Secure the server with firewall rules (iptables)

If you are behind a NAT and not running the Pi-hole on a cloud server, you do not need to issue the IPTABLES commands below as the firewall rules are already handled by the RoadWarrior installer, but you will need to portforward whatever port you chose in the setup from your public ip to your device using your router.

This step is optional but recommended if you are running your server in the cloud, such as a droplet made on Digital Ocean. If this is the case, you need to secure the server for your safety as well as others to prevent aiding in DDoS attacks.

In addition to the risk of being an open resolver, your Web interface is also open to the world increasing the risk. So you will want to prevent ports 53 and 80, respectively, from being accessible from the public Internet.

It's recommended that you clear out your entire firewall so you have full control over its setup. You have two options for setting up your firewall with your VPN.

Option 1: Allow everything from within your VPN

Enter this command, which will allow all traffic through the VPN tun0 interface.

iptables -I INPUT -i tun0 -j ACCEPT

Option 2: Explicitly allow what can be accessed within the VPN

These commands will allow DNS and HTTP needed for name resolution (using Pi-hole as a resolver) and accessing the Web interface, respectively.

iptables -A INPUT -i tun0 -p tcp --destination-port 53 -j ACCEPT
iptables -A INPUT -i tun0 -p udp --destination-port 53 -j ACCEPT
iptables -A INPUT -i tun0 -p tcp --destination-port 80 -j ACCEPT

You will also want to enable SSH and VPN access from anywhere.

iptables -A INPUT -p tcp --destination-port 22 -j ACCEPT
iptables -A INPUT -p tcp --destination-port 1194 -j ACCEPT
iptables -A INPUT -p udp --destination-port 1194 -j ACCEPT

The next crucial setting is to explicitly allow TCP/IP to do "three-way handshakes":

iptables -I INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT

Also, we want to allow any loopback traffic, i.e. the server is allowed to talk to itself without any limitations using 127.0.0.0/8:

iptables -I INPUT -i lo -j ACCEPT

Finally, reject access from anywhere else (i.e. if no rule has matched up to this point):

iptables -P INPUT DROP
Blocking HTTPS advertisement assets

Since you're :head-desk:ing with iptables, you can also use this opportunity to block HTTPS advertisements to improve blocking ads that are loaded via HTTPS and also deal with QUIC.

Why doesn't Pi-hole just use a certificate to prevent this? The answer is here.

iptables -A INPUT -p udp --dport 80 -j REJECT --reject-with icmp-port-unreachable
iptables -A INPUT -p tcp --dport 443 -j REJECT --reject-with tcp-reset
iptables -A INPUT -p udp --dport 443 -j REJECT --reject-with icmp-port-unreachable

Depending on the systems you have connecting, you may benefit from appending --reject-with tcp-reset to the command above. If you still get slow load times of HTTPS assets, the above may help.

IPv6 iptables

If your server is reachable via IPv6, you'll need to run the same commands but using ip6tables:

ip6tables -A INPUT -i tun0 -p tcp --destination-port 53 -j ACCEPT
ip6tables -A INPUT -i tun0 -p udp --destination-port 53 -j ACCEPT
ip6tables -A INPUT -i tun0 -p tcp --destination-port 80 -j ACCEPT
ip6tables -A INPUT -p tcp --destination-port 22 -j ACCEPT
ip6tables -A INPUT -p tcp --destination-port 1194 -j ACCEPT
ip6tables -A INPUT -p udp --destination-port 1194 -j ACCEPT
ip6tables -I INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
ip6tables -I INPUT -i lo -j ACCEPT
ip6tables -A INPUT -p udp --dport 80 -j REJECT --reject-with icmp6-port-unreachable
ip6tables -A INPUT -p tcp --dport 443 -j REJECT --reject-with tcp-reset
ip6tables -A INPUT -p udp --dport 443 -j REJECT --reject-with icmp6-port-unreachable
ip6tables -P INPUT DROP

View the rules you just created

iptables -L --line-numbers

and they should look something like this:

Chain INPUT (policy DROP)
num  target     prot opt source               destination
1    ACCEPT     all  --  anywhere             anywhere
2    ACCEPT     all  --  anywhere             anywhere             state RELATED,ESTABLISHED
3    ACCEPT     all  --  anywhere             anywhere
4    ACCEPT     tcp  --  anywhere             anywhere             tcp dpt:domain
5    ACCEPT     udp  --  anywhere             anywhere             udp dpt:domain
6    ACCEPT     tcp  --  anywhere             anywhere             tcp dpt:http
7    ACCEPT     udp  --  anywhere             anywhere             udp dpt:80
8    ACCEPT     tcp  --  anywhere             anywhere             tcp dpt:ssh
9    ACCEPT     tcp  --  anywhere             anywhere             tcp dpt:openvpn
10   ACCEPT     udp  --  anywhere             anywhere             udp dpt:openvpn
11   ACCEPT     tcp  --  10.8.0.0/24          anywhere             tcp dpt:domain
12   ACCEPT     udp  --  10.8.0.0/24          anywhere             udp dpt:domain
13   ACCEPT     tcp  --  10.8.0.0/24          anywhere             tcp dpt:http
14   ACCEPT     udp  --  10.8.0.0/24          anywhere             udp dpt:80
15   ACCEPT     tcp  --  10.8.0.0/24          anywhere             tcp dpt:domain
16   ACCEPT     tcp  --  10.8.0.0/24          anywhere             tcp dpt:http
17   ACCEPT     udp  --  10.8.0.0/24          anywhere             udp dpt:domain
18   ACCEPT     udp  --  10.8.0.0/24          anywhere             udp dpt:80
19   REJECT     tcp  --  anywhere             anywhere             tcp dpt:https reject-with icmp-port-unreachable

Chain FORWARD (policy ACCEPT)
num  target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
num  target     prot opt source               destination

Similarly, ip6tables -L --line-numbers should look like this:

Chain INPUT (policy DROP)
num  target     prot opt source               destination
1    ACCEPT     all      anywhere             anywhere
2    ACCEPT     all      anywhere             anywhere             state RELATED,ESTABLISHED
3    ACCEPT     tcp      anywhere             anywhere             tcp dpt:domain
4    ACCEPT     udp      anywhere             anywhere             udp dpt:domain
5    ACCEPT     tcp      anywhere             anywhere             tcp dpt:http
6    ACCEPT     udp      anywhere             anywhere             udp dpt:80
7    ACCEPT     tcp      anywhere             anywhere             tcp dpt:ssh
8    ACCEPT     tcp      anywhere             anywhere             tcp dpt:openvpn
9    ACCEPT     udp      anywhere             anywhere             udp dpt:openvpn
10   REJECT     tcp      anywhere             anywhere             tcp dpt:https reject-with icmp6-port-unreachable

Chain FORWARD (policy ACCEPT)
num  target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
num  target     prot opt source               destination
Verify the rules are working

Connect to the VPN as a client and verify you can resolve DNS names as well as access the Pi-hole Web interface. These settings are stored in memory until you save them. If it's not working, you can restart your server to start from scratch. Alternatively, you could also go through and delete lines with iptables -D INPUT <SOME LINE NUMBER>

Save your iptables

If things look good, you may want to save your rules so you can revert to them if you ever make changes to the firewall. Save them with these commands:

iptables-save > /etc/pihole/rules.v4
ip6tables-save > /etc/pihole/rules.v6

Similarly, you can restore these rules:

iptables-restore < /etc/pihole/rules.v4
ip6tables-restore < /etc/pihole/rules.v6