Post

Running Pi-hole in 2025 — updated best practices (Docker, Unbound, DoH)

A concise 2025 follow-up: how to run Pi-hole reliably today — Docker Compose example, Unbound resolver, DNS-over-HTTPS options, systemd-resolved tips, and troubleshooting checklist.

Running Pi-hole in 2025 — updated best practices (Docker, Unbound, DoH)

Pi-hole remains the easiest way to get network-wide adblocking, but the recommended setups have shifted since 2022. This follow-up covers practical, low-maintenance approaches for 2025: running Pi-hole in Docker (recommended for most installs), using a local recursive resolver (Unbound), optionally using DNS-over-HTTPS (DoH) for upstream privacy, and common pitfalls with systemd-resolved or router DHCP.


🛡️ Why run Pi-hole differently in 2025

Hardware and OS expectations have changed: many users now prefer containerised deployments for portability, simplified updates, and better sandboxing. Also, privacy-focused upstreams (DoH/DoT) and local recursive resolvers are now easy to run alongside Pi-hole — giving you both performance and privacy.

Key goals for the modern setup:

  • Keep Pi-hole small and easy to update (Docker Compose recommended)
  • Avoid leaking DNS to third parties (Unbound or DoH)
  • Make sure DHCP and DNS roles are unambiguous (router vs Pi-hole)
  • Ensure persistent, restored firewall/iptables or nftables rules across reboot

Running Pi-hole in Docker isolates it from OS changes and makes upgrades repeatable. Below is a minimal, ILLUSTRATIVE docker-compose.yml. Adapt the UID/GID, timezone and network interfaces for your host.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
version: "3.8"
services:
  pihole:
    image: pihole/pihole:latest
    container_name: pihole
    restart: unless-stopped
    ports:
      - "53:53/tcp"
      - "53:53/udp"
      - "80:80/tcp"
      - "443:443/tcp"  # optional if using DoH proxy inside the container
    environment:
      TZ: 'Europe/Berlin'
      WEBPASSWORD: 'changeme'
      DNSMASQ_LISTENING: 'all'
    volumes:
      - ./etc-pihole:/etc/pihole
      - ./etc-dnsmasq.d:/etc/dnsmasq.d
    cap_add:
      - NET_ADMIN
    networks:
      - pihole_net

networks:
  pihole_net:
    driver: bridge

Notes:

  • Bind port 53 on the host. If another service (like systemd-resolved) already listens on 53, stop or reconfigure it.
  • Persist /etc/pihole and /etc/dnsmasq.d so updates keep your config and blocklists.
  • NET_ADMIN capability is often needed for DNS features; avoid giving more privileges than necessary.

Option B Native install (Pi OS or other Linux)

If you prefer a system package, the official installer still works. Native installs can be slightly leaner on resources and simpler for single-board computers without Docker.

1
curl -sSL https://install.pi-hole.net | bash

Remember to configure a static IP and ensure the Pi is the primary DNS target for clients or the router.


🔍 Upstream resolver choices — privacy vs performance

Unbound (local recursive resolver)

Running Unbound locally gives the best privacy (no upstream logs) and often better response times once cached. Run Unbound on the same host (localhost or container) and point Pi-hole’s upstream to 127.0.0.1#5335 (or the Unbound container address).

Quick Unbound install (Debian/Raspbian):

1
2
3
sudo apt update
sudo apt install unbound
# use the example config from the Unbound docs for stub or full recursive

Then in Pi-hole Admin → Settings → DNS, add 127.0.0.1#5335 as a custom upstream and disable public upstreams if you want full recursion only.

DNS-over-HTTPS (DoH) / DNS-over-TLS (DoT)

If you prefer to use a privacy-preserving upstream instead of Unbound, use a DoH/DoT proxy (cloudflared, doh-proxy, or a small forwarder) and point Pi-hole at localhost:PORT. This keeps upstream queries encrypted to the provider.

Example using cloudflared as an outbound proxy:

1
2
cloudflared proxy-dns --port 5053 --upstream https://1.1.1.1/dns-query
# then set Pi-hole to use 127.0.0.1#5053

Tradeoffs:

  • Unbound avoids any third-party resolver entirely (best privacy)
  • DoH/DoT keeps upstream private from on-path observers but still trusts a provider

🧩 Common gotchas and system integrations

systemd-resolved and port 53 conflicts

Many modern Linux distributions have systemd-resolved listening on port 53 (loopback). If you see “address already in use” when starting Pi-hole or its container, either:

  • Disable or reconfigure systemd-resolved to avoid binding port 53, or
  • Run Pi-hole on a different host interface and update your router/device DNS to point to it.

To disable systemd-resolved (example):

1
2
3
4
sudo systemctl stop systemd-resolved
sudo systemctl disable systemd-resolved
sudo rm /etc/resolv.conf
sudo ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf

(Adjust carefully for your distro — some use resolvconf or rely on systemd-resolved.)

DHCP: router vs Pi-hole

Decide where DHCP should live. Running Pi-hole as DHCP server gives you integrated client names and grouping, but many prefer the router’s DHCP for network-wide stability. If you keep router DHCP, set the router’s DNS to the Pi-hole IP instead of changing every device.

Persistent firewall rules

If you add NAT or forwarding rules for captive setups (e.g., Wi‑Fi on the Pi), ensure rules persist across reboot (iptables-persistent, nftables’ persistent backend, or systemd service to restore rules).


🗂️ Managing blocklists and false positives

  • Use curated sources like Firebog to find reliable lists. Don’t add dozens of overlapping lists — that can slow gravity and increase false positives.
  • Use group management in the Pi-hole UI to allow different clients different block policies.
  • Whitelist and use regex carefully; log the queries (FTL) to understand blocked domains.

🔁 Maintenance and updates

  • For Docker: docker-compose pull && docker-compose up -d to update.
  • For native: pihole -up to update Pi-hole components.
  • Regularly run pihole -g after changing blocklists to refresh gravity.
  • Backup /etc/pihole and group configurations (Teleporter) before major upgrades.

🧭 Traffic flow overview

graph LR
client["Client device"] -->|"DNS query"| pihole["Pi-hole (DNS)"]
pihole -->|"Local recursion"| unbound["Unbound (local) | 127.0.0.1:5335"]
pihole -->|"DoH/DoT upstream"| doh["DoH/DoT provider"]
pihole -->|"Blocked (sinkhole)"| null["Blocked / sinkholed domain"]

✅ Quick checklist — post-install verification

  • DNS port free: sudo ss -ltnp | grep :53
  • Pi-hole reachable: curl -I http://<PI_IP>/admin (replace <PI_IP>)
  • Upstream resolver check: dig @127.0.0.1 -p 5335 example.com (if using Unbound)
  • DoH proxy check: dig @127.0.0.1 -p 5053 example.com (if using cloudflared)
  • Gravity updated: pihole -g
  • FTL status: pihole-FTL status

🧠 Final Thoughts

Pi-hole in 2025 is flexible: containerised deployments simplify maintenance, and pairing Pi-hole with Unbound or a DoH proxy gives you excellent privacy and performance. Pick the deployment model that matches your comfort level (Docker for portability, native for minimalism) and follow the checklist above to avoid the common pitfalls.

This post is a follow-up to the 2022 overview and focuses on robust, maintainable setups for modern home networks.

This post is licensed under CC BY 4.0 by the author.