Tutorial

Pi-hole v6 on a VPS: block ads and trackers at the DNS level

Security & Monitoring13 min read8 steps

You run a dozen services on your VPS — n8n, Nextcloud, Grafana, a few APIs — and each of them keeps reaching out to tracking and advertising domains. Pi-hole v6, released in February 2025, brings a complete architectural rewrite: no more PHP or lighttpd, a web server embedded directly in the FTL binary, and a native REST API that simplifies integration into a Docker stack. This guide takes you from zero to a working DNS resolver on your VPS, secured against public exposure.

Contents· Why run your own Pi-hole DNS resolver on a VPS1/10
  1. 01Why run your own Pi-hole DNS resolver on a VPS
  2. 02What Pi-hole concretely brings to a self-hosted stack
  3. 03Pi-hole v6 — what changes for self-hosters
  4. 04VPS prerequisites before you start
  5. 05Pi-hole v6 deployment with Docker Compose: complete procedure
  6. 06Post-installation configuration
  7. 07Pi-hole v6 vs AdGuard Home — 2026 comparison
  8. 08Hardening: rules not to overlook
  9. 09Troubleshooting: common errors
  10. 10Integrating Pi-hole into your security stack

Why run your own Pi-hole DNS resolver on a VPS

A self-hosted DNS resolver on a VPS is not just for homelabs. Once you operate multiple containers and services on the same server, it becomes a central network control point: every DNS query goes through Pi-hole before reaching the internet, giving you visibility and control that public resolvers cannot offer.

The argument for a VPS over a local Raspberry Pi is straightforward. The VPS runs 24/7, is reachable from any node on your Docker network or your VPN mesh, and does not depend on the availability of your home network. For a developer administering multiple servers or working remotely, this is the sensible location.

What Pi-hole concretely brings to a self-hosted stack

  • Network-wide blocking: every query to ad, tracking or malware domains is blocked before the TCP connection is even established — for all containers on the Docker network, without modifying each application.
  • Centralised DNS logs: a single dashboard shows all DNS queries from your infrastructure, making it easier to debug an application that contacts an unexpected external service.
  • Bandwidth reduction: blocked queries generate no network response. On a VPS with a bandwidth quota, this is a measurable saving for heavy stacks.
  • DNS query privacy: by pairing Pi-hole with Unbound as a local recursive resolver, your queries no longer go through a third-party resolver — they query authoritative DNS servers directly.
  • Self-hosted stack integration: Pi-hole acts as a local DNS server for your services, allowing you to create custom DNS entries (grafana.myserver.local) without editing /etc/hosts on every machine.
  • Community blocklists: the Pi-hole ecosystem has one of the richest maintained blocklist communities — Hagezi, oisd, Steven Black — updated automatically.
  • Non-breaking v6 upgrade: Pi-hole v6 maintains backward compatibility with v5 clients; migration does not interrupt service.

Pi-hole v6 — what changes for self-hosters

Pi-hole v6 was announced on February 18, 2025 on pi-hole.net. This is a major rewrite, not an incremental update.

PHP and lighttpd removed. Version 5 relied on lighttpd as a web server and PHP for the admin interface. In v6, the pihole-FTL binary directly embeds a Lua-based web server. Result: the Docker image is lighter, there are no separate dependencies to manage, and the attack surface is reduced.

New native REST API. The v6 API is documented and versioned. It exposes statistics, list management and configuration directly from http://<ip>/api/. In a Docker Compose stack, this enables automating Pi-hole management from a script or from n8n without workarounds.

FTLCONF_* environment variables. The configuration schema has changed. The v5 WEBPASSWORD variable is replaced by FTLCONF_webserver_api_password. All FTL configuration options are now exposed via FTLCONF_<section>_<key> variables, making the docker-compose.yml self-contained and readable.

Basic / Expert mode. The v6 interface separates essential settings (Basic mode) from advanced options (Expert mode). For a VPS deployment, Expert mode gives access to DNS listening interface control and security parameters.

Antigravity (subscription allowlists). Mirroring Gravity for blocklists, Antigravity allows subscribing to community-maintained allowlists — useful for avoiding false positives on legitimate domains.

Since the v6 launch in February 2025, several updates have followed: FTL v6.5 in February 2026, FTL v6.6 in April 2026, FTL v6.6.1 in April 2026 with security fixes. The official Docker image is tagged 2026.06.0 for the latest June 2026 release.

VPS prerequisites before you start

Pi-hole v6 is designed to be lightweight. The requirements are significantly lower than those of a SIEM or a monitoring stack.

Minimum recommended resources:
- 512 MB of RAM is sufficient for moderate use (a few containers, fewer than 10,000 requests/hour). Plan for 1 GB for heavy use or if you enable extended query history.
- 1 vCPU is enough. Pi-hole FTL is a single, efficient process.
- 4 GB of disk minimum for the image and query databases. The SQLite database storing the query log grows with request volume.
- Root access to the VPS to manage Docker and network configuration.

Network ports to consider:
- 53/UDP and 53/TCP: DNS port. Do not expose publicly — this is the most important rule in this guide (see the security section).
- 80/TCP and 443/TCP: admin web interface, to be exposed only behind a reverse proxy with authentication.

Software prerequisites:
- Docker Engine 24.0+ and Docker Compose v2 (docker compose, without hyphen).
- Operating system: Debian 12 or Ubuntu 22.04/24.04 LTS.

Pre-check — port 53:
On recent Debian/Ubuntu systems, systemd-resolved listens on port 53. This is the most common cause of failure on first Pi-hole startup. Check and disable if needed:

ss -tlunp | grep ':53'
systemctl disable --now systemd-resolved

If you disable systemd-resolved, ensure /etc/resolv.conf points to a working resolver while deploying:

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

Pi-hole v6 deployment with Docker Compose: complete procedure

  1. Prepare the server and install Docker

    Update the system and install Docker Engine from the official repository:

    apt-get update && apt-get upgrade -y
    curl -fsSL https://get.docker.com | sh
    docker --version && docker compose version

    Enable Docker at boot and verify Docker Compose v2 responds (command is docker compose, without hyphen):

    systemctl enable --now docker
  2. Create the directory structure

    Create a dedicated directory and persistent volumes for Pi-hole configuration and databases:

    mkdir -p /opt/pihole/etc-pihole
    cd /opt/pihole

    These directories persist FTL configuration, Gravity lists and the query log. Without them, every container recreation starts from scratch.

  3. Write the Pi-hole v6 docker-compose.yml

    Create /opt/pihole/docker-compose.yml with the following configuration. Note the use of FTLCONF_webserver_api_password (v6 variable) and FTLCONF_dns_listeningMode to work with the Docker bridge network:

    services:
      pihole:
        container_name: pihole
        image: pihole/pihole:2026.06.0
        ports:
          - "127.0.0.1:53:53/tcp"
          - "127.0.0.1:53:53/udp"
          - "127.0.0.1:8080:80/tcp"
        environment:
          TZ: 'Europe/Paris'
          FTLCONF_webserver_api_password: 'change-this-password'
          FTLCONF_dns_listeningMode: 'ALL'
          FTLCONF_dns_upstreams: '1.1.1.1;8.8.8.8'
        volumes:
          - './etc-pihole:/etc/pihole'
        cap_add:
          - SYS_NICE
        restart: unless-stopped

    Critical point: 127.0.0.1:53 binds the DNS port to the host loopback interface only. The resolver is accessible from the server itself and from the internal Docker network, but not from the internet.

  4. Start Pi-hole and verify its status

    Launch the container in the background and verify it is healthy:

    docker compose up -d
    docker compose ps
    docker compose logs pihole | tail -30

    On first start, Pi-hole downloads the Gravity lists (a few seconds). The web interface is available at http://127.0.0.1:8080/admin from the server itself. If you see Pi-hole blocking is enabled, the deployment is working.

  5. Configure DNS on the internal Docker network

    For your containers to use Pi-hole as their DNS resolver, define dns in each service of your other Docker Compose stacks, or configure the Docker daemon globally.

    Option A — per service (recommended for existing stacks):

    services:
      my-app:
        image: my-image
        dns:
          - 172.17.0.1

    172.17.0.1 is the IP of the default Docker bridge network gateway, which corresponds to the host interface where Pi-hole listens.

    Option B — global Docker daemon (/etc/docker/daemon.json):

    {
      "dns": ["172.17.0.1", "1.1.1.1"]
    }

    Restart Docker after modification: systemctl restart docker. The second resolver 1.1.1.1 is a fallback if Pi-hole is stopped.

  6. Expose the dashboard via an HTTPS reverse proxy

    Never expose the Pi-hole dashboard directly on public port 80. Use nginx as a reverse proxy with a Let's Encrypt certificate:

    server {
        listen 443 ssl;
        server_name pihole.your-domain.com;
    
        ssl_certificate /etc/letsencrypt/live/pihole.your-domain.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/pihole.your-domain.com/privkey.pem;
    
        location / {
            proxy_pass http://127.0.0.1:8080;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
        }
    }

    Obtain the certificate with Certbot: certbot --nginx -d pihole.your-domain.com. The Pi-hole authentication (password defined in FTLCONF_webserver_api_password) remains the only entry point.

  7. Secure the resolver against public exposure

    An open DNS resolver on the internet is a DDoS amplification vector and can be used by anyone. Verify that port 53 is not reachable from outside.

    Check from a remote machine:

    nmap -sU -p 53 <YOUR_VPS_IP>

    The port should be filtered or closed. If it is open, your resolver is public.

    Close port 53 with ufw:

    ufw deny 53/udp
    ufw deny 53/tcp
    ufw allow from 172.16.0.0/12 to any port 53

    The allow from 172.16.0.0/12 rule allows internal Docker networks while blocking external traffic.

    Cleaner alternative with docker-compose.yml binding: the 127.0.0.1:53:53 configuration in Step 3 is the preferred method — Docker does not forward the port externally when bound to 127.0.0.1. Also verify that your cloud firewall (security group, VPS firewall) does not expose port 53.

  8. Add blocklists and enable automatic updates

    The Pi-hole v6 interface > Lists allows adding lists by URL. Recommended lists to add after installation:

    - Hagezi Multi Pro: https://raw.githubusercontent.com/hagezi/dns-blocklists/main/adblock/pro.txt
    - oisd Big: https://big.oisd.nl/
    - Steven Black Unified: https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts

    After adding, trigger a Gravity update:

    docker exec pihole pihole -g

    To automate the weekly update, add a cron entry on the host:

    0 3 * * 0 docker exec pihole pihole -g >> /var/log/pihole-gravity.log 2>&1

Post-installation configuration

Once Pi-hole is operational and your services point to it, a few adjustments increase its day-to-day usefulness.

Custom DNS for internal services. In Pi-hole > Local DNS, you can create A records that resolve local names: grafana.local → 127.0.0.1, n8n.local → 127.0.0.1. This replaces /etc/hosts modifications on each machine.

Adjust the logging level. By default, Pi-hole retains 24 hours of query log. To extend to 7 days or reduce to limit disk usage: in Settings > System, modify the Query log option. On a VPS with limited storage, disabling detailed logs (while keeping statistics) is a viable option.

Dashboard and statistics. The v6 dashboard shows in real time: the percentage of blocked queries, the most requested domains, the most active clients. This data is useful for identifying a container making unusual requests (callbacks to a telemetry service, massive resolution of random names).

Allowlisting false positives. Some blocklists are aggressive and block legitimate domains. Pi-hole > Domains > Allow lets you add exceptions without touching the lists. System update domains (apt.releases.ubuntu.com, etc.) are generally already excluded by well-maintained lists, but check if an internal service stops working after activating a new list.

Pi-hole v6 vs AdGuard Home — 2026 comparison

Scroll the table

CriterionPi-hole v6AdGuard Home
ArchitectureFTL binary with integrated Lua web server — no more PHP or lighttpd since v6 (February 2025)Single Go binary, multi-platform (Linux, Windows, macOS, OpenWrt, FreeBSD)
Encrypted DNS (DoH/DoT/DoQ)Not built-in — requires a sidecar Unbound or cloudflared container for DoH/DoTBuilt-in natively — DoH, DoT and DoQ available without additional configuration
Memory footprint~70-150 MB under normal load, depending on query volume and enabled history~50-100 MB; slightly lighter on simple configurations without extended history
Community and listsLargest ecosystem: hundreds of compatible lists (hosts and adblock format), active forums, extensive documentationCompatible with adblock format lists (uBlock Origin); growing ecosystem but younger
API and automationNative documented REST API in v6; full management via `FTLCONF_*` environment variablesREST API available; configuration via YAML file or web interface
Per-client rulesPer-client filtering (IP or network name), without native granular per-device rulesPer-client and per-group rules natively, built-in parental controls

Hardening: rules not to overlook

Never expose port 53 publicly. This is the main risk of a DNS resolver on a VPS. An open port 53 allows anyone to use your server as a resolver — and potentially as a DNS amplification vector in a DDoS attack. Check regularly with nmap -sU -p 53 <VPS_IP> from outside.

Change the default password. The FTLCONF_webserver_api_password variable in the Compose must contain a strong password. If you omit it, Pi-hole generates a random password and displays it in the logs on first start — convenient for testing, unacceptable in production.

Update the image regularly. Pi-hole v6 releases in 2026 included security fixes (local privilege escalation vulnerability in April 2026, web interface XSS corrections). Add a periodic check:

docker compose pull && docker compose up -d

No DHCP in production on a VPS. Pi-hole's DHCP feature is designed for local networks. On a VPS it serves no purpose, and enabling it accidentally (cap_add: NET_ADMIN) can create network conflicts with the host infrastructure.

Also check your cloud provider's firewall. Beyond ufw and the 127.0.0.1:53 binding, verify that the VPS control panel or security group does not expose port 53 externally — some providers open all ports by default until you configure their network-level firewall. Two layers of protection are better than one.

Troubleshooting: common errors

Here are the most common issues when deploying Pi-hole v6 on a VPS, with exact causes and fixes.

1. Port 53 is already in use — bind: address already in use
Cause: systemd-resolved listens on 127.0.0.53:53. Check with ss -tlunp | grep ':53'. Fix: disable systemd-resolved (systemctl disable --now systemd-resolved) and replace /etc/resolv.conf with a static file pointing to 1.1.1.1 during deployment. On Ubuntu 22.04+, the recommended procedure is to replace the /etc/resolv.conf symlink with a static file.

2. DNS queries from containers do not go through Pi-hole
Cause: containers use Docker's default resolver (127.0.0.11), not Pi-hole. Check from a container: docker exec <container> cat /etc/resolv.conf. If it shows 127.0.0.11, the dns: option is not configured in your Compose or in /etc/docker/daemon.json.

3. Dashboard inaccessible after startup
Common cause: port 8080 is bound to 127.0.0.1 (not reachable from outside) but the reverse proxy is not yet configured. Check locally: curl http://127.0.0.1:8080/admin/. If it responds, the problem is with the reverse proxy or TLS certificate.

4. FTLCONF_webserver_api_password ignored after container recreation
Cause: Pi-hole stores configuration in /etc/pihole/pihole.toml. If this file already exists in the ./etc-pihole volume with an old password, the environment variable does not override it. Fix: either delete the pihole.toml file (loss of config), or change the password from the web interface.

5. Gravity update fails — Could not access the internet
Cause: the Pi-hole container cannot resolve the list URLs, usually because FTLCONF_dns_upstreams is not configured or the container itself uses Pi-hole as its resolver (loop). Verify that FTLCONF_dns_upstreams points to an external resolver (1.1.1.1;8.8.8.8) in your Compose.

Integrating Pi-hole into your security stack

Pi-hole is a DNS filtering layer, not an intrusion detection system. Its scope is precise: it acts on domain name queries before the connection is established. It complements other tools rather than replacing them.

Paired with Wazuh or CrowdSec, Pi-hole handles preventive filtering while the other tools analyse network and system behaviour in real time. A compromised container reaching out to a known command-and-control domain will be blocked by Pi-hole — and the absence of a DNS response can trigger an alert in Wazuh if you have configured Pi-hole log monitoring.

Paired with NetBird or WireGuard, Pi-hole can become the DNS resolver for your entire private VPN mesh. Every device connected to the VPN then benefits from filtering, including from a remote workstation.

To host Pi-hole on a VPS with full root access, dedicated IPv4 and Docker pre-installed, see ServOrbit VPS plans. Pi-hole v6 runs comfortably on an entry-level plan — the resources consumed are modest and leave room for the rest of your stack.

Related articles: automatic security updates on Debian/Ubuntu VPS, port-free VPN mesh with NetBird, Wazuh open source SIEM on VPS.

Your VPS for Pi-hole and your self-hosted stack

Root access, dedicated IPv4, Docker pre-installed. Pi-hole v6 runs comfortably on an entry-level plan and leaves room for the rest of your infrastructure.

Need help?

Browse our help center and FAQ, or reach our team — callback, WhatsApp or email. Support in French, English and Arabic.

Message us on WhatsAppopens in a new tab