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

Self-Hosted DNS Server: PowerDNS vs BIND vs Unbound vs CoreDNS 2026

Compare and configure self-hosted DNS servers: PowerDNS for authoritative hosting, BIND as the classic standard, Unbound for recursive resolving, and CoreDNS for cloud-native environments.

OS
Editorial Team

Why Run Your Own DNS Server?

DNS is the backbone of every network interaction — translating human-readable domain names into IP addresses. Relying entirely on your ISP’s or a third-party provider’s DNS servers means surrendering visibility and control over a critical piece of your infrastructure.

Running your own DNS server gives you several tangible benefits:

  • Full ownership of your domains: Authoritative DNS servers let you host your own zone files, manage records, and control TTLs without depending on a registrar’s interface or paying for premium DNS hosting.
  • Internal name resolution: A local recursive or caching DNS server speeds up lookups for your entire network, reduces external query volume, and lets you define custom internal domains (e.g., app.internal, db.local).
  • Split-horizon DNS: Serve different answers to internal vs. external queries — expose internal IPs for employees while returning public IPs to the rest of the world.
  • Resilience and independence: If your upstream DNS provider goes down, your local server keeps answering cached queries. In self-hosted setups, DNS is one of the last services you want to outsource.
  • Compliance and auditing: Some industries require you to log DNS queries for security audits. Self-hosted DNS gives you full query logs without handing them to a third party.

This guide covers four of the most widely used DNS server software options: BIND (the internet’s original DNS server), PowerDNS (a modern authoritative server with database backends), Unbound (a focused recursive resolver), and CoreDNS (a cloud-native, plugin-based DNS server). Each serves a different purpose, and in practice, many production setups combine them.


Quick Comparison at a Glance

FeatureBIND 9PowerDNSUnboundCoreDNS
Primary roleAuthoritative + RecursiveAuthoritativeRecursiveAuthoritative + Proxy
Written inCC++CGo
Backend storageZone files, DLZMySQL, PostgreSQL, SQLite, BIND zone files, LMDB— (recursive only)Plugins (file, etcd, kubernetes, MySQL, etc.)
DNSSECYes (signer + validator)Yes (validator + signer)Yes (validator)Yes (via plugin)
API/ControlRNDC (remote control)REST API (pdns_server)unbound-controlHTTP/metrics endpoint
IPv6Full supportFull supportFull supportFull support
Config languageNamed.conf (declarative)pdns.conf + SQL schemasunbound.confCorefile (plugin chain)
docker-friendlyModerateGoodExcellentExcellent
Best forTraditional DNS, learningMulti-domain hosting, API-drivenRecursive caching, DNSSEC validationKubernetes, service discovery, cloud-native

BIND 9: The Internet’s Original DNS Server

BIND (Berkeley Internet Name Domain) has been the reference implementation of DNS since 1984. It powers a significant portion of the internet’s authoritative name servers and remains the most feature-complete DNS server available.

When to Choose BIND

  • You need a battle-tested, standards-compliant DNS server
  • You want both authoritative and recursive capabilities in one package
  • You’re managing traditional zone files and prefer file-based configuration
  • You need advanced DNS features like views (split-horizon), TSIG, and DNSSEC signing

Installation

Ubuntu/Debian:

1
2
sudo apt update
sudo apt install bind9 bind9utils bind9-dnsutils

Docker:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
version: "3.8"
services:
  bind9:
    image: ubuntu/bind9:latest
    container_name: bind9
    ports:
      - "53:53/tcp"
      - "53:53/udp"
    environment:
      - BIND9_NO_ROOT_WARNINGS=yes
    volumes:
      - ./config:/etc/bind
      - ./zones:/var/cache/bind
    restart: unless-stopped

Basic Configuration

The main configuration file is /etc/bind/named.conf (or /etc/bind/named.conf.local for user-defined zones):

 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
// /etc/bind/named.conf.local

// Authoritative zone
zone "example.com" {
    type master;
    file "/etc/bind/zones/db.example.com";
    allow-transfer { 10.0.0.2; };  // Secondary DNS server
};

// Reverse DNS zone
zone "0.168.192.in-addr.arpa" {
    type master;
    file "/etc/bind/zones/db.192.168.0";
};

// Forwarders for recursive queries
options {
    forwarders {
        8.8.8.8;
        1.1.1.1;
    };
    dnssec-validation auto;
    allow-query { any; };
    allow-recursion { 192.168.0.0/24; };
};

A typical zone file (/etc/bind/zones/db.example.com):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
$TTL 86400
@   IN  SOA ns1.example.com. admin.example.com. (
            2026041301  ; Serial (YYYYMMDDNN)
            3600        ; Refresh
            1800        ; Retry
            604800      ; Expire
            86400       ; Minimum TTL
)

@       IN  NS      ns1.example.com.
@       IN  NS      ns2.example.com.
@       IN  A       192.168.1.10
@       IN  AAAA    2001:db8::10

ns1     IN  A       192.168.1.10
ns2     IN  A       192.168.1.11
www     IN  A       192.168.1.20
mail    IN  A       192.168.1.30
@       IN  MX  10  mail.example.com.
@       IN  TXT     "v=spf1 mx -all"

Managing BIND

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Check configuration syntax
sudo named-checkconf

# Validate a zone file
sudo named-checkzone example.com /etc/bind/zones/db.example.com

# Reload configuration without restart
sudo rndc reload

# View server status
sudo rndc status

# Flush the cache
sudo rndc flush

# Query logs (journalctl)
sudo journalctl -u bind9 -f

DNSSEC Signing with BIND

BIND includes dnssec-keygen and dnssec-signzone for signing zones:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Generate ZSK (Zone Signing Key)
dnssec-keygen -a RSASHA256 -b 1024 -n ZONE example.com

# Generate KSK (Key Signing Key)
dnssec-keygen -f KSK -a RSASHA256 -b 2048 -n ZONE example.com

# Sign the zone
dnssec-signzone -o example.com -k Kkey.* /etc/bind/zones/db.example.com

# The signed zone file is db.example.com.signed — update named.conf to point to it

PowerDNS: Modern Authoritative DNS with Database Backends

PowerDNS is a high-performance authoritative DNS server that stands out for its ability to store zone data in SQL databases (MySQL, PostgreSQL, SQLite) instead of zone files. This makes it ideal for environments where DNS records need to be managed programmatically or through a web interface.

When to Choose PowerDNS

  • You want to manage DNS records through a database or REST API
  • You need to host hundreds or thousands of domains with dynamic updates
  • You want integration with management interfaces like PowerDNS-Admin or the official PowerDNS UI
  • You need high availability with database-backed replication

Installation

Ubuntu/Debian (Authoritative Server):

1
2
sudo apt update
sudo apt install pdns-server pdns-backend-pgsql

Docker with PostgreSQL Backend:

 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
version: "3.8"
services:
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: powerdns
      POSTGRES_USER: pdns
      POSTGRES_PASSWORD: ${PDNS_DB_PASSWORD}
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U pdns"]
      interval: 5s
      retries: 5

  powerdns:
    image: pschiffe/pdns-mysql:4.8  # or pdns-postgresql variant
    container_name: powerdns
    ports:
      - "53:53/tcp"
      - "53:53/udp"
      - "8081:8081"  # API port
    environment:
      - PDNS_gmysql_host=postgres
      - PDNS_gmysql_port=3306
      - PDNS_gmysql_dbname=powerdns
      - PDNS_gmysql_user=pdns
      - PDNS_gmysql_password=${PDNS_DB_PASSWORD}
      - PDNS_api=yes
      - PDNS_api-key=${PDNS_API_KEY}
      - PDNS_webserver=yes
      - PDNS_webserver-address=0.0.0.0
      - PDNS_webserver-password=${PDNS_WEB_PASSWORD}
      - PDNS_webserver-allow-from=0.0.0.0/0
    depends_on:
      postgres:
        condition: service_healthy
    restart: unless-stopped

volumes:
  pgdata:

Database Schema Setup

If using PostgreSQL, initialize the schema:

1
2
3
4
5
6
# Connect to the database
psql -U pdns -d powerdns -h localhost

# PowerDNS provides schema files
# For PostgreSQL:
\i /usr/share/doc/pdns-backend-pgsql/schema.pgsql.sql

Managing Records via REST API

PowerDNS has a built-in REST API for managing zones and records:

 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
# Create a new zone
curl -s -X POST "http://localhost:8081/api/v1/servers/localhost/zones" \
  -H "X-API-Key: ${PDNS_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "example.com.",
    "kind": "Native",
    "masters": [],
    "nameservers": ["ns1.example.com.", "ns2.example.com."]
  }'

# Add an A record
curl -s -X PATCH "http://localhost:8081/api/v1/servers/localhost/zones/example.com." \
  -H "X-API-Key: ${PDNS_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "rrsets": [{
      "name": "www.example.com.",
      "type": "A",
      "ttl": 300,
      "changetype": "REPLACE",
      "records": [{
        "content": "192.168.1.20",
        "disabled": false
      }]
    }]
  }'

# List all zones
curl -s "http://localhost:8081/api/v1/servers/localhost/zones" \
  -H "X-API-Key: ${PDNS_API_KEY}" | jq '.[].name'

PowerDNS-Admin (Web UI)

For a web-based management interface, PowerDNS-Admin is the most popular option:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# Add to your docker-compose.yml:
  powerdns-admin:
    image: ngoduykhanh/powerdns-admin:latest
    container_name: powerdns-admin
    ports:
      - "9191:80"
    environment:
      - SQLALCHEMY_DATABASE_URI=mysql://pda:pda@mysql-server/pda
      - GUNICORN_TIMEOUT=60
      - GUNICORN_WORKERS=2
    depends_on:
      - mysql-server
    restart: unless-stopped

Unbound: The Focused Recursive Resolver

Unbound is a validating, recursive, caching DNS resolver developed by NLnet Labs. Unlike BIND and PowerDNS, Unbound does not serve authoritative zones — it focuses exclusively on resolving queries from clients by recursively querying upstream servers.

When to Choose Unbound

  • You want a fast, secure recursive DNS resolver for your network
  • You need DNSSEC validation (Unbound has one of the most robust validators)
  • You want to run a local caching resolver to reduce external DNS latency
  • You’re building a privacy-focused DNS setup (pairs well with stubby or dnscrypt-proxy)

Installation

Ubuntu/Debian:

1
2
sudo apt update
sudo apt install unbound unbound-anchor unbound-host

Docker:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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
    cap_add:
      - NET_BIND_SERVICE
    restart: unless-stopped

Configuration

The main configuration file is /etc/unbound/unbound.conf:

 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
44
45
46
47
server:
  # Listen on all interfaces
  interface: 0.0.0.0
  interface: ::0
  port: 53

  # Access control — only allow local network
  access-control: 127.0.0.0/8 allow
  access-control: 192.168.0.0/16 allow
  access-control: 10.0.0.0/8 allow
  access-control: 0.0.0.0/0 refuse

  # DNSSEC validation
  auto-trust-anchor-file: "/var/lib/unbound/root.key"
  dnssec: yes
  dnssec-log-bogus: yes

  # Privacy: minimize query information
  qname-minimisation: yes
  harden-algo-downgrade: yes
  harden-below-nxdomain: yes
  harden-referral-path: yes

  # Performance tuning
  num-threads: 2
  msg-cache-size: 64m
  rrset-cache-size: 128m
  cache-max-ttl: 86400
  cache-min-ttl: 300

  # Logging
  verbosity: 1
  log-queries: no
  log-replies: no
  log-tag-queryreply: no

  # Forwarding (optional — use instead of recursion)
  # forward-zone:
  #   name: "."
  #   forward-addr: 1.1.1.1@853  # Cloudflare DoT
  #   forward-addr: 8.8.8.8@853  # Google DoT
  #   forward-tls-upstream: yes

remote-control:
  control-enable: yes
  control-interface: 127.0.0.1
  control-port: 8953

Managing Unbound

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# Update the root trust anchor (DNSSEC)
sudo unbound-anchor

# Check configuration syntax
sudo unbound-checkconf

# Restart the service
sudo systemctl restart unbound

# Flush the cache
sudo unbound-control flush_zone example.com

# View cache statistics
sudo unbound-control stats

# Dump the cache to a file
sudo unbound-control dump_cache > cache_dump.txt

# Query a domain through Unbound
dig @127.0.0.1 example.com A +dnssec

Pairing Unbound with Stubby (DoT Upstream)

For maximum privacy, use Unbound as a local cache and Stubby for encrypted upstream resolution:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
sudo apt install stubby

# /etc/stubby/stubby.yml
dns:
  listen_addresses: 127.0.0.1@5353
  upstream_recursive_servers:
    - address_data: 1.1.1.1
      tls_auth_name: "cloudflare-dns.com"
    - address_data: 1.0.0.1
      tls_auth_name: "cloudflare-dns.com"
    - address_data: 8.8.8.8
      tls_auth_name: "dns.google"

Then configure Unbound to forward to Stubby:

1
2
3
forward-zone:
  name: "."
  forward-addr: 127.0.0.1@5353

CoreDNS: Cloud-Native DNS for Modern Infrastructure

CoreDNS is a DNS server written in Go with a plugin-based architecture. It is the default DNS server in Kubernetes and excels at service discovery, acting as a DNS proxy, and integrating with modern infrastructure components.

When to Choose CoreDNS

  • You’re running Kubernetes and want to customize cluster DNS
  • You need DNS-based service discovery for microservices
  • You want a lightweight DNS server with a simple configuration language (Corefile)
  • You need integration with etcd, Kubernetprometheusute 53, or other cloud backends
  • You want Prometheus metrics out of the box

Installation

Docker (standalone):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
version: "3.8"
services:
  coredns:
    image: coredns/coredns:latest
    container_name: coredns
    ports:
      - "53:53/tcp"
      - "53:53/udp"
      - "9153:9153"  # Prometheus metrics
    volumes:
      - ./Corefile:/etc/coredns/Corefile
    command: -conf /etc/coredns/Corefile
    restart: unless-stopped

Corefile Configuration

CoreDNS uses a Corefile — a declarative plugin chain that processes DNS queries:

 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
# Corefile — basic recursive + caching setup
.:53 {
    log
    errors
    health
    ready
    prometheus :9153
    cache 30
    forward . 1.1.1.1 8.8.8.8 {
        max_concurrent 1000
        health_check 5s
    }
    loop
    reload 30s
    loadbalance
}

# Internal zone with custom records
internal.local:53 {
    file /etc/coredns/db.internal.local {
        reload 30s
    }
    cache 30
    log
}

Zone File for CoreDNS

Create /etc/coredns/db.internal.local:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
$TTL 3600
internal.local.    IN SOA   ns.internal.local. admin.internal.local. (
                        2026041301 ; serial
                        7200       ; refresh
                        3600       ; retry
                        1209600    ; expire
                        3600       ; minimum
)

internal.local.    IN NS    ns.internal.local.
ns.internal.local. IN A     192.168.1.10

app.internal.local.    IN A   192.168.1.20
api.internal.local.    IN A   192.168.1.21
db.internal.local.     IN A   192.168.1.30
cache.internal.local.  IN A   192.168.1.31

Kubernetes Integration

In Kubernetes, CoreDNS runs as a Deployment and Service in the kube-system namespace. To customize it, edit the coredns ConfigMap:

1
kubectl edit configmap coredns -n kube-system

Example with custom upstream and rewrite rules:

 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
Corefile: |
  .:53 {
      errors
      health { lameduck 5s }
      ready
      kubernetes cluster.local in-addr.arpa ip6.arpa {
          pods insecure
          fallthrough in-addr.arpa ip6.arpa
          ttl 30
      }
      prometheus :9153
      forward . /etc/resolv.conf {
          max_concurrent 1000
      }
      cache 30
      loop
      reload
      loadbalance
  }

  # Custom domain pointing to an internal service
  api.internal.cluster.local:53 {
      forward . 192.168.1.21:53
      cache 30
  }

etcd Backend for Service Discovery

CoreDNS can use etcd as a DNS backend, enabling dynamic service discovery:

1
2
3
4
5
6
7
8
.:53 {
    etcd internal.local {
        endpoint http://etcd:2379
        path /skydns
    }
    cache 30
    log
}

Register a service in etcd:

1
2
3
4
5
6
7
8
9
# Install etcdctl
apt install etcd-client

# Add a DNS record
etcdctl put /skydns/local/internal/app/website \
  '{"host":"192.168.1.20","port":8080,"ttl":60}'

# Query it
dig @127.0.0.1 app.internal.local A

Prometheus Metrics

CoreDNS exposes metrics at :9153/metrics by default when the prometheus plugin is enabled:

1
2
3
4
5
6
7
8
9
# Scrape metrics
curl http://localhost:9153/metrics

# Key metrics to monitor
# coredns_dns_request_count_total    — total queries received
# coredns_dns_response_rcode_count_total — responses by RCODE
# coredns_cache_hits_total           — cache hit rate
# coredns_forward_request_count_total — upstream queries
# coredns_dns_request_duration_seconds — query latency histogram

Choosing the Right DNS Server for Your Setup

The right choice depends on what you need DNS to do:

ScenarioRecommended Server(s)
Hosting your own domain’s DNSPowerDNS (with database) or BIND
Local network recursive resolverUnbound
Kubernetes cluster DNSCoreDNS (default, hard to beat)
DNS-based service discoveryCoreDNS + etcd or Kubernetes plugin
High-availability authoritative DNSPowerDNS with database replication
DNSSEC validation for your networkUnbound
Learning DNS fundamentalsBIND
Split-horizon DNSBIND (views) or PowerDNS (geoip backend)
Dynamic DNS record management via APIPowerDNS
Lightweight DNS proxy with pluginsCoreDNS

Common Production Patterns

In real-world setups, you often combine multiple DNS servers:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   Clients   │────▶│   Unbound   │────▶│  Stubby     │
│  (LAN)      │     │ (cache +    │     │  (DoT/DoH   │
└─────────────┘     │  validate)  │     │   upstream) │
                    └─────────────┘     └─────────────┘
                    ┌──────▼──────┐
                    │  PowerDNS   │
                    │ (internal   │
                    │  zones)     │
                    └─────────────┘

A typical self-hosted stack might use Unbound as the local recursive resolver (with DNSSEC validation), PowerDNS as the authoritative server for your domains (managed via API), and Stubby or dnscrypt-proxy as the encrypted upstream forwarder.

Getting Started Recommendation

If you’re new to self-hosted DNS:

  1. Start with Unbound as a local caching resolver — it’s simple, secure, and immediately improves your network’s DNS performance.
  2. Add PowerDNS when you need to host your own domains with programmatic record management.
  3. Explore CoreDNS when you adopt Kubernetes or need plugin-based DNS routing.
  4. Study BIND to understand DNS deeply — even if you don’t run it in production, knowing BIND makes every other DNS server easier to understand.

Monitoring and Maintenance

Regardless of which DNS server you choose, set up proper monitoring:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# Health check for any DNS server
dig @127.0.0.1 example.com A +short +time=2 +tries=1

# Check DNSSEC validation (look for "ad" flag in response)
dig @127.0.0.1 dnssec.works A +dnssec | grep "flags:"

# Monitor query latency
watch -n 1 "dig @127.0.0.1 example.com A +stats 2>&1 | grep 'Query time'"

# Test against public resolvers for comparison
dig @8.8.8.8 example.com A +stats | grep "Query time"
dig @1.1.1.1 example.com A +stats | grep "Query time"
dig @127.0.0.1 example.com A +stats | grep "Query time"

Set up automated alerts for:

  • DNS server process failures (systemd monitoring)
  • Cache hit rate dropping below 50% (indicates upstream issues)
  • DNSSEC validation failures (potential attacks or misconfigurations)
  • Query response times exceeding 500ms

Running your own DNS server is one of the most impactful infrastructure decisions you can make. It gives you control, visibility, and resilience — and with the tools covered in this guide, there’s a solution for every use case from home lab to enterprise production.

Frequently Asked Questions (FAQ)

Which one should I choose in 2026?

The best choice depends on your specific requirements:

  • For beginners: Start with the simplest option that covers your core use case
  • For production: Choose the solution with the most active community and documentation
  • For teams: Look for collaboration features and user management
  • For privacy: Prefer fully open-source, self-hosted options with no telemetry

Refer to the comparison table above for detailed feature breakdowns.

Can I migrate between these tools?

Most tools support data import/export. Always:

  1. Backup your current data
  2. Test the migration on a staging environment
  3. Check official migration guides in the documentation

Are there free versions available?

All tools in this guide offer free, open-source editions. Some also provide paid plans with additional features, priority support, or managed hosting.

How do I get started?

  1. Review the comparison table to identify your requirements
  2. Visit the official documentation (links provided above)
  3. Start with a Docker Compose setup for easy testing
  4. Join the community forums for troubleshooting
Advertise here