Self-Hosting πŸ“– 12 min read

Pi-hole Deployment Guide

Pi-hole is a DNS server for your local network that refuses to resolve known ad, tracking and telemetry domains. Every device that uses it as its DNS server gets that filtering, including smart TVs, consoles and IoT gear where you can't install an ad blocker. This guide installs Pi-hole v6 on Linux or in Docker, points your network at it, and covers what it can't block and how to fix the usual problems.

What You Need

  • Hardware: Any always-on machine: a Raspberry Pi, a mini PC, a VM or a container host. Pi-hole lists 512 MB RAM and 2 GB free disk (4 GB recommended) as its minimum.
  • Software: A maintained release of Debian, Ubuntu, Raspberry Pi OS, Fedora, CentOS Stream, Alpine or Armbian, or any Linux host with Docker Engine and the Compose plugin
  • Time: About an hour, plus whatever your router's admin pages need
  • Pi-hole needs a fixed IP address. The Pi-hole prerequisites note that a DHCP reservation on your router is fine.
  • On Ubuntu and Fedora, systemd-resolved already listens on port 53. See Troubleshooting before you install.
  • Use the Docker method if the machine already runs containers (the Docker Installation Guide covers Docker Engine itself).

Versions: written for Pi-hole v6 and the official pihole/pihole image tag 2026.09.0, the latest release on the docker-pi-hole releases page when this page was updated. Commands are from the pihole command reference. Pi-hole v6 replaced several v5 commands and Docker environment variables (the password is now set with pihole setpassword or FTLCONF_webserver_api_password), so commands from older v5 guides may no longer work.

What It Can and Can't Block

Pi-hole works at the DNS level. When a device asks for the address of a known tracking or ad domain, Pi-hole answers with a blocked response and the request never leaves your network. That covers third-party ad servers, analytics and telemetry endpoints, for every device on the network.

It can't block ads that come from the same domain as the content. YouTube video ads, sponsored posts in social feeds and ads that a site serves from its own servers look the same as the content you want, so DNS filtering can't separate them. The AdGuard Home FAQ describes the same limit for its own DNS blocker. Many in-app ads on phones fall into this category too.

πŸ“Ί Devices that ignore your DNS server

Some devices ignore the DNS server your router hands out and send queries straight to a public resolver. Those queries bypass Pi-hole. If your router or firewall supports it, add a NAT rule that redirects outbound port 53 traffic (TCP and UDP) from the LAN to Pi-hole, and exclude Pi-hole itself from that rule. Routers running OpenWrt or a firewall such as OPNsense can do this (see the OPNsense Firewall Setup); most ISP-supplied routers can't. A redirect doesn't help with devices that use encrypted DNS (DNS-over-HTTPS or DNS-over-TLS), because that traffic doesn't use port 53.

Installation Options

Pi-hole runs well on modest hardware. You can install it:

  1. Directly on Linux, for example on a Raspberry Pi (the Raspberry Pi Setup Guide covers the headless OS install)
  2. In Docker, if the host already runs containers
  3. In a VM or LXC container on a hypervisor; inside the guest it's the same as option 1

Option 1: Standard Installation (Bare Metal)

Terminal window with the commands from the section Option 1: Standard Installation (Bare Metal)
Give the Pi-hole host a fixed address with nmcli before installing.

Give the Machine a Fixed Address

Clients will be told to send every DNS query to this address, so it must not change. The simplest way is a DHCP reservation (static lease) on your router for the machine's MAC address.

If you'd rather set the address on the machine itself and it uses NetworkManager (Raspberry Pi OS since Bookworm, Fedora, many desktop installs), use nmcli. Older guides edit /etc/dhcpcd.conf, which has no effect on NetworkManager systems.

# Find the connection name
nmcli connection show

# Set a fixed address (adjust names and addresses for your network)
sudo nmcli connection modify "Wired connection 1" \
 ipv4.method manual \
 ipv4.addresses 192.168.1.100/24 \
 ipv4.gateway 192.168.1.1 \
 ipv4.dns "1.1.1.1"
sudo nmcli connection up "Wired connection 1"

Pick an address outside your router's DHCP pool so nothing else gets it. On Ubuntu Server, the address is set in netplan instead.

Run the Installer

The one-step install from the Pi-hole installation docs:

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

This pipes a script from the internet into a shell. To read it first, use the documented alternative:

wget -O basic-install.sh https://install.pi-hole.net
less basic-install.sh
sudo bash basic-install.sh

The installer is interactive. It asks which network interface to use and which upstream DNS provider to forward allowed queries to, and offers a default blocklist and query logging. The defaults are reasonable for a first install, and every choice can be changed later in the web interface.

Note the web interface password the installer shows at the end. To set your own, or if you lose it:

sudo pihole setpassword

Run without an argument, it prompts for the password so it doesn't end up in your shell history.

Pi-hole v6 CLI commands authenticate against its API. To run them as your normal user without typing the password each time, add yourself to the pihole group, as described in the post-install docs:

sudo usermod -aG pihole $USER

Option 2: Docker Installation

This is the quick-start file from the Pi-hole Docker docs, with the image pinned and the optional lines left out. Save it as compose.yaml:

Terminal window with the commands from the section Option 2: Docker Installation
A random admin password goes into .env, the file is locked down, then the container starts.
services:
  pihole:
    container_name: pihole
    image: pihole/pihole:2026.09.0
    ports:
      - "${LAN_IP:?Set LAN_IP}:53:53/tcp"
      - "${LAN_IP:?Set LAN_IP}:53:53/udp"
      - "127.0.0.1:8080:80/tcp"
      - "127.0.0.1:8443:443/tcp"
    environment:
      TZ: 'Europe/London'
      FTLCONF_webserver_api_password: ${PIHOLE_PASSWORD:?Set PIHOLE_PASSWORD}
      FTLCONF_dns_listeningMode: 'ALL'
    volumes:
      - './etc-pihole:/etc/pihole'
    restart: unless-stopped

Before starting, set LAN_IP to the host’s reserved private LAN address and generate a password. These variables are required; do not substitute a public address. The protected .env file contains the dashboard secret and must stay out of version control.

umask 077
printf 'LAN_IP=192.168.1.100\nPIHOLE_PASSWORD=%s\n' "$(openssl rand -hex 24)" > .env
chmod 600 .env
docker compose config --quiet
docker compose up -d

Publish DNS only to your LAN and do not forward port 53 from the internet. Docker’s published ports can bypass some host firewall rules: enforce LAN restrictions at the router/provider firewall too. For the dashboard, run ssh -N -L 8080:127.0.0.1:8080 user@pihole-host and open http://localhost:8080/admin. Query logs reveal browsing-related metadata; inform other users and choose suitable query-log retention.

What the settings do:

  • FTLCONF_webserver_api_password sets the web interface password. If you leave it out, Pi-hole generates a random one and prints it to the log (docker logs pihole).
  • FTLCONF_dns_listeningMode: 'ALL' is needed with Docker's default bridge network, because queries arrive from outside the container's own subnet.
  • Any setting passed as an FTLCONF_ variable becomes read-only in the web interface, per the Docker configuration docs. Remove the variable to manage that setting in the UI again.
  • The ./etc-pihole volume holds the configuration and databases, so they survive container updates.
  • Using Pi-hole as your DHCP server in Docker needs extra steps: port 67/udp, the NET_ADMIN capability and usually host networking. Follow the Docker docs for that case.

Alternatively, use docker run instead of Compose. First load the generated values into this shell with set -a; . ./.env; set +a. Only source the local file you created yourself; this executes shell input. The equivalent command is:

docker run -d --name pihole \
 -p "${LAN_IP:?Set LAN_IP}:53:53/tcp" -p "${LAN_IP:?Set LAN_IP}:53:53/udp" \
 -p 127.0.0.1:8080:80/tcp -p 127.0.0.1:8443:443/tcp \
 -e TZ=Europe/London \
 -e "FTLCONF_webserver_api_password=${PIHOLE_PASSWORD:?Set PIHOLE_PASSWORD}" \
 -e FTLCONF_dns_listeningMode=ALL \
 -v "$(pwd)/etc-pihole:/etc/pihole" \
 --restart unless-stopped \
 pihole/pihole:2026.09.0

To update, read the release notes, change the tag to the newer release and run docker compose pull followed by docker compose up -d. The Docker tips page recommends against unattended updaters such as Watchtower for Pi-hole, since a failed update takes DNS down for the whole network.

Configuring Your Network

Pi-hole is running, but nothing uses it yet. You have three options.

Option A: Configure Your Router (Best)

In your router's DHCP settings, set the DNS server handed to clients to Pi-hole's address (for example 192.168.1.100). Devices pick up the change when they renew their lease; reconnecting them speeds this up.

Avoid entering a public resolver as the secondary DNS server. Clients don't treat the second entry strictly as a fallback, so some queries would go around Pi-hole. If you want redundancy, run a second Pi-hole and hand out both.

Some ISP-supplied routers don't let you change the DNS server. In that case use option B or C.

Option B: Per-Device Configuration

Set the DNS server manually on each device. It works with any router but is easy to forget on new devices.

Option C: Pi-hole as DHCP Server

Pi-hole can take over DHCP from your router (Settings β†’ DHCP in the web interface). Turn off DHCP on the router first, so there is only one DHCP server on the network. A side benefit is that the query log shows device names instead of bare IP addresses.

The Pi-hole host itself doesn't use Pi-hole for its own lookups unless you configure it to. The post-install docs explain how, and warn that if Pi-hole then fails, the host loses DNS too.

The Admin Dashboard

For the Docker setup above, use the SSH tunnel and http://localhost:8080/admin. On a native LAN installation, use http://pi.hole/admin or the host’s LAN address, and restrict dashboard access to trusted devices. With Pi-hole v6 the web server is built into pihole-FTL; HTTPS on port 443 uses a self-signed certificate unless you supply your own.

The dashboard shows total queries, how many were blocked and the blocked percentage. The Query Log lists every lookup with the client that made it and whether it was blocked. It's the most useful page in the interface, both for seeing what devices contact and for working out why something broke.

Adding More Blocklists

Blocklists are managed under Groups β†’ Lists. Paste a list URL, add it, then rebuild the blocklist database (Pi-hole calls this "gravity") from Tools β†’ Update Gravity or the command line:

pihole updateGravity

Gravity also runs automatically once a week. Two commonly used lists:

  • https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts, a unified hosts file that combines several sources
  • https://raw.githubusercontent.com/crazy-max/WindowsSpyBlocker/master/data/hosts/spy.txt, Windows telemetry domains

Warning: larger and more aggressive lists block more legitimate domains. Add one list at a time and use it for a while before adding the next, so you know which list caused a problem.

When Things Break

Sooner or later a blocklist will catch a domain something on your network needs. Typical signs are an app that opens but can't load content, sign in or play media.

  1. Check the Query Log for blocked domains from that device around the time it failed.
  2. Allow the domain under Groups β†’ Domains, or from the command line with pihole allow example.com.
  3. Pause blocking to confirm Pi-hole is the cause: the Disable Blocking menu has timed options, or run pihole disable 5m.

Keep a note of the domains you allow and why. When you later change lists, you'll know which entries are still needed.

Local DNS Records

Pi-hole can also answer for your own devices, so you can reach a NAS by name instead of by IP address. Add records under Settings β†’ Local DNS Records, for example:

  • nas.home.arpa β†’ 192.168.1.150
  • printer.home.arpa β†’ 192.168.1.55
  • proxmox.home.arpa β†’ 192.168.1.10

home.arpa is the domain reserved for home networks by RFC 8375, so it won't collide with a real public domain. If you run several self-hosted services, the Complete Self-Hosting Guide shows how DNS fits with a reverse proxy and remote access.

Keeping It Updated

Terminal window with the commands from the section Keeping It Updated
Check the versions, update, and rebuild the blocklists.
# Show installed versions of Core, Web and FTL
pihole version

# Update a bare-metal install
pihole updatePihole

# Rebuild the blocklists now instead of waiting for the weekly run
pihole updateGravity

In Docker, pihole updatePihole doesn't apply; update by moving to a newer image tag as shown above. Before larger changes, export a backup from Settings β†’ Teleporter.

Troubleshooting

Terminal window with the commands from the section Troubleshooting
If systemd-resolved holds port 53, turn off its stub listener.

Port 53 is already in use

On Ubuntu (17.10 and later) and Fedora (33 and later), systemd-resolved runs a DNS stub listener on port 53, so Pi-hole can't bind to it. Check what holds the port:

sudo ss -lntup 'sport = :53'

The fix in the Pi-hole Docker tips turns off only the stub listener and keeps systemd-resolved running. It applies to bare-metal installs too:

sudo sh -c 'mkdir -p /etc/systemd/resolved.conf.d && printf "[Resolve]\nDNSStubListener=no\n" | tee /etc/systemd/resolved.conf.d/no-stub.conf'
sudo sh -c 'rm -f /etc/resolv.conf && ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf'
sudo systemctl restart systemd-resolved

The second command points /etc/resolv.conf at the real upstream servers instead of the disabled stub. The Pi-hole docs advise against disabling systemd-resolved entirely, because that can break VPN name resolution and netplan.

Web interface port conflict

If another web server already uses port 80 or 443, a bare-metal Pi-hole tries 8080 and 8443 instead. In Docker, change the host side of the mapping, for example "8080:80/tcp".

Devices still show ads

  • Check which DNS server the device actually received (ipconfig /all on Windows, resolvectl status on systemd-based Linux).
  • Query Pi-hole directly to confirm it answers: nslookup example.com 192.168.1.100. If that works but the device isn't in the Query Log, it isn't using Pi-hole.
  • Browsers with DNS-over-HTTPS turned on send queries to their own provider. Turn it off in the browser, or accept that those lookups bypass Pi-hole.
  • In Docker, if the container runs but nothing gets answers, confirm FTLCONF_dns_listeningMode is set to ALL.

Anything else

pihole status shows whether DNS and blocking are running, and pihole debug collects a diagnostic report you can review or share on the Pi-hole forum.

Pi-hole or AdGuard Home?

Pi-hole won't make browsing faster; it adds a local DNS hop and caches answers. What you get is network-wide blocking for devices that can't run an ad blocker, a query log that shows what each device contacts, and local DNS names for your services.

AdGuard Home does the same job with encrypted DNS and per-client rules built in. The AdGuard Home Deployment guide covers its setup and how the two compare.