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-resolvedorbind9if 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.


