Overview
CoreDNS is a DNS server written in Go that operates through a flexible plugin chain architecture. Each DNS request passes through a series of plugins that can perform actions like caching, forwarding, logging, rewriting, and serving zone data. CoreDNS replaced kube-dns as the default DNS server in Kubernetes starting with version 1.13, making it the most widely deployed DNS server in container environments. It serves as both an authoritative DNS server and a recursive resolver.
CoreDNS configuration uses a Corefile, inspired by Caddy’s configuration format. Server blocks define zones and the plugins that handle them. Plugins are executed in a fixed order regardless of their position in the config file. CoreDNS supports DNS over TLS (DoT), DNS over HTTPS (DoH), DNS over gRPC, and traditional UDP/TCP. It can read zones from files, databases, etcd, Kubernetes API, or serve dynamically generated records. Its lightweight footprint and extensive plugin ecosystem make it suitable for everything from simple caching resolvers to complex multi-zone authoritative servers.
Installation
Binary Installation
# Download latest release
wget https://github.com/coredns/coredns/releases/download/v1.11.3/coredns_1.11.3_linux_amd64.tgz
tar xzf coredns_1.11.3_linux_amd64.tgz
sudo mv coredns /usr/local/bin/
# Verify
coredns --version
# Run with default Corefile
coredns
Docker
docker run -d --name coredns \
-p 53:53/udp -p 53:53/tcp \
-v $(pwd)/Corefile:/Corefile \
-v $(pwd)/zones:/zones \
coredns/coredns
Systemd Service
[Unit]
Description=CoreDNS DNS Server
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/coredns -conf /etc/coredns/Corefile
Restart=on-failure
User=coredns
Group=coredns
LimitNOFILE=8192
[Install]
WantedBy=multi-user.target
Core Configuration (Corefile)
Basic Forwarding Resolver
. {
forward . 8.8.8.8 8.8.4.4 {
tls_servername dns.google
}
cache 300
log
errors
}
Authoritative Zone
example.com {
file /etc/coredns/zones/example.com.zone
log
errors
}
. {
forward . 8.8.8.8
cache 3600
log
}
Zone File Example
; /etc/coredns/zones/example.com.zone
$ORIGIN example.com.
$TTL 3600
@ IN SOA ns1.example.com. admin.example.com. (
2024010101 ; Serial
3600 ; Refresh
900 ; Retry
604800 ; Expire
86400 ; Minimum TTL
)
IN NS ns1.example.com.
IN NS ns2.example.com.
IN A 203.0.113.10
IN MX 10 mail.example.com.
ns1 IN A 203.0.113.1
ns2 IN A 203.0.113.2
www IN A 203.0.113.10
mail IN A 203.0.113.20
api IN A 203.0.113.30
db IN A 10.0.0.50
Plugin Reference
| Plugin | Description |
|---|
forward | Proxy DNS requests to upstream resolvers |
cache | Enable DNS response caching |
file | Serve zones from RFC 1035 zone files |
log | Enable query logging |
errors | Log errors to stdout |
hosts | Serve from /etc/hosts style file |
rewrite | Rewrite queries before processing |
template | Generate responses from templates |
health | HTTP health check endpoint |
ready | Readiness probe endpoint |
prometheus | Export Prometheus metrics |
loadbalance | Randomize A/AAAA record order |
loop | Detect forwarding loops |
kubernetes | Kubernetes service discovery |
etcd | Serve records from etcd |
acl | Access control lists |
Kubernetes DNS
Default Kubernetes CoreDNS ConfigMap
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
}
prometheus :9153
forward . /etc/resolv.conf {
max_concurrent 1000
}
cache 30
loop
reload
loadbalance
}
Custom DNS Entries in Kubernetes
apiVersion: v1
kind: ConfigMap
metadata:
name: coredns-custom
namespace: kube-system
data:
custom.server: |
example.com:53 {
forward . 10.0.0.53
}
internal.override: |
rewrite name api.internal.svc.cluster.local api-service.default.svc.cluster.local
Advanced Configuration
DNS over TLS (DoT)
tls://.:853 {
tls /etc/coredns/cert.pem /etc/coredns/key.pem
forward . 8.8.8.8
cache 300
log
}
DNS over HTTPS (DoH)
https://.:443 {
tls /etc/coredns/cert.pem /etc/coredns/key.pem
forward . 8.8.8.8
cache 300
}
Query Rewriting
example.com {
rewrite name prefix old. new.
rewrite name exact legacy.example.com modern.example.com
rewrite stop {
name regex (.*)\.old\.example\.com {1}.new.example.com
answer name (.*)\.new\.example\.com {1}.old.example.com
}
forward . 8.8.8.8
}
Split DNS / Conditional Forwarding
internal.corp {
forward . 10.0.0.53 10.0.0.54
cache 600
}
aws.internal {
forward . 10.0.0.2
cache 60
}
. {
forward . 8.8.8.8 1.1.1.1
cache 3600
}
Template Plugin (Dynamic Records)
example.com {
template IN A {
match .*\.example\.com
answer "{{ .Name }} 60 IN A 10.0.0.1"
fallthrough
}
}
ACL Plugin
. {
acl {
allow net 10.0.0.0/8
allow net 192.168.0.0/16
block
}
forward . 8.8.8.8
}
Monitoring
# Health check endpoint (default :8080)
curl http://localhost:8080/health
# Readiness endpoint (default :8181)
curl http://localhost:8181/ready
# Prometheus metrics (default :9153)
curl http://localhost:9153/metrics
Key Prometheus Metrics
| Metric | Description |
|---|
coredns_dns_requests_total | Total DNS requests by zone and type |
coredns_dns_responses_total | Total responses by rcode |
coredns_dns_request_duration_seconds | Request latency histogram |
coredns_cache_hits_total | Cache hit count |
coredns_cache_misses_total | Cache miss count |
Troubleshooting
| Issue | Solution |
|---|
bind: address already in use | Stop existing DNS service on port 53 (systemd-resolved) |
| Loop detected | Remove forward . /etc/resolv.conf if resolv.conf points to CoreDNS itself |
| Kubernetes DNS not resolving | Check CoreDNS pods: kubectl -n kube-system get pods -l k8s-app=kube-dns |
| SERVFAIL responses | Check upstream resolver connectivity; review errors plugin output |
| Slow DNS resolution | Enable cache plugin; check upstream latency with dig |
| Zone file not loading | Validate zone file syntax; check serial number incremented |
| Hot-reload not working | Ensure reload plugin is in the Corefile |