This is a baseline setup of Pi-hole and Unbound using Docker. It assumes that you already have a gateway/router with a separate DHCP and NTP server. If you want Pi-hole to handle DHCP, additional configuration is needed.
This setup follows the official Pi-hole Unbound guide but adapts it for Pihole v6 and Docker Compose.
In this setup, Unbound does not have its own network interface; instead, it runs using Pi-hole’s network stack (network_mode: service:pihole
). This means:
- Unbound is not exposed to the host network but can still resolve recursive DNS queries.
- Pi-hole forwards all upstream DNS queries to
127.0.0.1#5335
, where Unbound handles recursive lookups. - No additional networking configurations are needed for Unbound.
Before you begin, ensure you are running:
- A Debian or Debian-based Linux distribution (Ubuntu, Raspberry Pi OS, etc.)
- Docker installed
Before downloading the repository, set up the necessary directories for your bind mounts.
Run the following commands:
mkdir -p ~/docker/pihole-unbound
sudo mkdir -p /srv/docker/pihole-unbound/pihole/etc-pihole
sudo mkdir -p /srv/docker/pihole-unbound/pihole/etc-dnsmasq.d
sudo mkdir -p /srv/docker/pihole-unbound/unbound/etc-unbound
sudo chown -R $USER:$USER /srv/docker
chmod -R 755 /srv/docker
touch /srv/docker/pihole-unbound/unbound/etc-unbound/unbound.log
cd ~/docker/pihole-unbound
mkdir -p ~/docker/pihole-unbound
: Creates a working directory in your home folder.sudo mkdir -p /srv/docker/...
: Creates bind mounts for Pi-hole and Unbound.sudo chown -R $USER:$USER /srv/docker
: Ensures your user owns the folders.chmod -R 755 /srv/docker
: Sets read/write permissions for better access.touch unbound.log
: Prepares the log file for Unbound.
You can download the latest version of this repository using wget
or curl
:
Option 1: Using wget
wget https://github.com/kaczmar2/pihole-unbound/archive/refs/heads/main.tar.gz
tar -xzf main.tar.gz --strip-components=1
Option 2: Using curl
curl -L -o main.tar.gz https://github.com/kaczmar2/pihole-unbound/archive/refs/heads/main.tar.gz
tar -xzf main.tar.gz --strip-components=1
The --strip-components=1
flag ensures the contents are extracted directly into ~/docker/pihole-unbound
instead of creating an extra subdirectory.
Optional: Remove the archive after extraction
rm main.tar.gz
Now, deploy the Pi-hole and Unbound services using:
docker compose up -d
To confirm Unbound is resolving queries correctly, run the following commands in the pihole container:
Open a bash
shell in the container:
docker exec -it pihole /bin/bash
Test that Unbound is operational:
dig pi-hole.net @127.0.0.1 -p 5335
The first query may be quite slow, but subsequent queries should be fairly quick.
Test validation
dig fail01.dnssec.works @127.0.0.1 -p 5335
dig dnssec.works @127.0.0.1 -p 5335
The first command should give a status report of SERVFAIL and no IP address. The second should give NOERROR plus an IP address.
To set the pihole web admin password, run the following commands in the pihole container, if you're not already there from the previous step (docker exec -it pihole /bin/bash
):
pihole setpassword 'mypassword'
Get the hashed password from pihole.toml
:
cat /etc/pihole/pihole.toml | grep -w pwhash
exit
the container and copy the hashed password into your .env
file on the host.
Make sure to enclose the value in single quotes (''
).
WEB_PWHASH='$BALLOON-SHA256$v=1$s=1024,t=32$pZCbBIUH/Ew2n144eLn3vw==$vgej+obQip4DvSmNlywD0LUHlsHcqgLdbQLvDscZs78='
Uncomment the FTLCONF_webserver_api_pwhash
enviornment variable in docker-compose.yml
:
FTLCONF_webserver_api_pwhash: ${WEB_PWHASH}
Restart the containers:
docker compose down
docker compose up -d
Once running, open your web browser and go to:
http://<your-server-ip>/admin/
Login using the password you set.
The configuration in pi-hole.conf
sets the socket receive buffer size for incoming DNS queries to a higher-than-default value in order to handle high query rates. You may see a warning in unbound related to this setting:
so-rcvbuf 1048576 was not granted. Got 425984. To fix: start with root permissions(linux) or sysctl bigger net.core.rmem_max(linux) or kern.ipc.maxsockbuf(bsd) values.
To fix it:
- Check the current limit. This will show something like
net.core.rmem_max = 425984
:
sudo sysctl net.core.rmem_max
- Temporarily increase the limit to match Unbound's request:
sudo sysctl -w net.core.rmem_max=1048576
- Make it permanent. Edit
/etc/sysctl.conf
and add or edit the line:
net.core.rmem_max=1048576
- Save and apply:
sudo sysctl -p
For enhanced security, see my other guides on configuring SSL encryption for the Pi-hole web interface.
This will show live logs for both the pihole
and unbound
containers.
docker logs -f pihole
docker logs -f unbound
The unbound.conf.d/
directory contains custom configuration settings for Unbound.
unbound.conf.d/
(Custom Unbound settings, automatically included via a wildcard inunbound.conf
.)10-pi-hole.conf
– Configures Unbound for use with Pi-hole following the Pi-hole Unbound guide.20-private-domains.conf
– Adds exceptions for domains that resolve private IPs from public DNS servers.- Includes an entry for
plex.direct
, required for Plex clients to connect to the local server. - Additional entries may be needed for other services that rely on domain-based local resolution.
- Includes an entry for
- All files in
unbound.conf.d/
are automatically read by Unbound via a wildcard include directive inunbound.conf
. - Modify
20-private-domains.conf
as needed to allow other trusted services that require resolving private IPs. - The following files have been removed from the configuration as they are not required for the Pi-hole + Unbound setup:
forward-records.conf
: This setup uses Unbound as a recursive resolver so forwarding is unnecessary.a-records.conf
: Pi-hole can handle local hostname resolution instead.srv-records.conf
: Most home networks don’t need this, unless you are doing things running Active Directory or doing service discovery.