Install Pi-hole on Proxmox VE

Why LXC for Pi-hole?

Pi-hole is a DNS server. It needs almost no resources — 50-100 MB of RAM, negligible CPU, a few GB of disk. Running it in a full virtual machine wastes resources. A Proxmox LXC container gives Pi-hole its own isolated environment with native performance and near-zero overhead. LXC is the ideal fit.

The setup: create a Debian or Ubuntu LXC container, install Docker inside it, run Pi-hole in Docker. This gives you container isolation (from LXC), easy upgrades (from Docker), and minimal resource usage (from not running a full VM kernel).

Prerequisites

  • Proxmox VE 8.0+ installed and accessible
  • 1 vCPU available
  • 512 MB RAM available
  • 4 GB disk space available
  • SSH access to the Proxmox host
  • A static IP address for Pi-hole on your network

Create the LXC Container

Download the Template

From the Proxmox web UI, go to your storage (usually local) > CT Templates > Templates and download debian-12-standard. Or from the CLI:

pveam update
pveam download local debian-12-standard_12.7-1_amd64.tar.zst

Create the Container

pct create 200 local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst \
  --hostname pihole \
  --cores 1 \
  --memory 512 \
  --swap 256 \
  --rootfs local-lvm:4 \
  --net0 name=eth0,bridge=vmbr0,ip=192.168.1.53/24,gw=192.168.1.1 \
  --features nesting=1,keyctl=1 \
  --unprivileged 1 \
  --onboot 1 \
  --start 1 \
  --nameserver 1.1.1.1 \
  --password

You will be prompted to set a root password for the container.

Configuration breakdown:

  • --cores 1 — One vCPU is more than enough. Pi-hole barely uses any CPU.
  • --memory 512 — 512 MB RAM. Pi-hole typically uses 50-100 MB. The rest is buffer for Docker and the OS.
  • --rootfs local-lvm:4 — 4 GB disk. Pi-hole’s database, blocklists, and query logs fit comfortably. Increase to 8 GB if you want extended log retention.
  • --net0 ... ip=192.168.1.53/24,gw=192.168.1.1 — Static IP assigned directly in Proxmox. No need to configure networking inside the container. Adjust the IP and gateway to match your network.
  • --features nesting=1,keyctl=1 — Required for running Docker inside the LXC container.
  • --unprivileged 1 — Security best practice. Unprivileged containers cannot access host devices or escalate privileges.
  • --onboot 1 — Start the container automatically when Proxmox boots. Critical for a DNS server — your network depends on it.
  • --nameserver 1.1.1.1 — Temporary DNS for the container during setup. After Pi-hole is running, the container can use Pi-hole itself.

Verify the Container

pct enter 200

Check networking:

ip addr show eth0
ping -c 3 1.1.1.1

Install Docker Inside the LXC

Inside the container:

apt update && apt install -y curl ca-certificates gnupg

# Install Docker
curl -fsSL https://get.docker.com | sh

Verify Docker is running:

docker run --rm hello-world

If Docker fails with overlay filesystem errors, the LXC storage backend may not support it. Solutions:

  1. Use a directory-backed storage for the LXC rootfs (not ZFS or BTRFS)
  2. Or add --storage-driver=vfs to Docker’s configuration (slower but works everywhere)
  3. Or use a privileged container: pct set 200 --unprivileged 0 (less secure)

Deploy Pi-hole

Create the Pi-hole directory and Docker Compose file:

mkdir -p /opt/pihole

Create /opt/pihole/docker-compose.yml:

services:
  pihole:
    container_name: pihole
    image: pihole/pihole:2026.02.0
    ports:
      - "53:53/tcp"
      - "53:53/udp"
      - "80:80/tcp"
      - "443:443/tcp"
    environment:
      TZ: "America/New_York"
      FTLCONF_webserver_api_password: "change-me-to-a-strong-password"
      FTLCONF_dns_upstreams: "1.1.1.1;1.0.0.1"
      FTLCONF_dns_listeningMode: "ALL"
    volumes:
      - ./etc-pihole:/etc/pihole
    cap_add:
      - NET_ADMIN
      - SYS_TIME
      - SYS_NICE
    restart: unless-stopped

Debian 12 does not run systemd-resolved, so there is no port 53 conflict. Pi-hole binds to port 53 without issues.

Start Pi-hole:

cd /opt/pihole
docker compose up -d

Verify it is running:

docker compose logs pihole | tail -20

Test DNS resolution:

dig @127.0.0.1 google.com

Access the admin interface at http://192.168.1.53/admin.

Configuration

Proxmox Network Configuration

The static IP was set during container creation. If you need to change it later, edit from the Proxmox host:

pct set 200 --net0 name=eth0,bridge=vmbr0,ip=192.168.1.53/24,gw=192.168.1.1

Or edit through the Proxmox web UI: Container > Network > eth0 > Edit.

The LXC container’s network goes through the Proxmox bridge (vmbr0). Make sure this bridge is connected to your physical network interface — otherwise the container will not be reachable from other devices.

Proxmox Firewall Rules

If you use Proxmox’s built-in firewall, allow DNS and web traffic to the Pi-hole container.

From the Proxmox web UI: Container 200 > Firewall > Add Rule:

DirectionActionProtocolDest. PortComment
INACCEPTTCP53DNS TCP
INACCEPTUDP53DNS UDP
INACCEPTTCP80Pi-hole web UI
INACCEPTTCP443Pi-hole web UI HTTPS

Or from the CLI, edit /etc/pve/firewall/200.fw:

[RULES]
IN ACCEPT -p tcp -dport 53
IN ACCEPT -p udp -dport 53
IN ACCEPT -p tcp -dport 80
IN ACCEPT -p tcp -dport 443

If the Proxmox firewall is not enabled (default), these rules are not needed — all traffic passes through.

Point Your Network to Pi-hole

Configure your router to use Pi-hole as the DNS server:

  1. Log into your router’s admin panel
  2. Set primary DNS to 192.168.1.53 (the LXC container’s IP)
  3. Do not use a different secondary DNS (it would bypass Pi-hole)
  4. Save and reboot the router

Devices pick up the new DNS server on their next DHCP lease renewal.

Start on Boot Priority

Pi-hole should start as early as possible during Proxmox boot — other VMs and containers may depend on DNS. Set a low startup order number:

From the Proxmox web UI: Container 200 > Options > Start/Shutdown order:

  • Start order: 1 (first to start)
  • Startup delay: 0
  • Shutdown order: 100 (last to stop)

Or from CLI:

pct set 200 --startup order=1,up=0,down=100

This ensures Pi-hole is the first container to start and the last to stop during Proxmox restarts.

Multiple Pi-hole Instances for Redundancy

Proxmox makes it trivial to run multiple Pi-hole instances. DNS is critical infrastructure — if Pi-hole goes down, your entire network loses DNS. Running two instances on separate LXC containers provides fault tolerance.

Create a Second Instance

Clone the first container or create a new one:

pct create 201 local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst \
  --hostname pihole-secondary \
  --cores 1 \
  --memory 512 \
  --swap 256 \
  --rootfs local-lvm:4 \
  --net0 name=eth0,bridge=vmbr0,ip=192.168.1.54/24,gw=192.168.1.1 \
  --features nesting=1,keyctl=1 \
  --unprivileged 1 \
  --onboot 1 \
  --nameserver 1.1.1.1 \
  --password

Install Docker and Pi-hole the same way as the first instance. Set the same upstream DNS and blocklists.

Configure Your Router

Set your router’s DNS:

  • Primary DNS: 192.168.1.53 (Pi-hole #1)
  • Secondary DNS: 192.168.1.54 (Pi-hole #2)

When both are Pi-hole instances, all queries get filtered regardless of which server the device uses.

Keep Blocklists in Sync

Two Pi-hole instances need the same blocklists, whitelists, and settings. Options:

  1. Manual sync — Export settings from Pi-hole #1 via Teleporter, import into Pi-hole #2. Simple but manual.
  2. Orbital Sync — A Docker container that automatically syncs Pi-hole settings between instances:
  orbital-sync:
    container_name: orbital-sync
    image: mattwebbio/orbital-sync:1.10.1
    environment:
      PRIMARY_HOST_BASE_URL: "http://192.168.1.53"
      PRIMARY_HOST_PASSWORD: "your-pihole-password"
      SECONDARY_HOST_1_BASE_URL: "http://192.168.1.54"
      SECONDARY_HOST_1_PASSWORD: "your-pihole-password"
      INTERVAL_MINUTES: 60
    restart: unless-stopped

Run Orbital Sync on either Pi-hole instance or on a separate container.

Platform-Specific Tips

  • LXC vs VM for Pi-hole. There is no reason to use a VM for Pi-hole. An LXC container starts in seconds, uses native performance, and shares the host kernel. A VM adds 512 MB+ RAM overhead for its own kernel and virtual hardware.
  • Resource monitoring. Proxmox shows per-container CPU, RAM, disk, and network usage in the web UI. Pi-hole’s LXC should show <5% CPU and <100 MB RAM under normal operation. If you see more, check if gravity updates are running or if Docker is using excessive resources.
  • Snapshot before upgrades. Before updating Pi-hole (inside the container), take a Proxmox snapshot from the host:
    pct snapshot 200 pre-upgrade --description "Before Pi-hole upgrade"
    Roll back instantly if the upgrade breaks something:
    pct rollback 200 pre-upgrade
  • Disk space monitoring. The 4 GB rootfs is tight. Monitor usage:
    pct exec 200 -- df -h /
    If space runs low, resize from the Proxmox host:
    pct resize 200 rootfs +4G
  • Backups. Use Proxmox’s backup scheduler (Datacenter > Backup) to back up the Pi-hole container. A full container backup at 4 GB compresses to under 500 MB. Schedule daily backups and keep 7 copies.

Troubleshooting

LXC Container Cannot Reach the Internet

Symptom: ping 1.1.1.1 works inside the container but apt update or dig google.com fails.

Fix: DNS is not configured inside the container. Check:

cat /etc/resolv.conf

If empty or pointing to a non-functional server, set it manually:

echo "nameserver 1.1.1.1" > /etc/resolv.conf

Or set it from the Proxmox host:

pct set 200 --nameserver 1.1.1.1

After Pi-hole is running, you can point the container’s DNS to itself (127.0.0.1).

Docker Fails to Start in LXC

Symptom: docker info shows errors about overlay2, storage driver, or cgroup.

Fix: Docker inside LXC requires nesting=1 and keyctl=1 features. Verify:

# From the Proxmox host
pct config 200 | grep features

If not set:

pct stop 200
pct set 200 --features nesting=1,keyctl=1
pct start 200

If overlay2 still fails on ZFS-backed storage, configure Docker to use the vfs or fuse-overlayfs storage driver:

mkdir -p /etc/docker
echo '{"storage-driver": "fuse-overlayfs"}' > /etc/docker/daemon.json
apt install -y fuse-overlayfs
systemctl restart docker

Pi-hole Container Restarts in a Loop

Symptom: docker compose ps shows the Pi-hole container in a restart loop.

Fix: Check logs:

docker compose logs pihole

Common causes:

  1. Port 53 in use — unlikely in Debian LXC, but check: ss -tlnp | grep ':53'
  2. Insufficient memory — if the LXC has less than 256 MB, Pi-hole may OOM. Increase from the Proxmox host:
    pct set 200 --memory 512
  3. Volume permission issues — if the ./etc-pihole directory has wrong permissions:
    chown -R 999:999 /opt/pihole/etc-pihole

Proxmox Host Loses DNS After Pi-hole LXC Stops

Symptom: If the Pi-hole LXC container stops, the Proxmox host itself loses DNS resolution.

Fix: Do not point the Proxmox host’s DNS to the Pi-hole LXC. The host should use external DNS (e.g., 1.1.1.1). Only point your router and client devices to Pi-hole.

Edit the Proxmox host’s DNS: Node > System > DNS — set to 1.1.1.1 or your preferred public DNS.

High Disk Usage in Small LXC

Symptom: The 4 GB rootfs fills up over time.

Fix: Pi-hole’s FTL database (pihole-FTL.db) stores query logs and grows continuously. Limit retention:

  1. In the Pi-hole web UI: Settings > Privacy — set to “Anonymous mode” or limit log retention to 24 hours
  2. Manually clean the database:
    docker compose exec pihole pihole flush
  3. Resize the LXC from the Proxmox host if you need more space:
    pct resize 200 rootfs +4G

Comments