← Back to posts
comparison guide self-hosted · · 13 min read

dnsdist vs PowerDNS Recursor vs Unbound: Self-Hosted DNS Load Balancing Guide 2026

Compare dnsdist, PowerDNS Recursor, and Unbound for self-hosted DNS load balancing. Includes Docker Compose configs, benchmarking tips, and production deployment guides.

OS
Editorial Team

DNS load balancing sits at the foundation of every resilient self-hosted infrastructure. Whether you’re distributing traffic across multiple authoritative name servers, balancing resolver queries to reduce latency, or protecting upstream DNS infrastructure from query floods — the right tool makes the difference between a responsive network and a cascading failure.

Most homelab and self-hosted setups run a single DNS resolver or authoritative server. That works fine until traffic spikes, a server goes down, or you need geographic query distribution. DNS-level load balancing solves all three problems without touching application-layer infrastructure.

In this guide, we compare three proven open-source tools for self-hosted DNS load balancing: dnsdist (by PowerDNS), PowerDNS Recursor, and Unbound. Each takes a different architectural approach, and the right choice depends on whether you need raw query distribution, recursive resolution with smart forwarding, or a validating resolver with failover capabilities.

Why Self-Host DNS Load Balancing?

Running your own DNS load balancer gives you control that cloud DNS services simply cannot match:

  • No vendor lock-in — you own the entire resolution chain, from stub resolver to root hints
  • Geographic distribution — route queries to the nearest data center based on client subnet
  • Query-level policies — rate limit abusive clients, block malicious domains, prioritize internal resolvers
  • High availability — automatic failover when upstream servers become unreachable
  • Cost savings — commercial DNS load balancing services charge per million queries; open-source alternatives run on a $5 VPS
  • Full observability — query logging, real-time metrics, and packet capture for debugging

If you’re already running self-hosted DNS servers like those covered in our DNS resolver comparison, adding a load balancing layer is the natural next step for production reliability.

dnsdist: The Dedicated DNS Load Balancer

dnsdist is a highly flexible, programmable DNS load balancer created by the PowerDNS team. Unlike general-purpose load balancers or full DNS resolvers, dnsdist’s sole purpose is to receive DNS queries and distribute them across backends according to configurable rules.

As of April 2026, dnsdist is part of the PowerDNS/pdns repository, which has 4,345 stars and was last updated on 2026-04-17. The project supports over 40 query routing rules including round-robin, weighted distribution, least-outstanding queries, and Lua-based custom logic.

Key Features

  • Multiple balancing algorithms: round-robin, weighted, least-connections, hashed (by client IP or query name)
  • Lua scripting: full LuaJIT engine for custom query/response manipulation
  • Health checking: active TCP/UDP probes with automatic backend removal on failure
  • Query filtering: block, rewrite, or redirect queries based on QTYPE, QNAME, or client subnet
  • Encryption: native DNS-over-TLS (DoT) and DNS-over-HTTPS (DoH) termination
  • REST API: real-time metrics, backend management, and configuration via HTTP API
  • eBPF support: kernel-level packet filtering for DDoS mitigation at line rate

docker Compose Deployment

The official PowerDNS repository includes a multi-service Docker Compose setup that runs dnsdist alongside an authoritative server and recursor:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
version: '2.0'
services:
  dnsdist:
    image: powerdns/dnsdist:latest
    environment:
      - DNSDIST_API_KEY=your-api-key-here
    links:
      - recursor
      - auth
    ports:
      - "53:53"
      - "53:53/udp"
      - "5199:5199"
      - "8083:8083"
    volumes:
      - ./dnsdist.conf:/etc/dnsdist/dnsdist.conf:ro

  recursor:
    image: powerdns/pdns-recursor:latest
    environment:
      - PDNS_RECURSOR_API_KEY=recursor-key
    ports:
      - "2053:53"
      - "2053:53/udp"

  auth:
    image: powerdns/pdns-auth:latest
    environment:
      - PDNS_AUTH_API_KEY=auth-key
    ports:
      - "1053:53"
      - "1053:53/udp"

dnsdist Configuration Example

A standalone dnsdist configuration for load balancing across multiple upstream resolvers:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
-- dnsdist.conf

-- Define upstream servers with weights
newServer({address="10.0.1.10:53", name="resolver-1", weight=10})
newServer({address="10.0.1.11:53", name="resolver-2", weight=10})
newServer({address="10.0.1.12:53", name="resolver-3", weight=5, checkName="health.example.com"})

-- Set default balancing policy (least outstanding queries)
setServerPolicy(leastOutstanding)

-- Rate limit: max 100 queries/second per /24 subnet
addAction(NetmaskGroupRule("0.0.0.0/0"), QPSAction(100))

-- Block specific domains
addAction(QNameRule("blocked.example.com"), RCodeAction(DNSRCode.REFUSED))

-- Enable web API and dashboard
webserver("0.0.0.0:8083", "your-api-key-here")
setACL({"0.0.0.0/0", "::/0"})

-- Health check interval (seconds)
setServFailWhenNoServer(true)

Performance Profile

dnsdist excels at raw throughput. Benchmarks on commodity hardware show it handling 500,000+ queries per second with sub-millisecond latency for rule evaluation. The eBPF packet filter can drop millions of packets per second before they reach userspace, making it one of the few open-source DNS tools capable of surviving volumetric DDoS attacks without upstream filtering.

PowerDNS Recursor: Smart Forwarding with Built-in Balancing

PowerDNS Recursor is a full recursive DNS resolver that also supports sophisticated query forwarding and load distribution. While not a dedicated load balancer like dnsdist, its forwarding architecture can distribute queries across multiple upstream resolvers with health checking and policy-based routing.

The recursor shares the same GitHub repository as dnsdist (PowerDNS/pdns), benefiting from the same active development cycle and community support.

Key Features

  • Recursive resolution: full DNS recursion with DNSSEC validation built in
  • Forward zones: per-zone forwarding to specific upstream servers with load balancing
  • Lua scripting: custom response policy zones (RPZ) and query manipulation via Lua
  • Tag-based routing: route queries based on client IP, query type, or domain
  • DNSSEC validation: built-in validating resolver — no separatprometheusolver needed
  • Prometheus metrics: native export of query statistics, cache hit rates, and latency histograms

Docker Deployment

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
version: '3.8'
services:
  pdns-recursor:
    image: powerdns/pdns-recursor:latest
    ports:
      - "53:53"
      - "53:53/udp"
      - "8082:8082"
    environment:
      - PDNS_RECURSOR_API_KEY=your-api-key
    volumes:
      - ./recursor.conf:/etc/powerdns/recursor.conf:ro
      - ./recursor.lua:/etc/powerdns/recursor.lua:ro
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "rec_control", "ping"]
      interval: 30s
      timeout: 5s
      retries: 3

PowerDNS Recursor Configuration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# recursor.conf

# Listen on all interfaces
local-address=0.0.0.0

# Forward specific zones to different upstreams
forward-zones=example.com=10.0.1.10;10.0.1.11, internal.corp=10.0.2.10

# Forward everything else to public resolvers (with fallback order)
forward-zones-recurse=.=1.1.1.1;1.0.0.1, .=8.8.8.8;8.8.4.4

# DNSSEC validation
dnssec=validate

# Enable Lua scripting
lua-config-file=/etc/powerdns/recursor.lua

# API for monitoring
api-key=your-api-key
webserver=yes
webserver-address=0.0.0.0
webserver-port=8082

# Performance tuning
threads=4
max-cache-entries=1000000
1
2
3
4
5
6
7
8
9
-- recursor.lua — custom query policy
function preresolve(dq)
    -- Route internal queries to specific resolvers
    if dq.qname:isPartOf(newDN("internal.corp")) then
        dq:addAnswer(pdns.CNAME, newDN("resolver.internal.corp"))
        return true
    end
    return false
end

Unbound: Validating Resolver with Failover

Unbound is a validating, recursive, caching DNS resolver developed by NLnet Labs. While primarily a resolver rather than a load balancer, its forward-zone configuration with multiple targets and health checking provides effective query distribution for self-hosted setups.

As of April 2026, Unbound has 4,442 stars on GitHub and was last updated on 2026-04-17, making it one of the most actively maintained DNS projects in the open-source ecosystem.

Key Features

  • DNSSEC validation: strict validation mode with trust anchor management
  • Forward zones with multiple targets: automatic failover between upstream servers
  • Stub zones: direct queries to authoritative servers for specific domains
  • QNAME minimization: privacy-focused query resolution by default
  • Python module support: extensible response processing via Python plugins
  • Low resource footprint: runs comfortably on a Raspberry Pi with minimal memory

Docker Deployment

Unbound does not provide an official Docker Compose file, but the community-maintained mvance/unbound image and LinuxServer.io’s linuxserver/unbound work well:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
version: '3.8'
services:
  unbound:
    image: mvance/unbound:latest
    container_name: unbound
    ports:
      - "53:53/tcp"
      - "53:53/udp"
    volumes:
      - ./unbound.conf:/opt/unbound/etc/unbound/unbound.conf:ro
    restart: unless-stopped
    cap_add:
      - NET_BIND_SERVICE
    healthcheck:
      test: ["CMD", "drill", "@127.0.0.1", "cloudflare.com"]
      interval: 60s
      timeout: 10s
      retries: 3

Unbound Configuration for Load Balancing

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# unbound.conf

server:
    interface: 0.0.0.0
    port: 53
    do-ip4: yes
    do-ip6: yes
    do-udp: yes
    do-tcp: yes

    # DNSSEC validation
    harden-dnssec-stripped: yes
    trust-anchor-file: /opt/unbound/etc/unbound/root.key

    # Privacy: minimize QNAME exposure
    qname-minimisation: yes
    qname-minimisation-strict: yes

    # Performance
    num-threads: 4
    msg-cache-size: 256m
    rrset-cache-size: 512m
    cache-max-ttl: 86400

    # Access control
    access-control: 10.0.0.0/8 allow
    access-control: 172.16.0.0/12 allow
    access-control: 192.168.0.0/16 allow
    access-control: 0.0.0.0/0 refuse

forward-zone:
    name: "."
    # Multiple targets — Unbound tries them in order and fails over
    forward-addr: 10.0.1.10
    forward-addr: 10.0.1.11
    forward-addr: 1.1.1.1
    forward-addr: 8.8.8.8

# Per-zone forwarding for internal domains
forward-zone:
    name: "internal.corp"
    forward-addr: 10.0.2.10
    forward-addr: 10.0.2.11

Comparison Table

FeaturednsdistPowerDNS RecursorUnbound
Primary roleDNS load balancerRecursive resolverRecursive resolver
Load balancing algorithmsRound-robin, weighted, least-outstanding, hashed, Lua customWeighted forwarding, per-zone routingOrdered failover only
DNSSEC validationPass-throughBuilt-inBuilt-in (strict mode)
Lua scriptingFull LuaJITLua (query hooks)No Lua support
Python scriptingNoNoPython module support
DoT/DoH terminationNativeVia add-onVia add-on (stunnel)
Health checkingActive TCP/UDP probesImplicit (forward failover)Implicit (forward failover)
REST APIYes (built-in)Yes (built-in)No
eBPF supportYesNoNo
RPZ (Response Policy Zones)Via rulesNative (Lua)Via Python module
Prometheus metricsYes (exporter)NativeVia unbound_exporter
GitHub stars4,345 (shared repo)4,345 (shared repo)4,442
Last updated2026-04-172026-04-172026-04-17
LicenseBSDBSDBSD

Choosing the Right Tool

The choice between these three depends on your architecture:

Use dnsdist when:

  • You need dedicated DNS load balancing as a front-end to multiple authoritative or recursive servers
  • You want programmable query routing with Lua scripting
  • You need rate limiting or DDoS mitigation at the DNS layer
  • You want encrypted DNS termination (DoT/DoH) in front of unencrypted backends
  • You need a real-time dashboard and API for monitoring query distribution

dnsdist is the right tool when your DNS infrastructure has grown beyond a single resolver and you need intelligent query distribution. If you’re comparing it to applinginxn-layer solutions, see our HAProxy vs Envoy vs nginx load balancer guide for the HTTP-layer perspective.

Use PowerDNS Recursor when:

  • You need a full recursive resolver with DNSSEC validation AND query forwarding
  • You want per-zone forwarding policies (different upstreams for different domains)
  • You need RPZ-based response filtering for security or content blocking
  • You want a single binary that handles recursion, caching, and forwarding

The recursor is ideal when you’re building a complete DNS resolution stack. For authoritative DNS serving, compare options in our authoritative DNS server guide.

Use Unbound when:

  • You want a lightweight validating resolver with minimal resource usage
  • You need strict DNSSEC validation with QNAME minimization for privacy
  • You want simple ordered failover between upstream resolvers
  • You’re deploying on resource-constrained hardware (Raspberry Pi, small VPS)

Unbound is the simplest option to deploy and maintain. It won’t give you the programmable routing of dnsdist or the per-zone policies of the PowerDNS Recursor, but it handles the core job — reliable, validated recursive resolution — with very little configuration.

Production Architecture: Combining Tools

In production, these tools are not mutually exclusive. The most robust DNS load balancing architectures layer them together:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
                    ┌─────────────┐
   Client queries   │   dnsdist   │  ← DoT/DoH termination, rate limiting,
   (DoT/DoH/clear)  │  (frontend) │     query distribution, eBPF DDoS filter
                    └──────┬──────┘
              ┌────────────┼────────────┐
              ▼            ▼            ▼
        ┌──────────┐ ┌──────────┐ ┌──────────┐
        │ Unbound  │ │ Unbound  │ │ PDNS     │
        │ Node 1   │ │ Node 2   │ │ Recursor │
        └──────────┘ └──────────┘ └──────────┘
              │            │            │
              └────────────┼────────────┘
                   Upstream root/TLD
                   authoritative servers

In this architecture:

  1. dnsdist receives all client queries, terminates encrypted connections, and applies rate limiting
  2. Queries are distributed across multiple Unbound or PowerDNS Recursor instances using least-outstanding algorithm
  3. Each resolver performs DNSSEC validation and maintains its own cache
  4. If one resolver becomes unhealthy, dnsdist automatically removes it from the pool
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# Production Docker Compose: dnsdist + multiple Unbound resolvers
version: '3.8'
services:
  dnsdist:
    image: powerdns/dnsdist:latest
    ports:
      - "53:53"
      - "53:53/udp"
      - "853:853"
      - "443:443"
    volumes:
      - ./dnsdist-prod.conf:/etc/dnsdist/dnsdist.conf:ro
      - ./certs:/etc/dnsdist/certs:ro
    depends_on:
      unbound-1:
        condition: service_healthy
      unbound-2:
        condition: service_healthy

  unbound-1:
    image: mvance/unbound:latest
    volumes:
      - ./unbound-1.conf:/opt/unbound/etc/unbound/unbound.conf:ro
    healthcheck:
      test: ["CMD", "drill", "@127.0.0.1", "cloudflare.com"]
      interval: 30s
      timeout: 10s
      retries: 3

  unbound-2:
    image: mvance/unbound:latest
    volumes:
      - ./unbound-2.conf:/opt/unbound/etc/unbound/unbound.conf:ro
    healthcheck:
      test: ["CMD", "drill", "@127.0.0.1", "cloudflare.com"]
      interval: 30s
      timeout: 10s
      retries: 3

Monitoring and Alerting

DNS load balancers are only useful if you can verify they’re working. Here’s how to monitor each tool:

dnsdist Metrics

dnsdist exposes a built-in web dashboard and a Prometheus-compatible metrics endpoint:

1
2
3
4
5
6
# Query dnsdist metrics endpoint
curl -s http://localhost:8083/metrics | grep dnsdist

# Check backend health via API
curl -s -H "X-API-Key: your-api-key" http://localhost:8083/api/v1/servers/localhost | \
  python3 -c "import sys,json; data=json.load(sys.stdin); print(json.dumps(data['servers'], indent=2))"

PowerDNS Recursor Monitoring

The recursor exposes Prometheus metrics natively:

1
2
3
4
5
6
# prometheus.yml scrape config
scrape_configs:
  - job_name: 'pdns-recursor'
    static_configs:
      - targets: ['pdns-recursor:8082']
    metrics_path: '/metrics'

Key metrics to alert on: recursor_queries, recursor_cache_hits, recording_servfails, and recursor_sysfd-queries.

Unbound Monitoring

Use unbound_exporter to expose Prometheus metrics:

1
2
3
4
5
6
7
# Run the exporter alongside Unbound
docker run -d \
  --name unbound-exporter \
  -p 9167:9167 \
  -e UNBOUND_HOST=unbound \
  -e UNBOUND_PORT=8953 \
  kumina/unbound_exporter:latest

For a broader monitoring perspective that includes DNS health checks alongside other infrastructure metrics, see our Datadog alternative guide.

FAQ

What is the difference between DNS load balancing and DNS-based application load balancing?

DNS load balancing distributes DNS queries across multiple resolver or authoritative servers to improve reliability and performance. DNS-based application load balancing (like round-robin A records) returns different IP addresses to clients to distribute application traffic. They operate at different layers — the former balances the DNS infrastructure itself, the latter uses DNS as a mechanism to balance application servers.

Can I use HAProxy for DNS load balancing?

HAProxy can load balance TCP and UDP traffic, including DNS on port 53. However, it lacks DNS-aware features like query inspection, response policy zones, health checking based on DNS response codes, and programmable routing rules. For basic TCP/UDP failover, HAProxy works. For DNS-specific intelligence, use dnsdist.

How does dnsdist handle health checking for backend servers?

dnsdist sends periodic DNS queries (configurable query name and type) to each backend server over both TCP and UDP. If a backend fails to respond within the timeout (default 1 second), it is marked as down and removed from the balancing pool. When the backend recovers, it is automatically re-added. You can configure separate check intervals per backend and use different check queries for different backends.

Is it safe to run dnsdist as the public-facing DNS server?

Yes. dnsdist is designed to be the first point of contact for DNS queries. It supports eBPF-based packet filtering to drop volumetric attacks before they consume CPU, rate limiting per subnet or client, query validation to drop malformed packets, and DoT/DoH termination to encrypt client traffic. Many large DNS operators use dnsdist as their public-facing layer.

Can I combine dnsdist with PowerDNS Recursor for both load balancing and recursion?

Absolutely. This is one of the most common production patterns. dnsdist handles query distribution, rate limiting, and encryption termination at the front end, while PowerDNS Recursor (running behind dnsdist) handles recursive resolution, DNSSEC validation, and caching. The two tools complement each other — dnsdist’s newServer() directives point to the recursor instances, and the recursor’s local-address binds only to the internal network.

Does Unbound support weighted load balancing like dnsdist?

No. Unbound’s forward-zone configuration uses ordered failover — it tries the first forward-addr entry, and only falls back to the next one if the first is unreachable. It does not support weighted distribution, round-robin, or least-outstanding algorithms. If you need weighted balancing with Unbound as the backend, place dnsdist in front and use Unbound instances as dnsdist backends.

How do I benchmark DNS load balancer performance?

Use tools like dnsperf (from BIND utilities) or queryperf to generate load and measure throughput, latency, and error rates:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Install dnsperf
apt install dnsperf

# Create a query file
echo "example.com A" > queries.txt
for i in $(seq 1 999); do echo "example.com A" >> queries.txt; done

# Run benchmark against dnsdist
dnsperf -s 127.0.0.1 -d queries.txt -c 50 -T 4 -l 60

# Results show queries/second, average latency, and response codes

Run benchmarks with varying concurrency levels (-c) and thread counts (-T) to find the saturation point of your configuration.

Advertise here