Pi-hole is the network-wide ad blocker that runs on your TrueNAS box and blocks ads, trackers, and malware domains for every device on your network — no browser extensions, no per-device configuration.
What it is
| Platform | TrueNAS Scale (Docker custom app) |
| Ports | 53 (DNS), 80 (web UI), 443 (HTTPS) |
| Image | ghcr.io/pi-hole/pihole:latest |
| Config path | /mnt/apps/pihole/config → /etc/pihole |
| Web path | /mnt/apps/pihole/dnsmasq → /etc/dnsmasq.d |
| License | MIT |
Pi-hole sits between your devices and the internet, filtering DNS requests through blocklists. When a device asks for an ad domain (like ads.doubleclick.net), Pi-hole responds with 0.0.0.0 — the request is silently dropped. The device never loads the ad.
It works at the network level: every device that uses your Pi-hole as its DNS server gets ad blocking automatically. Phones, smart TVs, gaming consoles, IoT devices — all covered without installing anything on them individually.
The hard part: port 53 and DNS conflicts
The install is five minutes. The hard part is that port 53 (DNS) is often already in use on TrueNAS Scale by systemd-resolved. If you try to start Pi-hole and get a "port already in use" error, this is why.
- Port 53 conflict
- systemd-resolved is listening on 127.0.0.53
- 53
- Pi-hole tries to bind to 0.0.0.0
- 53
- → Error
- address already in use
- Fix
- stop systemd-resolved and disable it
- Or
- use a different port (5353) and configure your router to forward DNS to 5353
The cleanest approach: disable systemd-resolved before deploying Pi-hole. This frees port 53 for Pi-hole to use exclusively.
- DNS resolution chain without Pi-hole
- DNS resolution chain with Pi-hole
- Device → Router (DNS) → Pi-hole (192.168.1.x
- 53) → Upstream DNS (1.1.1.1) → Internet
How to install it
Step 1: Stop systemd-resolved
sudo systemctl stop systemd-resolved
sudo systemctl disable systemd-resolved
sudo rm /etc/resolv.conf
echo "nameserver 1.1.1.1" | sudo tee /etc/resolv.conf
This prevents port 53 conflicts. Pi-hole needs port 53 to serve DNS to your network.
Step 2: Create directories
mkdir -p /mnt/apps/pihole/config
mkdir -p /mnt/apps/pihole/dnsmasq
Step 3: Generate a password
# Generate a random password for the web UI:
openssl rand -base64 12
Save this password — it is the admin password for the Pi-hole web interface.
Step 4: Deploy the Docker Compose
TrueNAS Scale → Apps → Discover Apps → Custom App (Docker Compose):
services:
pihole:
image: ghcr.io/pi-hole/pihole:latest
container_name: pihole
restart: unless-stopped
environment:
- TZ=Europe/London
- WEBPASSWORD=your_generated_password_here
- DNSMASQ_LISTENING=all
- PIHOLE_DNS_=1.1.1.1;1.0.0.1
ports:
- "53:53/tcp"
- "53:53/udp"
- "80:80/tcp"
volumes:
- /mnt/apps/pihole/config:/etc/pihole
- /mnt/apps/pihole/dnsmasq:/etc/dnsmasq.d
cap_add:
- NET_ADMIN
After deploy:
# Check Pi-hole is running:
docker logs pihole 2>&1 | tail -5
# Verify DNS is working:
dig @127.0.0.1 google.com
Log in at http://<nas-ip>/admin with username admin and the password you generated.
Step 5: Point your router to Pi-hole
- Open your router's admin page (usually
192.168.1.1) - Go to DNS Settings or WAN Settings
- Set the Primary DNS to your TrueNAS IP (
192.168.1.x) - Set the Secondary DNS to
1.1.1.1(Cloudflare as fallback) - Save and restart your router
All devices on your network now use Pi-hole for DNS automatically.
Configuration that matters
Upstream DNS servers
| Provider | Primary | Secondary | Protocol |
|---|---|---|---|
| Cloudflare | 1.1.1.1 |
1.0.0.1 |
DNS-over-TLS |
8.8.8.8 |
8.8.4.4 |
Standard DNS | |
| Quad9 | 9.9.9.9 |
149.112.112.112 |
DNS-over-TLS |
| OpenDNS | 208.67.222.222 |
208.67.220.220 |
Standard DNS |
Configure in Pi-hole → Settings → DNS. Cloudflare and Quad9 support DNS-over-TLS for encrypted queries.
Blocklists
| List | URL | What it blocks |
|---|---|---|
| Steven Black's | https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts |
Ads, malware, phishing |
| OAD | https://big.oisd.nl |
Ads, tracking, telemetry |
| 1Hosts (Lite) | https://cdn.jsdelivr.net/gh/badmojr/1Hosts@master/Lite/hosts.txt |
Balanced blocking |
| Peter Lowe's | https://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts |
Ads and tracking |
Add lists in Pi-hole → Adlists → paste URL → Save → Run pihole -g to update.
DHCP (optional)
| Setting | Recommended value | Why |
|---|---|---|
| DHCP server | Off (use your router) | Simpler setup, less to break |
| DHCP on Pi-hole | On only if you disable router DHCP | Pi-hole can serve DHCP instead |
| Lease time | 24 hours | Default is fine for most networks |
If you enable DHCP on Pi-hole, disable it on your router first. Two DHCP servers on the same network cause IP conflicts.
Whitelist and blacklist
| Action | When to use |
|---|---|
| Whitelist domain | A legitimate site is broken by Pi-hole (e.g., analytics.example.com) |
| Blacklist domain | An ad domain slips through the blocklist |
| Disable blocking | Temporarily, for debugging DNS issues |
Access via Pi-hole → Whitelist / Blacklist in the web UI, or via CLI:
# Whitelist a domain:
docker exec pihole pihole -w example.com
# Blacklist a domain:
docker exec pihole pihole -b ads.example.com
# Temporarily disable blocking:
docker exec pihole pihole -n off
# Re-enable blocking:
docker exec pihole pihole -n on
What I'd tell anyone building this
-
Disable systemd-resolved before deploying Pi-hole. If port 53 is already in use, Pi-hole will not start. The fix is to stop and disable systemd-resolved, then point
/etc/resolv.confto a public DNS like1.1.1.1. -
The password is set at deploy time via
WEBPASSWORD. If you forget it, check the container logs or reset it withdocker exec pihole pihole -a -p. -
Do not run Pi-hole on a Raspberry Pi if you have a NAS. Pi-hole uses almost no CPU or RAM. Running it on TrueNAS means one less device to maintain, and it stays up as long as your NAS is up.
-
Pi-hole does not replace a firewall. It blocks DNS requests to ad domains, but it does not block traffic at the IP level. For that, you need pfSense, OPNsense, or firewall rules on your router.
-
The blocklist update is manual. Run
docker exec pihole pihole -gperiodically, or set up a cron job to run it weekly. Pi-hole does not auto-update blocklists. -
Whitelist carefully. If a website or app breaks, temporarily disable Pi-hole to confirm it is the cause, then whitelist only the specific domain causing the issue. Whitelisting too broadly defeats the purpose.
Get it
Links:
Quick install (Docker Compose):
services:
pihole:
image: ghcr.io/pi-hole/pihole:latest
container_name: pihole
restart: unless-stopped
environment:
- TZ=Europe/London
- WEBPASSWORD=your_generated_password_here
- DNSMASQ_LISTENING=all
- PIHOLE_DNS_=1.1.1.1;1.0.0.1
ports:
- "53:53/tcp"
- "53:53/udp"
- "80:80/tcp"
volumes:
- /mnt/apps/pihole/config:/etc/pihole
- /mnt/apps/pihole/dnsmasq:/etc/dnsmasq.d
cap_add:
- NET_ADMIN
After deploy:
# Verify DNS is working:
dig @<nas-ip> google.com
# Update blocklists:
docker exec pihole pihole -g
# Check stats:
docker exec pihole pihole status
Log in at http://<nas-ip>/admin, add blocklists, and point your router's DNS to the NAS IP.