Hello,
I have set up Pi-hole as a container and it runs fine.
Now I am trying to set up Unbound, which gives me a little headache. I have set a veth2 for it with address 172.17.0.3 (Pi-hole is on veth1 at address 172.17.0.2) and gateway 172.17.0.1. I have forwarded Pi-hole and Unbound that way
22 ;;; pihole
chain=dstnat action=dst-nat to-addresses=172.17.0.2 to-ports=80 protocol=tcp dst-address=192.168.88.1 dst-port=8080 log=no log-prefix=""
23 ;;; pihole
chain=dstnat action=dst-nat to-addresses=172.17.0.2 to-ports=80 protocol=tcp dst-address=192.168.1.1 dst-port=8080 log=no log-prefix=""
24 ;;; pihole
chain=dstnat action=dst-nat to-addresses=172.17.0.2 to-ports=53 protocol=udp dst-address=192.168.1.1 dst-port=53 log=no log-prefix=""
25 ;;; unbound
chain=dstnat action=dst-nat to-addresses=172.17.0.3 to-ports=53 protocol=udp dst-address=192.168.1.1 dst-port=5053 log=no log-prefix=""
26 ;;; unbound
chain=dstnat action=dst-nat to-addresses=172.17.0.3 to-ports=53 protocol=tcp dst-address=192.168.1.1 dst-port=5053 log=no log-prefix=""
Unbound container starts with no problem. But when I try to set the DNS of Pi-hole to 192.168.1.1#5053, it doesn’t work.
I have supposed that I had problems of conf and root.hints files for Unbound, so I sftp copied those files from a previous install, stopped the container and started it again, with no success. The log of Unbound is as follows
2026-01-07 19:23:12 unbound *** start
2026-01-07 19:23:12 unbound *** started PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin TZ=Europe/Paris unbound
2026-01-07 19:23:12 unbound [1767813792] unbound[1:0] notice: init module 0: validator
2026-01-07 19:23:12 unbound [1767813792] unbound[1:0] notice: init module 1: iterator
2026-01-07 19:23:12 unbound [1767813792] unbound[1:0] info: start of service (unbound 1.24.2).
Any idea how I could troubleshoot that ?
EDIT : this is my command to create the container
/container/add remote-image=klutchell/unbound:latest interface=veth2 root-dir=containers/images/unbound mounts=MOUNT_UNBOUND_ETC,MOUNT_UNBOUND_LIB envlist=ENV_UNBOUND name=unbound
If both the VETHs of pihole and unbound are on the same bridge, then what you are seeing is a typical situation that normally requires Hairpin-NAT, namely:
pihole and unbound are in the same subnet and you tell pihole to contact 192.168.1.1 at port 5053, which is DSTNATed back to 172.17.0.3, which is in the same subnet as pihole (172.17.0.2).
pihole sends packets with source 172.17.0.2 to destination 192.168.1.1 port 5053.
- Router changes the destination from
192.168.1.1:5053 to 172.17.0.3:53 and forwards to unbound.
- When that is being done, the source IP of the packet is still
172.17.0.2. So, when unbound responds, it sees that the response should be sent to 172.17.0.2, which is in its subnet. unbound will send the response directly on-link (on layer 2), bypassing the router.
pihole receives the response packet, but the packet has source 172.17.0.3, not the expected 192.168.1.1. It won't accept that.
Normally you add a Hairpin-NAT rule to workaround this. But in this case, a much simpler fix is to change the pihole configuration so that it uses 172.17.0.3#53 in the setting instead of 192.168.1.1#5053. It will work and no Hairpin-NAT rule is needed. And the router doesn't need to be the intermediate gateway for this communication between pihole and unbound, improving the performance too.
BTW I preferer to use alpinelinux/unbound - Docker Image. The images are smaller and it's actually Alpine with the official unbound APK package from Alpine pre-installed. So, you can shell into it and add additional Alpine packages without fear of compatibility issues, and you can even update everything (including unbound) normally with apk version --no-cache / apk upgrade --no-cache to keep the whole Alpine installation up-to-date instead of doing a repull of the container. Just edit /etc/apk/repositories to:
https://dl-cdn.alpinelinux.org/alpine/latest-stable/main
https://dl-cdn.alpinelinux.org/alpine/latest-stable/community
2 Likes
I didn’t mention: I have set up a hairpin-NAT rule, just as described in the official tutorial. I have no access to my router right now but I will post the rule this evening (time of Paris).
So, here is my hairpin-NAT rule
21 ;;; pihole
chain=srcnat action=masquerade src-address=172.17.0.0/24 log=no log-prefix=""
What seems most surprising to me is that with this dns setup of pi-hole
I get these statistics of upstream servers
Which means that a certain amount of dns questions get resolutions with unbound. But not all of them, and noticeably not those coming from my laptop.
This evening it seems to work. I will control if it is stable.