coredns/coredns
Architecture
CoreDNS is a thin DNS server type plugged into the Caddy server framework. The actual DNS work happens in plugins. This page explains how the binary boots, how Corefiles become running servers, and how a query travels through the plugin chain.
The 30-second view
graph TD
Main[coredns.go: main]
Main -->|imports| AllPlugins[core/plugin/zplugin.go: blank-imports every plugin]
Main -->|calls| CoreMain[coremain.Run]
CoreMain --> Flag[flag.Parse + load Corefile]
CoreMain --> CaddyStart[caddy.Start]
CaddyStart --> InspectBlocks[dnsContext.InspectServerBlocks]
InspectBlocks --> MakeServers[dnsContext.MakeServers]
MakeServers --> Servers[Server, ServerTLS, ServerHTTPS, ServerHTTPS3, ServerQUIC, ServergRPC]
Servers --> ListenLoop[ServeDNS / ServeHTTP / serveStream]
ListenLoop --> Chain[plugin chain.ServeDNS]Three levels of code
CoreDNS code falls into three layers, top to bottom:
- Bootstrap —
coredns.goandcoremain/. Wires command-line flags, setscaddy.AppName, and callscaddy.Startafter loading a Corefile. The blank import ofcore/plugin/zplugin.go(generated fromplugin.cfg) ensures every plugin'sinit()runs and registers its directive. - DNS server type —
core/dnsserver/. Implements Caddy's server-type interface (InspectServerBlocks,MakeServers). Parses each Corefile server block into aConfig, groups configs by listen address, and creates the appropriateServer(Server,ServerTLS,ServerHTTPS,ServerHTTPS3,ServerQUIC,ServergRPC) for each transport.Server.ServeDNSis the per-query entry point. - Plugins —
plugin/<name>/. Each plugin registers itself withplugin.Register("name", setup)in aninit()function, parses its own Caddyfile syntax insidesetup, and provides aHandlerwhoseServeDNSis chained behind the previous plugin.
A small fourth layer — plugin/pkg/ — holds reusable building blocks (cache, parse, dnsutil, log, transport, etc.) shared by plugins and the server.
How Corefiles become servers
A Corefile is a list of server blocks. Each block has one or more keys (zones with optional schemes/ports) and a brace-enclosed body of directives. Each directive corresponds to a plugin name in plugin.cfg.
. { # server block
log # directive (plugin "log")
forward . 8.8.8.8
}The server-type setup does the following (see core/dnsserver/register.go):
InspectServerBlockswalks every key in every block. It splits off the transport scheme (dns://,tls://,https://,https3://,quic://,grpc://) viaplugin/pkg/parse.Transport, normalizes hosts viaplugin.SplitHostPort, expands CIDR keys to reverse-zone form, and creates one*Configper zone. EachConfigrecords its zone, port, transport, and the index that maps it back to the block.- Directive setup. Caddy iterates the directives in plugin.cfg order and runs each plugin's registered
setup(c *caddy.Controller). Setups read their own arguments from the Caddyfile (c.Next(),c.RemainingArgs()…) and calldnsserver.GetConfig(c).AddPlugin(...)to append themselves to the per-zone plugin chain. MakeServerspropagates shared parameters from the first config in each block to siblings, groups configs by listen address (transport://ip:port), and creates acaddy.Serverper group:NewServer,NewServerTLS,NewServerHTTPS,NewServerHTTPS3,NewServerQUIC, orNewServergRPC. View Filter plugins are also wired here.- Per-server compile step. Inside
dnsserver.NewServer, the plugin list of eachConfigis folded right-to-left into a singleplugin.Handler(stack = site.Plugin[i](stack)). Each plugin gets its predecessor as itsNext, so the finalpluginChainis the head of a linked list.
The result is a []caddy.Server Caddy can start. Each server type has its own listener-and-loop logic but ultimately calls the shared (*Server).ServeDNS.
How a query is served
sequenceDiagram
participant C as Client
participant L as Listener (UDP/TCP/TLS/HTTP/QUIC)
participant S as Server.ServeDNS
participant P as Plugin chain
C->>L: DNS query
L->>S: dns.Msg + ResponseWriter
S->>S: validate qclass, EDNS, recover
S->>S: longest-suffix zone match
S->>P: pluginChain.ServeDNS
loop until a plugin writes
P->>P: handle or NextOrFailure
end
P-->>S: rcode + err
alt rcode says client wrote
S-->>C: (already written)
else
S->>L: synth error reply (REFUSED, SERVFAIL, ...)
L-->>C: error response
endServer.ServeDNS (core/dnsserver/server.go) does five things:
- Defends against panics with a
recover()(controlled bydebugplugin andStacktraceconfig). - Refuses non-INET (CH) class queries unless a plugin like
chaosorforwardis loaded (EnableChaosmap). - Validates the EDNS0 OPT record version.
- Wraps the writer in
request.ScrubWriterso replies are truncated to fit the client buffer. - Walks the qname label by label, looking up
s.zones[q[off:]]. The first matching zone whose filter funcs (e.g.view) all pass is dispatched to. If the query is a DS record, the search continues so a parent-zone handler can answer.
Inside the chain, each plugin's ServeDNS(ctx, w, r) returns (rcode, err). CoreDNS treats SERVFAIL, REFUSED, FORMERR, and NOTIMP as "no reply written" — for any other rcode the server assumes the plugin wrote to w itself. plugin.ClientWrite encodes that policy.
A plugin that wants to delegate calls plugin.NextOrFailure(name, p.Next, ctx, w, r). NextOrFailure also wraps the writer with a pluginWriter so the metrics plugin can attribute the response to the plugin that actually wrote it.
Server types and transports
Each scheme in a Corefile key picks a different Server constructor:
| Scheme | Server type | Source |
|---|---|---|
dns:// (default) |
Server (UDP+TCP) |
core/dnsserver/server.go |
tls:// |
ServerTLS (DoT) |
core/dnsserver/server_tls.go |
https:// |
ServerHTTPS (DoH over HTTP/2) |
core/dnsserver/server_https.go |
https3:// |
ServerHTTPS3 (DoH3) |
core/dnsserver/server_https3.go |
quic:// |
ServerQUIC (DoQ) |
core/dnsserver/server_quic.go |
grpc:// |
ServergRPC |
core/dnsserver/server_grpc.go |
The HTTPS, HTTPS3, and QUIC servers translate their wire format to and from dns.Msg and then call the same (*Server).ServeDNS so plugins are transport-agnostic. DoH adds an HTTPRequestKey value to the context so plugins can read the original *http.Request.
Listening sockets use plugin/pkg/reuseport so reload events can hand sockets between processes. PROXY protocol and a UDP session tracking cache for Cloudflare Spectrum live in core/dnsserver/server.go and plugin/pkg/proxyproto. See Transports for the full picture.
Cross-cutting concerns
- Metrics. The
prometheusplugin (plugin/metrics/) exports a single/metricsendpoint; the server itself writes core counters viaplugin/metrics/vars. See How to monitor. - Tracing. The
traceplugin (plugin/trace/) installs a tracer onServer.trace;plugin.NextOrFailurestarts a child span for each downstream plugin. - Metadata. Plugins implementing
MetadataCollectorare bookmarked on the config; the server callsCollectonce per request before the chain runs, populatingmetadata.Mdfor downstream plugins. - Reload. The
reloadplugin (plugin/reload/) periodically rehashes the Corefile and triggers a Caddy graceful restart on change. - Health and readiness.
healthandreadyplugins each run a small HTTP server (/health,/ready) on a dedicated port. - Views. A
viewplugin makes a server block conditional on a CEL-like expression;MakeServerswires itsFilterinto the config'sFilterFuncs, evaluated inServer.ServeDNS.
Build-time code generation
plugin.cfg is the single source of truth for which plugins are compiled in. make gen (or go generate coredns.go) runs:
directives_generate.go→ writescore/plugin/zplugin.go(blank imports) andcore/dnsserver/zdirectives.go(the orderedDirectivesslice).owners_generate.go→ updatesCODEOWNERSbased on plugin ownership.
The COREDNS_PLUGINS environment variable can append extra plugins to the build without editing plugin.cfg. See Build and code generation.
Where to read next
- DNS server — the Server, Config, and request dispatch in detail.
- Plugin system — the
Handler/Plugininterfaces, registration, and chain compilation. - Plugins overview — the catalog of in-tree plugins grouped by purpose.
- Transports — how DoT, DoH, DoH3, DoQ, and gRPC work on top of the same server.
- Reference: configuration — Corefile, flags, environment variables, generated files.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.