Open-Source Wikis

/

CoreDNS

/

CoreDNS

/

Architecture

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:

  1. Bootstrapcoredns.go and coremain/. Wires command-line flags, sets caddy.AppName, and calls caddy.Start after loading a Corefile. The blank import of core/plugin/zplugin.go (generated from plugin.cfg) ensures every plugin's init() runs and registers its directive.
  2. DNS server typecore/dnsserver/. Implements Caddy's server-type interface (InspectServerBlocks, MakeServers). Parses each Corefile server block into a Config, groups configs by listen address, and creates the appropriate Server (Server, ServerTLS, ServerHTTPS, ServerHTTPS3, ServerQUIC, ServergRPC) for each transport. Server.ServeDNS is the per-query entry point.
  3. Pluginsplugin/<name>/. Each plugin registers itself with plugin.Register("name", setup) in an init() function, parses its own Caddyfile syntax inside setup, and provides a Handler whose ServeDNS is 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):

  1. InspectServerBlocks walks every key in every block. It splits off the transport scheme (dns://, tls://, https://, https3://, quic://, grpc://) via plugin/pkg/parse.Transport, normalizes hosts via plugin.SplitHostPort, expands CIDR keys to reverse-zone form, and creates one *Config per zone. Each Config records its zone, port, transport, and the index that maps it back to the block.
  2. 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 call dnsserver.GetConfig(c).AddPlugin(...) to append themselves to the per-zone plugin chain.
  3. MakeServers propagates shared parameters from the first config in each block to siblings, groups configs by listen address (transport://ip:port), and creates a caddy.Server per group: NewServer, NewServerTLS, NewServerHTTPS, NewServerHTTPS3, NewServerQUIC, or NewServergRPC. View Filter plugins are also wired here.
  4. Per-server compile step. Inside dnsserver.NewServer, the plugin list of each Config is folded right-to-left into a single plugin.Handler (stack = site.Plugin[i](stack)). Each plugin gets its predecessor as its Next, so the final pluginChain is 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
    end

Server.ServeDNS (core/dnsserver/server.go) does five things:

  1. Defends against panics with a recover() (controlled by debug plugin and Stacktrace config).
  2. Refuses non-INET (CH) class queries unless a plugin like chaos or forward is loaded (EnableChaos map).
  3. Validates the EDNS0 OPT record version.
  4. Wraps the writer in request.ScrubWriter so replies are truncated to fit the client buffer.
  5. 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 prometheus plugin (plugin/metrics/) exports a single /metrics endpoint; the server itself writes core counters via plugin/metrics/vars. See How to monitor.
  • Tracing. The trace plugin (plugin/trace/) installs a tracer on Server.trace; plugin.NextOrFailure starts a child span for each downstream plugin.
  • Metadata. Plugins implementing MetadataCollector are bookmarked on the config; the server calls Collect once per request before the chain runs, populating metadata.Md for downstream plugins.
  • Reload. The reload plugin (plugin/reload/) periodically rehashes the Corefile and triggers a Caddy graceful restart on change.
  • Health and readiness. health and ready plugins each run a small HTTP server (/health, /ready) on a dedicated port.
  • Views. A view plugin makes a server block conditional on a CEL-like expression; MakeServers wires its Filter into the config's FilterFuncs, evaluated in Server.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:

The COREDNS_PLUGINS environment variable can append extra plugins to the build without editing plugin.cfg. See Build and code generation.

  • DNS server — the Server, Config, and request dispatch in detail.
  • Plugin system — the Handler/Plugin interfaces, 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.

Architecture – CoreDNS wiki | Factory