Almost every Pi-hole guide on the internet assumes you are about to buy a Raspberry Pi. You are not. You already have a home server humming away in a closet, and you want it to block ads for the whole house. This is the pi-hole home server setup guide for that exact person, the one who already runs Proxmox or a Linux box with Docker and just wants to bolt on network-wide blocking without ordering more hardware.
Here is what nobody else tells you. On Proxmox, Pi-hole’s FTL engine dies on a fresh LXC container because of a systemd namespace permission error, and most guides pretend it doesn’t exist. On an Ubuntu Docker host, systemd-resolved is already squatting on port 53, so your container boots, looks healthy, and silently resolves nothing. I cover both failure modes below with the exact fixes. And I will show you the query log, which is the real reason Pi-hole gets its hooks in you.
One more thing up front. This is all Pi-hole v6. Version 6 ripped out lighttpd, moved everything to a TOML config, and renamed the Docker environment variables. If you follow a v5 tutorial on a fresh install today, it breaks immediately. Don’t. If you don’t have a server yet at all, go build one first with my guide to a Linux home server for under $200, then come back.
Why Pi-hole is worth the 10 minutes (it’s more than an ad blocker)
Most guides sell Pi-hole as a free ad blocker. That undersells it badly. An ad blocker is the demo. The product is knowing what every device in your house is actually saying to the internet.
What DNS-level blocking actually means (and why your TV finally gets quiet)
The whole reason a pi-hole home server setup beats a browser extension is the layer it works on. A browser extension blocks ads in your browser. That’s it. It does nothing for your phone’s apps, your game console, or that smart TV that runs a mystery operating system designed by people who hate you. Pi-hole works one level down, at DNS, the system that turns a name like ads.example.com into an IP address. When a device asks Pi-hole for a known ad or tracker domain, Pi-hole just answers “nope, that doesn’t exist.” The ad never loads because the device never finds the server.
Set it up once. Every device on the network is covered. No app to install on the TV, no profile to load on the phone, nothing. A smart TV that was beaming telemetry home a couple hundred times a day suddenly goes quiet. That is the whole pitch for DNS-level blocking, and it is a good one.
The query log: see every device on your network, for free
Here is the part that turns a weekend project into a habit. Pi-hole logs every DNS query from every device, with timestamps and the device name attached. You can scroll it like a feed.
You will find things. A “smart” lightbulb phoning a server in a country you’ve never visited. An old tablet you forgot was on the network hammering an analytics domain every thirty seconds. A kid’s device quietly resolving something it absolutely should not be resolving. The query log is rogue-IoT radar, malware-callback detector, and parental-controls audit trail, all in one dashboard you already paid zero dollars for. If I’m being honest, the ad blocking is what gets people in the door, but the query log is what keeps the container running a year later.
Which path is right for your pi-hole home server setup
No decision tree exists in any of the popular Pi-hole guides, because they all assume you’re starting from nothing. You aren’t. So pick your path based on what you already run, not on what some blog thinks is theoretically cleanest. Three options.

Already on Proxmox? Use an LXC container (Path A)
If your server runs Proxmox, give Pi-hole its own LXC container. It’s lighter than a full VM, isolated from everything else you have running, and trivial to back up or roll back. There is one nasty gotcha that breaks it on first boot, and I fix it in Path A. If you’re on Proxmox and haven’t read my Proxmox vs TrueNAS vs Unraid breakdown, that’s the context for why LXC is the right tool here.
Already running Docker on Linux? Use Docker Compose (Path B)
Got a Linux box already stacked with Docker containers? Add Pi-hole as one more service in a compose file. Same pattern you’d use for any self-hosted app, like the one in my n8n beginner guide. The catch on Ubuntu hosts is port 53, and I fix that in Path B.
Fresh Debian/Ubuntu server? Bare-metal install (Path C, fastest)
If you’ve got a clean Debian or Ubuntu box doing nothing else, skip the containers entirely. The official installer is a one-liner:
# Run as a regular user with sudo, not as root
curl -sSL https://install.pi-hole.net | bash
Walk through the prompts, pick an upstream DNS (more on that later), and you’re done in five minutes. Just heed the systemd-resolved warning the installer throws on Ubuntu, which is the same port 53 fight from Path B.
Path A: Pi-hole as a Proxmox LXC container
This is the pi-hole home server setup most Proxmox users want, and it’s the one where the popular guides quietly leave you stranded. We’ll create the container, install Pi-hole v6, and then fix the FTL error that nobody warns you about.
Create the LXC container (Debian template, 512MB RAM, static IP)
In the Proxmox web UI, create a new CT from a Debian 12 template. Pi-hole barely sips resources, so you don’t need much. 512MB RAM. One CPU core. 8GB disk. Give it an unprivileged container by default, and assign a static IP on your LAN, because a DNS server that changes addresses is useless. Write that IP down. You’ll point your whole router at it later.
From the Proxmox shell, the create looks like this if you’d rather not click through the wizard:
# Container ID 110, static IP .53 on your subnet, gateway is your router
pct create 110 local:vztmpl/debian-12-standard_12.12-1_amd64.tar.zst \
--hostname pihole \
--cores 1 --memory 512 --rootfs local-lvm:8 \
--net0 name=eth0,bridge=vmbr0,ip=192.168.1.53/24,gw=192.168.1.1 \
--unprivileged 1 --features nesting=1 --onboot 1
pct start 110
pct enter 110
Note the nesting=1 feature. Pi-hole v6 wants it, and leaving it off is one of the reasons FTL falls over inside an LXC.
Install Pi-hole v6 inside the container
You’re now inside the container as root. Update, grab curl, and run the official installer:
# Inside the LXC container
apt update && apt -y upgrade
apt -y install curl
curl -sSL https://install.pi-hole.net | bash
The installer walks you through upstream DNS, blocklists, and the admin interface. Pick a static IP confirmation, accept the defaults for now, and at the end it prints your admin password. Save it. On v6 you can reset it any time with pihole setpassword.
Fix the FTL systemd namespace error (the part every other guide skips)
Here’s where people rage-quit. On many Proxmox setups, Pi-hole installs fine, then pihole-FTL refuses to start. You check the logs and find something like this:
# systemctl status pihole-FTL shows a failure
Failed to set up mount namespacing: Permission denied
pihole-FTL.service: Failed with result 'exit-code'.
That’s not a Pi-hole bug. The FTL service unit asks systemd for private mount and temp namespaces as a hardening measure, and an unprivileged LXC container isn’t allowed to grant them. The kernel says no. The service dies. The fix is to tell that one service to stop asking for the namespaces it can’t have, with a systemd override:
# Inside the container
mkdir -p /etc/systemd/system/pihole-FTL.service.d
cat > /etc/systemd/system/pihole-FTL.service.d/override.conf <<'EOF'
[Service]
PrivateMounts=false
ProtectSystem=false
ProtectHome=false
PrivateTmp=false
ProcSubset=all
ProtectControlGroups=false
RestrictNamespaces=false
EOF
systemctl daemon-reload
systemctl restart pihole-FTL
That override loosens the sandboxing flags the unprivileged container can’t satisfy. It’s the difference between a Pi-hole that works and a Pi-hole that mocks you from the failed-services list. If you’d rather not touch systemd at all, the other option is making the container privileged, but loosening one service unit beats handing the whole container extra kernel access. Your call.
Verify it’s running
Check the service and the version. You want v6:
# Should report active (running) and a v6.x core
systemctl status pihole-FTL --no-pager
pihole -v
Open http://192.168.1.53/admin in a browser, log in, and you should see the v6 dashboard. Empty for now. It fills up fast once the router points at it.
Path B: Pi-hole via Docker Compose on an existing Linux server
If your home server already runs a stack of Docker containers, this is your pi-hole home server setup path. Same compose-file rhythm as everything else you self-host. The one landmine is on Ubuntu, and it’s a quiet one.
The docker-compose.yml (Pi-hole v6 environment variables)
Make a directory for Pi-hole’s persistent data and drop in a compose file. The v6 image changed the environment variable names, so copying an old v5 example is how you waste an afternoon. Here’s a current one:
# ~/pihole/docker-compose.yml
services:
pihole:
container_name: pihole
image: pihole/pihole:latest
ports:
- "53:53/tcp"
- "53:53/udp"
- "80:80/tcp"
environment:
TZ: "America/New_York"
# v6 uses FTLCONF_webserver_api_password, NOT the old WEBPASSWORD
FTLCONF_webserver_api_password: "changeme"
FTLCONF_dns_listeningMode: "all"
volumes:
- "./etc-pihole:/etc/pihole"
restart: unless-stopped
Pin the timezone or your query-log timestamps will be a riddle. Notice FTLCONF_webserver_api_password. On v5 that was WEBPASSWORD, and every stale tutorial still uses the old one, which silently does nothing on v6. You’ll set a password, it won’t take, and you’ll wonder why.
The Ubuntu port 53 problem and how to fix it
Now the silent killer. Run docker compose up -d on a fresh Ubuntu server and you’ll likely get either a flat refusal to bind port 53, or worse, a container that starts clean and resolves nothing. The culprit is systemd-resolved, Ubuntu’s local DNS stub, which already holds port 53 on 127.0.0.53. Two services, one port. One loses.
Check whether it’s listening:
# If you see 127.0.0.53:53 owned by systemd-resolve, that's the conflict
sudo ss -tulpn | grep ':53'
The fix is to stop resolved from grabbing the port while keeping local DNS working. Turn off its stub listener and point the host at a real resolver:
# Disable the stub listener in /etc/systemd/resolved.conf
sudo sed -i 's/#DNSStubListener=yes/DNSStubListener=no/' /etc/systemd/resolved.conf
# Replace the symlinked stub resolv.conf with the real one
sudo ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf
sudo systemctl restart systemd-resolved
Now port 53 is free. Bring the container up:
# From ~/pihole
docker compose up -d
docker compose logs -f pihole
network_mode: host vs bridge, which to use on an existing server
You’ll see guides tell you to use network_mode: host. On a single-purpose Raspberry Pi, fine. On a server already running a dozen containers, host mode dumps Pi-hole onto your host’s network with no isolation and a fresh chance to collide with whatever else binds port 80. Skip it.
Use explicit bridge port mapping like the compose file above. You stay isolated, you control exactly which ports leak out, and Pi-hole coexists with your other services instead of stomping on them. If you want Pi-hole to have its own LAN IP for cleaner per-device logging, macvlan is the grown-up option, but bridge mapping is plenty for most homelabs and far less fiddly.
Upstream DNS: pick one and understand why
Every guide makes this a dropdown you scroll past. It isn’t. Pi-hole blocks bad domains, but for everything it allows, it has to ask somebody “where does this name live?” Whoever you ask sees every site every device in your house visits. That’s a trust decision, not a preference.
Cloudflare (1.1.1.1): fastest, but still a third party
Cloudflare’s 1.1.1.1 is genuinely the fastest of the bunch most of the time, and they claim to scrub query logs within 24 hours. Good performance, decent policy. But it’s still a giant company seeing your lookups. Fast and convenient, not invisible. For most people this is the sane default.
Quad9 (9.9.9.9): free, non-profit, malware blocking built in
Quad9 is run by a Swiss non-profit, and it does something the others don’t: it refuses to resolve known malware and phishing domains at the DNS level. That’s a second safety net stacked under Pi-hole’s blocklists. A hair slower than Cloudflare in most tests, and worth it if “blocks malware by default” matters more to you than shaving a few milliseconds.
Unbound: no third party at all (adds 15 minutes of setup)
Want nobody seeing your queries? Run Unbound alongside Pi-hole and become your own recursive resolver. Instead of asking Cloudflare or Quad9, your server walks the DNS tree itself, straight from the root servers. No third party logs anything because there is no third party. It costs you about 15 minutes of extra setup and a slightly slower first lookup per domain. For the privacy-minded homelabber, this is the endgame. The other two are the convenient middle.
Point your router at Pi-hole (the step that makes it network-wide)
Pi-hole isn’t blocking anything for your devices until your router tells them to use it. This is the step that flips it from “a thing on my server” to “the whole house is covered.” And there’s one gotcha that takes your internet down if you skip it.
Set a DHCP reservation first (static IP for Pi-hole)
Your router must always find Pi-hole at the same address. Easiest way is a DHCP reservation: in your router’s admin page, tie Pi-hole’s MAC address to a fixed IP so it never changes. Or set the static IP inside the OS, like we did in Path A. Either works. Just pick one and make it permanent.
Change your router’s DNS server setting
Find the DHCP or LAN settings in your router and set the primary DNS that it hands out to clients to your Pi-hole’s IP. Save. Then the part people forget: reboot your devices or renew their leases, because most of them cached the old DNS server and will keep using it until forced to let go.
Why you need a secondary DNS (and what to set it to)
Tempting move: set Pi-hole as your only DNS server. Do not. The day Pi-hole reboots for an update, your entire network goes dark, and you’ll be the most hated person in the house. But here’s the trap on the other side. If you set the secondary to 8.8.8.8, devices will sometimes use it instead of Pi-hole, and ads leak through with no pattern you can debug.
The clean answer: if you run two Pi-holes, point primary and secondary at both. Most of us run one, so point the secondary at the same Pi-hole IP, or leave it blank if your router allows, so failover means “wait for Pi-hole to come back” rather than “quietly bypass the ad blocker.” A reboot of Pi-hole takes seconds. You’ll live.
Post-install: blocklists, conditional forwarding, and the admin dashboard
The base install blocks a respectable chunk already. A little tuning is what takes your pi-hole home server setup from “working” to “sharp,” and one homelab-specific setting makes your named services resolve properly.

Start with one good blocklist (Hagezi Multi Pro)
Do not enable every blocklist you find on a forum. That’s the fastest route to a network where half your apps break and you have no idea why. Start with one well-curated list. The Hagezi Multi Pro list is the opinionated pick: aggressive enough to catch real trackers, maintained well enough that it won’t randomly nuke your banking site. Add it in the admin UI under blocklists, run pihole -g to update gravity, and live with it for a week before adding anything else.
Conditional forwarding: resolve your homelab hostnames
Here’s the homelab feature no beginner guide bothers with. Out of the box, ask Pi-hole for proxmox.local or nas.local and it shrugs, because it forwards everything upstream and Cloudflare has never heard of your NAS. Conditional forwarding fixes it: tell Pi-hole that for your local subnet, forward name lookups to your router, which actually knows your devices. Set it under Settings, DNS, conditional forwarding, with your subnet and router IP. Now your local names resolve and your query log shows real device names instead of bare IP addresses. This is also what lets Pi-hole serve tidy local DNS for the hostnames in your Home Assistant dashboard.
Whitelisting a site Pi-hole over-blocked
It happens. A shopping link won’t load, or a login button does nothing. Before you blame your ISP, check the query log. If you see a domain from that site marked blocked, whitelist it in the admin UI under Domains, and the page springs back to life. Two clicks. The query log told you exactly which domain to allow, which is why the log is worth its weight.
Quick FAQ
Will Pi-hole slow down my internet?
No, the opposite usually. Pages with fewer ads and trackers to load actually render faster. Pi-hole answers blocked queries instantly and caches the rest, so repeat lookups get quicker over time. The only “slowdown” is the first lookup of a brand-new domain, measured in milliseconds you’ll never feel.
What happens if Pi-hole goes down?
If Pi-hole is your only DNS server, the network can’t resolve anything until it’s back, which is exactly why the secondary-DNS section above matters. Set it up right and a Pi-hole reboot is a non-event. Set it up lazily and a reboot is an outage. This is the single most common Pi-hole regret, so don’t earn it.
Can I use it with Tailscale?
Yep, and it’s a great combo. Point your tailnet’s DNS at your Pi-hole and you get ad blocking on your phone over cellular, anywhere in the world, like you’re home. I covered the setup in my Tailscale guide. Private DNS plus network-wide blocking on the same box is one of the better reasons to run both.
Does it block YouTube and Twitch ads?
Mostly not, and any guide that promises otherwise is lying to you. YouTube and Twitch serve ads from the same domains as the video itself, so blocking the ad blocks the show. Pi-hole crushes ads on the open web, in apps, and on smart TVs. Streaming-platform ads are a different fight.
Do I need dedicated hardware?
No, that’s the entire point of this guide. A pi-hole home server setup runs on the box you already own, and your existing server handles it without noticing. But if you’d rather give it its own box, a cheap mini PC like a Beelink or refurbished HP EliteDesk mini or a Raspberry Pi 5 does the job for not much money. Dedicated hardware is a want, not a need.
Wrapping up
That’s the whole job. Your pi-hole home server setup comes down to picking the path that matches what you already run, dodging the LXC namespace trap or the port-53 trap depending on your route, pointing the router at Pi-hole with a sane fallback, and tuning one good blocklist. Ten minutes of work, minus the troubleshooting you now won’t have to do.
The real pro tip from this article: don’t sleep on the query log. The ad blocking is nice, but watching what your devices actually talk to is the part that’ll make you a little paranoid in a useful way.
Sources
- Pi-hole, “Introducing Pi-hole v6” (architectural changes: embedded web server, TOML config, REST API)
- Official Pi-hole Docker documentation (v6 environment variable names and compose config)
- Official Pi-hole Docker image (GitHub) (release history and known issues)
- Pi-hole post-install documentation (router configuration and conditional forwarding)
- Sugeesh, “Setting up Pi-hole on Proxmox LXC: what I did, broke, and learned” (the FTL systemd namespace error and override fix)
- Pi-hole guide: Unbound recursive DNS (self-hosted upstream resolver)
Your turn
Which path did you take, LXC or Docker, and did you hit the namespace error or the port-53 ghost? Drop it in the comments. I especially want the query-log horror stories, the “I had no idea my smart TV was doing THAT” moment we all eventually get. And if this saved you an afternoon of troubleshooting, share it with the friend who keeps complaining about ads on their TV. Then go point them at the Tailscale guide so they can take their Pi-hole on the road.


