CoreDNS Installation and Configuration

CoreDNS is a fast, flexible DNS server written in Go that is the default DNS component in Kubernetes and can replace BIND for general-purpose DNS serving. This guide covers installing CoreDNS on Linux, configuring plugins for forwarding, caching, and zone management, and integrating with Kubernetes.

Prerequisites

  • Linux server (Ubuntu 22.04/Debian 12 or CentOS/Rocky 9)
  • Root or sudo access
  • Port 53 (TCP and UDP) available (stop systemd-resolved or bind9 if running)
  • Basic understanding of DNS zones and records

Install CoreDNS

# Download the latest CoreDNS binary
COREDNS_VERSION=1.11.3
curl -L https://github.com/coredns/coredns/releases/download/v${COREDNS_VERSION}/coredns_${COREDNS_VERSION}_linux_amd64.tgz \
  -o /tmp/coredns.tgz

tar xvf /tmp/coredns.tgz -C /tmp/
sudo mv /tmp/coredns /usr/local/bin/coredns
sudo chmod +x /usr/local/bin/coredns

# Verify
coredns --version

# Create config and zone directories
sudo mkdir -p /etc/coredns/zones

Free port 53 if needed:

# Check what's using port 53
sudo ss -tlnup | grep :53

# Disable systemd-resolved stub listener
sudo sed -i 's/#DNSStubListener=yes/DNSStubListener=no/' /etc/systemd/resolved.conf
sudo systemctl restart systemd-resolved

# Or stop bind9
sudo systemctl stop bind9 && sudo systemctl disable bind9

CoreDNS Configuration Basics (Corefile)

CoreDNS is configured entirely via a Corefile. Each block defines a server zone and the plugins to apply:

sudo tee /etc/coredns/Corefile << 'EOF'
# Default server - handles all queries
. {
    # Log all DNS queries
    log

    # Return errors to clients
    errors

    # Enable health check endpoint on port 8080
    health :8080

    # Forward all unresolved queries to upstream resolvers
    forward . 1.1.1.1 8.8.8.8 {
        prefer_udp
        health_check 5s
    }

    # Cache responses for performance
    cache 300

    # Prometheus metrics endpoint
    prometheus :9153
}
EOF

Test the configuration syntax:

coredns -conf /etc/coredns/Corefile -dns.port 1053 &
sleep 2
dig @127.0.0.1 -p 1053 google.com
kill %1

Forwarding and Caching

Configure intelligent forwarding with separate upstreams for different domains:

sudo tee /etc/coredns/Corefile << 'EOF'
# Internal domain - forward to internal resolver
internal.example.com {
    forward . 192.168.1.10:53 192.168.1.11:53 {
        policy round_robin
        health_check 10s
        max_fails 3
        expire 30s
    }
    cache 60
    log
    errors
}

# Forward DNS-over-TLS to Cloudflare for privacy
. {
    forward . tls://1.1.1.1 tls://1.0.0.1 {
        tls_servername cloudflare-dns.com
        health_check 5s
    }
    cache {
        success 9984 300    # cache up to 9984 positive responses for 300s
        denial 9984 60      # cache NXDOMAIN for 60s
        prefetch 10 60s     # prefetch popular entries
    }
    log
    errors
    prometheus :9153
}
EOF

Serving Authoritative Zones

Create a zone file and configure CoreDNS to serve it authoritatively:

# Create zone file in standard BIND format
sudo tee /etc/coredns/zones/example.com.db << 'EOF'
$ORIGIN example.com.
$TTL 3600

@   IN  SOA ns1.example.com. admin.example.com. (
            2026040401  ; Serial (YYYYMMDDNN)
            3600        ; Refresh
            900         ; Retry
            604800      ; Expire
            300 )       ; Minimum TTL

; Name servers
@       IN  NS  ns1.example.com.
@       IN  NS  ns2.example.com.

; A records
ns1     IN  A   192.168.1.10
ns2     IN  A   192.168.1.11
@       IN  A   203.0.113.10
www     IN  A   203.0.113.10
mail    IN  A   203.0.113.20
api     IN  A   203.0.113.30

; CNAME
webmail IN  CNAME mail.example.com.

; MX record
@       IN  MX  10 mail.example.com.

; TXT record
@       IN  TXT "v=spf1 mx -all"
EOF

Configure CoreDNS to serve this zone:

sudo tee /etc/coredns/Corefile << 'EOF'
# Authoritative zone
example.com {
    file /etc/coredns/zones/example.com.db
    log
    errors
    # Transfer to secondary DNS server
    transfer {
        to 192.168.1.11
    }
}

# Recursive resolver for everything else
. {
    forward . 1.1.1.1 8.8.8.8
    cache 300
    errors
}
EOF

Kubernetes Integration

CoreDNS is the default cluster DNS in Kubernetes. View the existing configuration:

kubectl -n kube-system get configmap coredns -o yaml

Customize the Kubernetes CoreDNS Corefile:

kubectl -n kube-system edit configmap coredns

Example Kubernetes Corefile with custom forwarding for an internal domain:

apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
data:
  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
        }
        # Forward internal.example.com to on-prem DNS
        forward internal.example.com 192.168.1.10 192.168.1.11
        prometheus :9153
        forward . /etc/resolv.conf {
           max_concurrent 1000
        }
        cache 30
        loop
        reload
        loadbalance
    }
# Reload the ConfigMap (CoreDNS picks it up automatically due to 'reload' plugin)
kubectl -n kube-system rollout restart deployment coredns

# Test DNS from within the cluster
kubectl run dnstest --image=busybox:1.28 --rm -it --restart=Never -- nslookup kubernetes.default

Custom Resolution Policies

Override specific DNS responses using the hosts or rewrite plugins:

sudo tee /etc/coredns/Corefile << 'EOF'
. {
    # Rewrite queries - redirect old domain to new
    rewrite name suffix .old.example.com .new.example.com

    # Block specific domains (return NXDOMAIN)
    template IN A blocked.example.com {
        rcode NXDOMAIN
    }

    # Static hosts overrides (like /etc/hosts)
    hosts /etc/coredns/custom-hosts {
        10.0.0.100  internal-app.example.com
        10.0.0.101  another-app.example.com
        fallthrough
    }

    forward . 1.1.1.1
    cache 300
    log
    errors
}
EOF

Run CoreDNS as a Systemd Service

# Create system user
sudo useradd -r -s /sbin/nologin coredns

# Allow binding to port 53 (privileged port) without root
sudo setcap cap_net_bind_service=+ep /usr/local/bin/coredns

sudo tee /etc/systemd/system/coredns.service << 'EOF'
[Unit]
Description=CoreDNS DNS Server
Documentation=https://coredns.io
After=network.target

[Service]
User=coredns
ExecStart=/usr/local/bin/coredns -conf /etc/coredns/Corefile
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
LimitNOFILE=1048576
LimitNPROC=512

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now coredns

# Verify it's listening
sudo ss -tlnup | grep :53

Troubleshooting

CoreDNS fails to start - port 53 in use:

sudo ss -tlnup | grep :53
# Find and stop the conflicting service
sudo systemctl stop systemd-resolved
# Set DNSStubListener=no in /etc/systemd/resolved.conf

Zone not loading:

# Check zone file syntax
named-checkzone example.com /etc/coredns/zones/example.com.db

# Check CoreDNS logs
journalctl -u coredns -n 50 --no-pager

# Run in debug mode
coredns -conf /etc/coredns/Corefile -dns.port 1053 2>&1

Queries timing out:

# Test directly
dig @127.0.0.1 google.com +stats

# Check upstream forwarding
dig @1.1.1.1 google.com

# Check CoreDNS metrics
curl http://localhost:9153/metrics | grep coredns_dns_requests_total

Kubernetes pods can't resolve services:

kubectl exec -it <pod> -- nslookup kubernetes.default
kubectl -n kube-system logs -l k8s-app=kube-dns --tail=50
kubectl -n kube-system get configmap coredns -o yaml

Conclusion

CoreDNS provides a modern, plugin-based approach to DNS that scales from simple forwarding setups to full authoritative serving and Kubernetes cluster DNS. Its configuration is compact and readable, and the plugin ecosystem covers caching, rewriting, health checking, and metrics out of the box. For Kubernetes deployments, understanding the Corefile configmap is essential for debugging DNS resolution issues across the cluster.