This is a guide for setting up a Raspberry Pi with Pi-hole and Unbound, plus some basic security practices. This is the "hard-mode" with the official image. If you're looking for something easy to set up, look into DietPi
Note: This guide targets Pi-hole v6 and Raspberry Pi OS Bookworm. Pi-hole v6 removed lighttpd and PHP, the web interface is now built into
pihole-FTLitself, and all settings live in/etc/pihole/pihole.toml.
- For the device choose RaspberryPi5
- For the OS, go to "Raspberry Pi OS (other)" and choose Raspberry Pi OS Lite 64-bit.
- Choose the device + OS + storage
- Click the gear/settings icon and set up SSH login, username and password before writing
- Insert the SDCard in the Pi, connect the Pi with an ethernet cable and power it on
- Open your router page and find the Pi in the connected devices / DHCP client list
- Note its MAC address and assign it a static IP (for example 192.168.1.5)
If you have a monitor and keyboard attached instead, you can find the MAC with
ip link show
ssh username@staticIPhere
sudo apt update && sudo apt upgrade -y && sudo reboot
- Edit the file with
visudo, which checks the syntax before saving:
sudo visudo -f /etc/sudoers.d/010_pi-nopasswd
- Change
yourusername ALL=(ALL) NOPASSWD: ALLtoyourusername ALL=(ALL) PASSWD: ALL - Mind the spaces and don't change anything else.
Don't edit this file with plain
nano. A typo in a sudoers file can lock you out ofsudocompletely, which is painful on a headless machine.visudorefuses to save a broken file.
- Edit the following file with:
sudo nano /etc/ssh/sshd_config
- Uncomment
Port 22and change it to your liking
sudo reboot
sudo apt install ufw -y
sudo ufw allow SSHPort/tcp
Do this before enabling the firewall, or you'll lock yourself out.
sudo ufw limit SSHPort/tcp
- Check ports being currently used with:
sudo ss -tupln
- Check which service is using which port number with:
sudo lsof -i :PORT
sudo nano /etc/ufw/before.rules
- Under
# ok icmp codes for INPUTadd a new line:
-A ufw-before-input -p icmp --icmp-type echo-request -j DROP
sudo reboot
ssh username@RPiIPHere -p SSHPortHere
- Clear it once with:
sudo truncate -s 0 /etc/motd
- If a system upgrade brings it back, add this line to
~/.bashrcinstead:
sudo truncate -s 0 /etc/motd 2>/dev/null
- Reload bashrc with:
source ~/.bashrc
Some older guides suggest adding
export TERM=xtermto.bashrcfor terminals like kitty. Avoid it, it tells every program your terminal is a basic xterm and breaks 256-colour support. If kitty gives you trouble over SSH, usekitty +kitten sshfrom your client instead.
- Some Debian 12 (bookworm) packages complain about the architecture
- The processor microcode module only supports AMD and Intel, not aarch64 (64-bit ARM)
- You can "fix" this by removing the needrestart package with:
sudo apt purge needrestart -y
- Or you can change the following value:
sudo sed -i 's/#\$nrconf{ucodehints} = 0;/$nrconf{ucodehints} = 0;/' /etc/needrestart/needrestart.conf
sudo nano /boot/firmware/config.txt
- Add the following line at the end of the file:
dtoverlay=disable-wifi
On Bookworm this file lives in
/boot/firmware/, not/boot/. Editing/boot/config.txton Bookworm does nothing.
- Reboot for it to take effect
- Install Pi-Hole with (choose Google or Cloudflare DNS for now, we replace it with Unbound later):
curl -sSL https://install.pi-hole.net | bash
- Set the web interface password with:
pihole setpassword
By default Pi-hole v6 binds to port 80 (and 443 for HTTPS). If either is already taken during install, it falls back to 8080. See step 24 if you want to move it.
- Install Unbound with:
sudo apt install unbound -y
- Copy the config found here
- Paste it into:
sudo nano /etc/unbound/unbound.conf.d/pi-hole.conf
- Restart Unbound:
sudo systemctl restart unbound
sudo systemctl disable --now unbound-resolvconf.service
sudo sed -Ei 's/^unbound_conf=/#unbound_conf=/' /etc/resolvconf.conf
sudo rm -f /etc/unbound/unbound.conf.d/resolvconf_resolvers.conf
- Open the Pi-hole interface -> Settings -> DHCP
- Enable "DHCP server enabled"
- Choose an IP range, for example 192.168.1.21 to 192.168.1.151
- For Router (gateway) IP address put your router IP, e.g. 192.168.1.1
- Open your router page
- Turn off the DHCP server in your router, and set primary and secondary DNS to your Pi (don't forget to save)
- List your connections on the Pi with:
nmcli connection show
- Edit your ethernet connection (
Wired connection 1in my case):
sudo nmcli connection edit "Wired connection 1"
- Now set the parameters according to your connection:
set ipv4.method manual
set ipv4.addresses RPiIP/your_subnet
set ipv4.gateway RouterIP
set ipv4.dns 127.0.0.1
save persistent
quit
- To revert any changes:
sudo nmcli connection edit "Wired connection 1"
reset
save persistent
quit
- Go to the Pi-hole interface -> Settings -> DNS
- Untick all upstream DNS servers, tick Custom 1 (IPv4) and enter
127.0.0.1#5335
- Shut down your router and your Raspberry Pi
- Turn your router on first
- Turn on the Pi
- Check Unbound answers with:
dig pi-hole.net @127.0.0.1 -p 5335
- You should get a
NOERRORstatus and an answer section. The first query may be slow while Unbound builds its cache.
- Check the current port with:
pihole-FTL --config webserver.port
- Change it with:
sudo pihole-FTL --config webserver.port 8080
- Or edit
/etc/pihole/pihole.tomldirectly and restart FTL:
sudo systemctl restart pihole-FTL
If you plan to run a reverse proxy such as Nginx Proxy Manager on the same Pi, move Pi-hole off ports 80 and 443 first. Pi-hole only auto-falls back to 8080 if a conflict exists at install time, installing a proxy afterwards will collide, and Let's Encrypt HTTP-01 challenges need port 80 free.
sudo ufw allow 53/tcp # DNS resolution
sudo ufw allow 53/udp # DNS resolution
sudo ufw allow 67/udp # only needed if you use Pi-hole as your DHCP server
sudo ufw allow 80/tcp # Pi-hole web interface (HTTP)
sudo ufw allow 443/tcp # HTTPS — Pi-hole native HTTPS, or a reverse proxy such as Nginx Proxy Manager
- If you moved the web interface to a custom port, allow that instead of 80:
sudo ufw allow YOURPORT/tcp
- Enable the firewall with:
sudo ufw enable
sudo apt install fastfetch -y
- Generate a config you can edit:
fastfetch --gen-config
- The config lands in
~/.config/fastfetch/config.jsonc
- Open
.bashrc:
nano ~/.bashrc
- Add
fastfetchat the end of the file, save and exit - Reload bashrc with:
source ~/.bashrc
sudo apt install nala tldr neovim -y
The docker package in Debian repos is not Docker, use the official install script, which also installs the Compose v2 plugin:
curl -fsSL https://get.docker.com | sh
- Add your current user to the docker group:
sudo usermod -aG docker "$USER"
- Enable docker autostart:
sudo systemctl enable docker
- Reboot so that your user is actually in the docker group:
sudo reboot
- Check both are working:
docker --version
docker compose version
- Create the directory and cd into it (note: no quotes around
~):
mkdir -p ~/Docker/portainer-ce && cd ~/Docker/portainer-ce
- Create the following file:
nano docker-compose.yml
- Paste the following inside (change
PORTto a port you want), exit and save:
services:
portainer-ce:
image: portainer/portainer-ce
container_name: portainer
command: -H unix:///var/run/docker.sock
ports:
- 'PORT:9000'
volumes:
- 'portainer_data:/data'
- '/var/run/docker.sock:/var/run/docker.sock'
restart: always
volumes:
portainer_data:- Start it with:
docker compose up -d
- Allow the port through the firewall:
sudo ufw allow PORT/tcp
- Go to your browser and open
http://RPiIP:PORTAINERPORT - Choose a name for your user and a password