CoreDNS Manual

Comprehensive manual covering all aspects of getting and running CoreDNS.

This manual is a work-in-progress! Help is appreciated! See the source of this manual if you want to help. Either by sending pull requests or by filing issues with specifics of what you want to see addressed here, any missing content or just confusing bits.

Also note: names used in this manual, like “Server Block”, and “Plugin Block” may not be the final names we use. As said, this is a work-in-progress.

The current set of issues is tagged with the “manual” label.

– CoreDNS Authors


Table of Contents



What is CoreDNS

CoreDNS is a DNS server. It is written in Go.

CoreDNS is different from other DNS servers, such as (all excellent) BIND, Knot, PowerDNS and Unbound (technically a resolver, but still worth a mention), because it is very flexible; it chains plugins.

Plugins can be stand alone or work together to perform a “DNS function”.

So what’s a “DNS function”? For the purpose of CoreDNS we define it as a piece of software that implements the CoreDNS Plugin API. The functionality implemented can wildly deviate. There are plugins that don’t themselves create a response, such as metrics or cache, but add functionality. Then there are plugins that do generate a response. These can also do anything; there are plugins that communicate with Kubernetes and provide service discovery, plugins that read data from a file or database.

There are currently about 30 plugins included in the default CoreDNS install, but there are also a whole bunch of external plugins that you can compile into CoreDNS to extend its functionality.

CoreDNS is powered by plugins.

Writing new plugins should be easy enough, but requires knowing Go and having some insight into how DNS works. CoreDNS abstracts away all other bits, so that you can just focus on writing the plugin functionality you need.




CoreDNS is written in Go, but unless you want to develop plugins or compile CoreDNS yourself you probably don’t care. The following sections detail how you can get CoreDNS binaries or install from source.


For every CoreDNS release, we provide pre-compiled binaries for various operating systems. For Linux, we also provide cross compiled binaries for ARM, PowerPC and other architectures.


We push every release as Docker images as well. You can find them in the public Docker hub for the CoreDNS organization.

Note that Docker images that are for architectures other than AMD64 don’t have any certificates installed. This means if you want to use CoreDNS on ARM and do things like DNS-over-TLS, you’ll need to create your own Docker image.


To compile CoreDNS, we assume you have a working Go setup. See various tutorials if you don’t have that already configured. The Go version that comes with your OS is probably too old to compile CoreDNS as we require Go 1.9.x at the moment (Feb 2018).

With CoreDNS, we try to vendor all our dependencies, but because of various reasons (mostly making it possible for external plugins to compile), we can not vendor all our dependencies. Hence to compile CoreDNS, you still need to go get some packages. The Makefile we include handles all of these steps. So compiling CoreDNS boils down to (as of this writing the latest version is 1.0.5):

$ export GOPATH=${GOPATH-~/go}
$ mkdir -p $GOPATH/src/
$ cd $GOPATH/src/
$ wget
$ tar xvf v1.0.5.tar.gz
$ mv coredns-1.0.5 coredns
$ cd coredns
$ make CHECKS= godeps all

When all of that is done, you should end up with a coredns executable in the current directory:

$ ./coredns -version
linux/amd64, go1.9.4,

The go1.9.4, part usually shows a git commit, but as this is a source tar ball, we don’t have this.

Source from Github

This is mostly the same set of steps:

$ export GOPATH=${GOPATH-~/go}
$ mkdir -p $GOPATH/src/
$ cd $GOPATH/src/
$ git clone
$ cd coredns
$ make CHECKS= godeps all


Once you have a coredns binary, you can use the -plugins flag to list all the compiled plugins. Without a Corefile (See Configuration) CoreDNS will load the whoami that will respond with the IP address and port of the client. So to test, we start CoreDNS to run on port 1053 and send it a query using dig:

$ ./coredns -dns.port=1053
2018/02/20 10:40:44 [INFO] CoreDNS-1.0.5
2018/02/20 10:40:44 [INFO] linux/amd64, go1.10,
linux/amd64, go1.10,

And from a different terminal window, a dig should return something similar to this:

$ dig @localhost -p 1053 a

;		IN	A

;; ADDITIONAL SECTION:	0	IN	AAAA	::1 0	IN	SRV	0 0 39368 .

The next section will show how to enable more interesting plugins.




Once CoreDNS has been started and has parsed the configuration, it runs Servers. Each Server is defined by the zones it serves and on what port. Each Server has its own Plugin Chain.

When a query is being processed by CoreDNS, the following steps are performed:

  1. If there are multiple Servers configured that listen on the queried port, it will check which one has the most specific zone for this query (longest suffix match). E.g. if there are two Servers, one for and one for, and the query is for, it will be routed to the latter.
  2. Once a Server has been found, it will be routed through the Plugin Chain that is configured for this server. This always happens in the same order. That (static) ordering is defined in plugin.cfg.
  3. Each plugin will inspect the query and determine if it should process it (some plugins allow you to filter further on the query name or other attributes). A couple of things can now happen:

    1. The query is processed by this plugin.
    2. The query is not processed by this plugin.
    3. The query is processed by this plugin, but half way through it decides it still wants to call the next plugin in the chain. We call this fallthrough after the keyword that enables it.
    4. The query is processed by this plugin, a “hint” is added and the next plugin is called. This hint provides a way to “see” the (eventual) response and act upon that.

Processing a query means a Plugin will respond to the client with a reply.

Note that a plugin is free to deviate from the above list as it wishes. Currently, all plugins that come with CoreDNS fall into one of these four groups though. Note this blog post also provides background in the query routing.

Query Is Processed

The plugin processes the query. It looks up (or generates, or whatever the plugin author decided this plugin does) a response and sends it back to the client. The query processing stops here, no next plugin is called. A (simple) plugin that works like this is whoami.

Query is not processed

If the plugin decides it will not process a query, it simply calls the next plugin in the chain. If the last plugin in the chain decides to not process the query, CoreDNS will return SERVFAIL back to the client.

Query is processed With Fallthrough

In this situation, a plugin handles the query, but the reply it got from its backend (i.e. maybe it got NXDOMAIN) is such that it wants other plugins in the chain to take a look as well. If fallthrough is provided (and enabled!), the next plugin is called. A plugin that works like this is the hosts plugin. First, a lookup in the host table (/etc/hosts) is attempted, if it finds an answer, it returns that. If not, it will fallthrough to the next one in the hope that other plugins may return something to the client.

Query is processed with a hint

A plugin of this kind will process a query, and will always call the next plugin. However, it provides a hint that allows it to see the response that will be written to the client. A plugin that does this is metrics. It times the duration …

Unregistered Plugins

There is another, special class of plugins that don’t handle any DNS data at all, but influence how CoreDNS behaves in other ways. Take for instance the bind plugin that controls to which interfaces CoreDNS should bind. The following plugins fall into this category:

  • bind - as said, control to what interfaces to bind.
  • root - set the root directory where CoreDNS plugins should look for files.
  • health - enable http health check endpoint.

Anatomy of Plugins

A plugin consists out of a Setup, Registration, and Handler part.

The Setup parses the configuration and the Plugin’s Directives (those should be documented in the plugin’s README).

The Handler is the code that processes the query and implements all the logic.

The Registration part registers the plugin in CoreDNS - this happens when CoreDNS is compiled. All of the registered plugins can be used by a Server. The decision of which plugins are configured in each Server happens at run time and is done in CoreDNS’s configuration file, the Corefile.

Plugin Documenation

Each plugin has its own README detailing how it can be configured. This README includes examples and other bits a user should be aware of. Each of these READMEs end up on, and we also compile them into manual pages.




There are various pieces that can be configured in CoreDNS. The first is determining which plugins you want to compile into CoreDNS. The binaries we provide have all plugins, as listed in plugin.cfg, compiled in. Adding or removing is easy, but requires a recompile of CoreDNS.

Thus most users use the Corefile to configure CoreDNS. When CoreDNS starts, and the -conf flag is not given, it will look for a file named Corefile in the current directory. That file consists of one or more Server Blocks. Each Server Block lists one or more Plugins. Those Plugins may be further configured with Directives.

The ordering of the Plugins in the Corefile does not determine the order of the plugin chain. The order in which the the plugins are executed is determined by the ordering in plugin.cfg.

Comments in a Corefile are started with a #. The rest of the line is then considered a comment.

Environment Variables

CoreDNS supports environment variables in its configuration. They can be used anywhere in the Corefile. The syntax is {$ENV_VAR} (a more Windows-like syntax {%ENV_VAR%} is also supported). CoreDNS substitutes the contents of the variable while parsing the Corefile.

Importing Other Files

See the import plugin. This plugin is a bit special in that it may be used anywhere in the Corefile.

Reusable Snippits

A special case of importing files is a snippet. A snippet is defined by naming a block with a special syntax. The name has to be put in parentheses: (name). After that, it can be included in other parts of the configuration with the import plugin:

# define a snippet
(snip) {

. {
    import snip

Server Blocks

Each Server Block starts with the zones the Server should be authoritative for. After the zone name or a list of zone names (separated with spaces), a Server Block is opened with an opening brace. A Server Block is closed with a closing brace. The following Server Block specifies a server that is responsible for all zones below the root zone: .; basically, this server should handle every possible query:

. {
    # Plugins defined here.

Server blocks can optionally specify a port number to listen on. This defaults to port 53 (the standard port for DNS). Specifying a port is done by listing the port after the zone separated by a colon. This Corefile instructs CoreDNS to create a Server that listens on port 1053:

.:1053 {
    # Plugins defined here.

Note: if you explicitly define a listening port for a Server you can’t overrule it with the -dns.port option.

Specifying a Server Block with a zone that is already assigned to a server and running it on the same port is an error. This Corefile will generate an error on startup:

.:1054 {


.:1054 {


Changing the second port number to 1055 makes these Server Blocks two different Servers.

Specifying a Protocol

Currently CoreDNS accepts three different protocols: plain DNS, DNS over TLS and DNS over gRPC. You can specify what a server should accept in the server configuration by prefixing a zone name with a scheme.

  • dns:// for plain DNS (the default if no scheme is specified).
  • tls:// for DNS over TLS.
  • grpc:// for DNS over gRPC.


Each Server Block specifies a number of plugins that should be chained for this specific Server. In its most simple form, you can add a Plugin by just using its name in a Server Block:

. {

The chaos plugin makes CoreDNS answer queries in the CH class - this can be useful for identifying a server. With the above configuration, CoreDNS will answer with its version when getting a request:

$ dig @localhost -p 1053 CH version.bind TXT
version.bind.		0	CH	TXT	"CoreDNS-1.0.5"

Most plugins allow more configuration with Directives. In the case of the chaos plugin we can specify a VERSION and AUTHORS as shown in its syntax:


chaos [VERSION] [AUTHORS...]
  • VERSION is the version to return. Defaults to CoreDNS-<version>, if not set.
  • AUTHORS is what authors to return. No default.

So, this adds some Directives to the chaos plugin that will make CoreDNS will respond with CoreDNS-001 as its version:

. {
    chaos CoreDNS-001

Other plugins with more configuration options have a Plugin Block, which, just as a Server Block, is enclosed in an opening and closing brace:

. {
    plugin {
       # Plugin Block

We can combine all this and have the following Corefile, which sets up 4 zones serving on two different ports: {
} {
} {

.:53 {
    proxy .

When parsed by CoreDNS, this will result in the following setup:

CoreDNS: Zones, plugins and query routing

External Plugins

External plugins are plugins that are not compiled into the default CoreDNS. You can easily enable them, but you’ll need to compile CoreDNS your self.

Possible Errors

The health plugin’s documentation states “This plugin only needs to be enabled once”, which might lead you to think that this would be a valid Corefile:


. {

But this doesn’t work and leads to a somewhat cryptic error:

"Corefile:3 - Error during parsing: Unknown directive '.'".

What happened here? health is seen as a zone (and the start of a Server Block). The parser expects to see plugin names (cache, etcd, etc.), but instead the next token is ., which isn’t a plugin. The Corefile should be constructed as follows:

. {

That line in the health plugin’s documentation means that once health is specified, it is global for the entire CoreDNS process, even though you’ve only specified it for one server.




Here you can find a bunch of configurations for CoreDNS. All setups are done assuming you are not the root user and hence can’t start listening on port 53. We will use port 1053 instead, using the -dns.port flag. In every setup, the configuration file used is the CoreDNS’ default, named Corefile. This means we don’t need to specify the configuration file with the -conf flag. In other words, we start CoreDNS with ./coredns -dns.port=1053 -conf Corefile, which can be abbreviated to ./coredns -dns.port=1053.

All DNS queries will be generated with the dig tool, the gold standard for debugging DNS. The full command line we use here is:

$ dig -p 1053 @localhost +noall +answer <name> <type>

But we shorten it in the setups below, so dig A is really dig -p 1053 @localhost +noall +answer A

Authoritative Serving From Files

This setup uses the file plugin. Note the external redis plugin enables authoritative serving from a Redis Database. Let’s continue with the setup using file.

The file we create here is a DNS zone file, and it can have any name (file plugin doesn’t care). The data we are putting in the file is for the zone

In your current directory, create a file named and put the following contents in it:

@	3600 IN	SOA (
				2017042745 ; serial
				7200       ; refresh (2 hours)
				3600       ; retry (1 hour)
				1209600    ; expire (2 weeks)
				3600       ; minimum (1 hour)

	3600 IN NS
	3600 IN NS

www     IN A
        IN AAAA  ::1

The last two lines are defining a name with two addresses, and (the IPv6) ::1.

Next, create this minimal Corefile that handles queries for this domain and adds the log plugin to enable query logging: {

Start CoreDNS and query it with dig:

$ dig AAAA    3600    IN  AAAA    ::1

It works. Because of the log plugin, we should also see the query being logged:

::1 - [22/Feb/2018:10:21:01 +0000] "AAAA IN udp 45 false 4096" NOERROR qr,aa,rd,ra 121 170.195┬Ás

The above logs show us the address CoreDNS replied from (::1) and the time and date it replied. Furthermore, it logs the query type, the query class, the query name, the protocol used (udp), the size in bytes of the incoming request, the DO bit state, and the advertised UDP buffer size. This is data from the incoming query. NOERROR signals the start of the reply, which is the Response Code sent back, followed by the set of flags on the reply: qr,aa,rd,ra, the size of the reply in bytes (121), and the duration it took to get the reply.


CoreDNS can be configured to forward traffic to a recursor. We currently have two plugins that allow for this, proxy and forward. Here, we will use forward and focus on the most basic setup: forwarding to Google Public DNS ( and Quad9 DNS (

We don’t need to create anything except for a Corefile with the configuration we want. In this case, we want all queries hitting CoreDNS to be forward to either or

. {
    forward .

Note that forward and proxy allow you to fine tune the names it will send upstream. Here, we chose all names (.). For instance: forward would only forward names within the domain.

Start CoreDNS and test it with dig:

$ dig AAAA	25837	IN	AAAA	2606:2800:220:1:248:1893:25c8:194

And in the logs:

:1 - [22/Feb/2018:10:34:39 +0000] 36325 "AAAA IN udp 45 false 4096" NOERROR qr,rd,ra,ad 73 1.859369ms

See the Authoritative Serving from Files section on what this log line conveys.

Forwarding Domains To Different Upstreams

A common scenario you may encounter is that queries for need to go to and the rest should be resolved via the name servers in /etc/resolv.conf. There are two ways that could be implemented in a Corefile; one way that may work (depending on the plugin’s implementation) and a way that is guaranteed to work.

Take this Corefile as an example:

. {
    forward . /etc/resolv.conf

The intent is to grab all possible queries (this Server Block is authoritative for the root domain), and then use the per-zone filtering of the forward plugin. Spoiler alert: this does not work. The reason is that the forward plugin can only be used once in a Server Block (it used to silently overwrite the previous configuration; now the above config triggers an error).

The above use case is a very valid one, so how do you implement this in CoreDNS? The quick answer is by using multiple Server Blocks, one for each of the domains you want to route on. Doing so results in this Corefile: {
    forward .

. {
    forward . /etc/resolv.conf

This leaves the domain routing to CoreDNS, which also handles special cases like DS queries. Having two smaller Server Blocks instead of one has no negative effects except that your Corefile will be slightly longer. Things like snippets and the import will help there.






Recursive Resolver

CoreDNS does not have a native (i.e. written in Go) recursive resolver, but there is an (external) plugin that utilizes libunbound. For this setup to work, you first have to recompile CoreDNS and enable the unbound plugin. Super quick primer here (you must have the CoreDNS source installed):

  • Add to plugin.cfg.
  • Do a go generate, followed by make.

Note: the unbound plugin needs cgo to be compiled, which also means the coredns binary is now linked against libunbound and not a static binary anymore.

Assuming this worked, you can then enable unbound with the following Corefile:

. {

cache has been included, because the (internal) cache from unbound is disabled to allow the cache’s metrics to works just like normal.



Writing Plugins

As mentioned before in this manual, plugins are the thing that make CoreDNS tick. We’ve seen a bunch of configuration in the previous section, but how can you write your own plugin?

See Writing Plugins for CoreDNS for an older post on this subject. The documented in CoreDNS’s source also has some background and talks about styling the

The canonical example plugin is the example plugin. Its github repository shows the most minimal code (with tests!) that is needed to create a plugin.

It has:

  1. setup.go and setup_test.go, which implement the parsing of configuration from the Corefile. The (usually named) setup function is called whenever the Corefile parser see the plugin’s name; in this case, “example”.
  2. example.go (usually named <plugin_name>.go), which contains logic for handling the query, and example_test.go, which has basic units tests to check if the plugin works.
  3. The that documents in a Unix manual style how this plugin can be configured.
  4. A LICENSE file. For inclusion in CoreDNS, this needs to have an APL like license.

The code also has extensive comments; feel free to fork it and base your plugin off of it.

How Plugins Are Called

When CoreDNS wants to use a plugin it calls the method ServeDNS. ServeDNS has three parameters:

  • a context.Context;
  • a dns.ResponseWriter that is basically the client’s connection;
  • a *dns.Msg that is the request from the client.

ServeDNS returns two values: a (response) code and an error. The error is logged when the errors is used in this server.

The code tells CoreDNS if a reply has been written by the plugin chain or not. In the latter case, CoreDNS will take care of that. For the code’s values, we reuse the DNS return codes (rcodes) from the dns package.

CoreDNS treats:

  • SERVFAIL (dns.RcodeServerFailure)
  • REFUSED (dns.RcodeRefused)
  • FORMERR (dns.RcodeFormatError)
  • NOTIMP (dns.RcodeNotImplemented)

as special and will then assume nothing has been written to the client. In all other cases, it assumes something has been written to the client (by the plugin).

See this post on how to compile CoreDNS with your plugin.

Logging From a Plugin

If your plugin needs to output a log line, you should use the log package. CoreDNS does not implement log levels. The standard way of outputting is: log.Printf("[LEVEL] ..."), and LEVEL can be: INFO, WARNING or ERROR.

In general, logging should be left to the higher layers when returning an error. However, if there is a reason to consume the error but still notify the user, then logging in the plugin can be acceptable.


When exporting metrics, the Namespace should be plugin.Namespace (=“coredns”), and the Subsystem should be the name of the plugin. The for the plugin should then also contain a Metrics section detailing the metrics. If the plugin supports dynamic health reporting, it should also have a Health section detailing some of its inner workings.


Each plugin should have a explaining what the plugin does and how it is configured. The file should have the following layout:

  • Title: use the plugin’s name
  • Subsection titled: “Named” with <plugin name> - <one line description>, i.e. NAME DASH DESCRIPTION
  • Subsection titled: “Description” with a longer description and all the options the plugin supports.
  • Subsection titled: “Syntax” detailing syntax and supported directives.
  • Subsection titled: “Examples”.
  • Optional Subsection titled: “See Also”, that references external documentation, like IETF RFCs.
  • Optional Subsection titled: “Bugs” that lists things that do not work yet.

More sections are, of course, possible.


We use the Unix manual page style:

  • The name of the plugin in the running text should be italic: *plugin*.
  • All CAPITAL user supplied arguments in the running text reference use strong text: **EXAMPLE**.
  • Optional text is in block quotes: [optional].
  • Use three dots to indicate multiple options are allowed: arg....
  • Item used literal: literal.

Example Domain Names

Please be sure to use or in any examples and tests you provide. These are the standard domain names created for this purpose. If you don’t, there is a chance your fantasy domain name is registered by someone and will actually serve web content (which you may like or not).


In a perfect world, the following would be true for plugins: “Either you are responsible for a zone or not”. If the answer is “not”, the plugin should call the next plugin in the chain. If “yes” it should handle all names that fall in this zone and the names below - i.e. it should handle the entire domain and all sub domains.

TODO(miek): ref to “Query Is Proccessed with Fallthrough”

. {
    file db.example

In this example the file plugin is handling all names below (and including) If a query comes in that is not a subdomain (or equal to) the next plugin is called.

Now, the world isn’t perfect, and there are good reasons to “fallthrough” to the next middleware, meaning a plugin is only responsible for a subset of names within the zone. The first of these to appear was the reverse plugin that synthesizes PTR and A/AAAA responses (useful with IPv6).

The nature of the reverse plugin is such that it only deals with A/AAAA and PTR, and then only for a subset of the names. Ideally, you would want to layer reverse in front of another plugin such as file or auto (or even proxy). This means reverse handles some special reverse cases and all other requests are handled by the backing plugin. This is exactly what “fallthrough” does. To keep things explicit we’ve opted that plugins implementing such behavior should implement a fallthrough keyword.

The fallthrough directive should optionally accept a list of zones. Only queries for records in one of those zones should be allowed to fallthrough.

Qualifying for main repo

Plugins for CoreDNS can live out-of-tree. plugin.cfg defaults to CoreDNS’s repo, but external repos work fine. So when do we consider the inclusion of a new plugin in the main repo?

  • The plugin authors should be willing to maintain the plugin, i.e. your GitHub handle will be listed in its OWNERS file.
  • The plugin should be useful for other people. “Useful” is a subjective term, but it should bring something new to CoreDNS.
  • It should be sufficiently different from other plugins to warrant inclusion.
  • Current Internet standards need be supported: IPv4 and IPv6, so A and AAAA records should be handled (if your plugin is in the business of dealing with address records, that is).
  • It must have tests.
  • It must have a for documentation.