Last updated: 2026-06-25
CoreDNS DNS over TLS/HTTPS Configuration Guide
This guide provides recommended DNS over TLS (DoT) and DNS over HTTPS (DoH) settings for CoreDNS, a flexible, plugin-driven DNS server. Encrypting DNS traffic prevents eavesdropping and tampering with DNS queries and responses. CoreDNS is configured through a Corefile, where each server block opens with one or more transport addresses and is built from plugins.
Prerequisites
- CoreDNS 1.14 or later (current stable release is 1.14.3, April 2026). Run the latest stable release where possible.
- SSL certificates (for serving DoT/DoH to clients)
- CoreDNS is built with Go's
crypto/tls, which provides strong TLS defaults
Install: Download the binary from the CoreDNS releases page, or use your distribution's package. On Kubernetes, CoreDNS is typically the default cluster DNS.
Certificate Setup
Place your certificates in a dedicated directory:
mkdir -p /etc/coredns/ssl
chmod 750 /etc/coredns/ssl
cp fullchain.pem /etc/coredns/ssl/fullchain.pem
cp privkey.pem /etc/coredns/ssl/privkey.pem
cp ca.pem /etc/coredns/ssl/ca.pem
chmod 644 /etc/coredns/ssl/fullchain.pem /etc/coredns/ssl/ca.pem
chmod 640 /etc/coredns/ssl/privkey.pem
Serving DNS over TLS (DoT)
A server block is keyed by its transport. Prefix the zone with tls:// and bind port 853 to accept DoT connections from clients. The tls plugin points CoreDNS at the certificate and key.
tls://.:853 {
tls /etc/coredns/ssl/fullchain.pem /etc/coredns/ssl/privkey.pem
forward . 9.9.9.9
cache 30
log
errors
}
The tls CERT KEY [CA] plugin loads the server certificate (fullchain.pem) and private key (privkey.pem). The optional CA argument is only meaningful when client certificate verification is enabled (see below).
Serving DNS over HTTPS (DoH)
CoreDNS serves DNS over HTTPS by keying the server block with the https:// transport on port 443. The same tls plugin supplies the certificate and key. The DoH endpoint is /dns-query.
https://.:443 {
tls /etc/coredns/ssl/fullchain.pem /etc/coredns/ssl/privkey.pem
forward . 9.9.9.9
cache 30
log
errors
}
CoreDNS also supports DNS over gRPC with the grpc:// transport, which uses the same tls plugin for certificate configuration.
Forwarding to Upstream over TLS
The forward plugin sends queries to an upstream resolver. Prefixing each upstream with tls:// encrypts the connection between CoreDNS and the upstream resolver. Set tls_servername so the upstream certificate is validated against the correct name.
. {
forward . tls://9.9.9.9 tls://149.112.112.112 {
tls_servername dns.quad9.net
health_check 5s
}
cache 30
}
Configuration Explained
- tls:// - The transport prefix on each upstream address forces the forwarder to use DNS over TLS instead of plaintext UDP/TCP.
- tls_servername - The expected server name in the upstream certificate. Quad9's DoT endpoints require this to be
dns.quad9.net. A sharedtls_servernameapplies to all upstreams in the block. - health_check - Polls each upstream so unhealthy resolvers are skipped.
Upstream certificates are validated against the system CA bundle by default. To pin a custom CA, add the tls option inside the forward block. With zero to three arguments it accepts tls (system CAs), tls CA (custom CA file), tls CERT KEY (client certificate with system CAs), or tls CERT KEY CA (client certificate with a custom CA):
. {
forward . tls://9.9.9.9 tls://149.112.112.112 {
tls_servername dns.quad9.net
tls /etc/coredns/ssl/ca.pem
}
cache 30
}
Mutual TLS (Client Certificate Verification)
To require clients connecting over DoT or DoH to present a valid certificate, add a client_auth block to the tls plugin. The CA argument is required and is used to verify presented client certificates.
tls://.:853 {
tls /etc/coredns/ssl/fullchain.pem /etc/coredns/ssl/privkey.pem /etc/coredns/ssl/ca.pem {
client_auth require_and_verify
}
forward . 9.9.9.9
}
The client_auth option maps to Go's TLS client authentication types and accepts nocert (default), request, require, verify_if_given, and require_and_verify. The CA parameter is only meaningful when client_auth is verify_if_given or require_and_verify.
Dual-Stack Binding
By default a server block listens on all interfaces. To bind explicit IPv4 and IPv6 addresses, use the bind plugin. Multiple addresses are separated by spaces, so a single directive can serve both stacks.
tls://.:853 {
bind 0.0.0.0 ::
tls /etc/coredns/ssl/fullchain.pem /etc/coredns/ssl/privkey.pem
forward . 9.9.9.9
}
The bind ADDRESS ... form accepts any number of addresses (and interface names), and supports an except block to exclude specific addresses.
Supporting Plugins
A small set of plugins is useful alongside the encrypted listeners:
- health - Exports a liveness endpoint, by default on
:8080/health. - ready - Exports a readiness endpoint, by default on
:8181/ready, reporting once all plugins have signaled ready. - log - Logs queries to standard output.
- errors - Logs errors to standard output.
These are typically placed in a plain . server block so they are not exposed on the TLS listeners:
. {
health
ready
log
errors
}
Complete Configuration
# Plaintext listener for local clients plus health and readiness
.:53 {
bind 0.0.0.0 ::
forward . tls://9.9.9.9 tls://149.112.112.112 {
tls_servername dns.quad9.net
health_check 5s
}
cache 30
health
ready
log
errors
}
# DNS over TLS listener for external clients
tls://.:853 {
bind 0.0.0.0 ::
tls /etc/coredns/ssl/fullchain.pem /etc/coredns/ssl/privkey.pem
forward . tls://9.9.9.9 tls://149.112.112.112 {
tls_servername dns.quad9.net
}
cache 30
log
errors
}
# DNS over HTTPS listener for external clients
https://.:443 {
bind 0.0.0.0 ::
tls /etc/coredns/ssl/fullchain.pem /etc/coredns/ssl/privkey.pem
forward . tls://9.9.9.9 tls://149.112.112.112 {
tls_servername dns.quad9.net
}
cache 30
log
errors
}
Security Notes
CoreDNS uses Go's crypto/tls package, which has a different vulnerability history than OpenSSL. Cipher suites and the minimum protocol version are governed by Go's defaults rather than a Corefile directive: the tls and forward plugins expose certificate, key, CA, and client-authentication options, but no cipher-suite or minimum-version setting. Go's crypto/tls enforces a TLS 1.2 minimum by default and negotiates TLS 1.3 where the peer supports it, using only AEAD ciphers (AES-GCM and ChaCha20-Poly1305) with ECDHE key exchange.
- POODLE (CVE-2014-3566, 2014): SSL 3.0 has never been supported in Go's TLS implementation.
- BEAST (CVE-2011-3389, 2011): Mitigated by the TLS 1.2 minimum; AEAD-only ciphers eliminate the CBC padding oracle.
- CRIME (CVE-2012-4929, 2012): TLS compression is not supported in Go's
crypto/tls. - Lucky13 (2013): AEAD-only cipher selection eliminates CBC padding timing side-channels entirely.
- FREAK (CVE-2015-0204, 2015): EXPORT-grade ciphers have never been supported in Go's
crypto/tls. - LOGJAM (CVE-2015-4000, 2015): DHE key exchange is not offered by default in Go; only ECDHE is used.
- Sweet32 (CVE-2016-2183, 2016): 3DES was removed from Go's default cipher list in Go 1.23 (August 2024) and is excluded from the recommended configuration.
- ROBOT (2017): Static RSA key exchange is not offered in Go's crypto/tls; only ECDHE is used.
- Downgrade attacks: Go's
crypto/tlsdoes not perform insecure version-fallback retries, and TLS 1.3 has built-in downgrade protection.
The following are not addressable through TLS configuration alone:
- Heartbleed (CVE-2014-0160, 2014): Not applicable. Go's
crypto/tlsis an independent TLS implementation and was never affected by Heartbleed. - BREACH (CVE-2013-3587, 2013): Not applicable to DNS over TLS, which does not involve HTTP. DNS over HTTPS uses HTTP but does not carry compressible secret data in responses.
- DROWN (CVE-2016-0800, 2016): Not applicable. Go's
crypto/tlsdoes not support SSLv2.
Verification
Validate the Corefile by starting CoreDNS with it:
coredns -conf /etc/coredns/Corefile
Test the DoT listener:
openssl s_client -connect dns.example.com:853
Test DNS resolution over TLS using kdig (from knot-dnsutils):
kdig @dns.example.com +tls example.com A
Test DNS resolution over HTTPS:
kdig @dns.example.com +https example.com A
Check the readiness and health endpoints:
curl http://localhost:8181/ready
curl http://localhost:8080/health
Confirm the DoT certificate and chain externally with the Mr.DNS SSL/TLS Certificate Check (port 853).