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 the (all excellent) BIND, Knot, PowerDNS and Unbound (technically a resolver, but still worthy 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 functionally 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.

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

CoreDNS is powered by plugins.

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




CoreDNS is written in Go, but unless you want to develop plugins are 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 organisation.

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 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 response 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 returns 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.




When 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 it’s 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 this 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 want 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 up on 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. When the last plugin in the chain also fails to process a query, CoreDNS will return SERVFAIL back to the client.

Query Is Proccessed 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 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 hopes other plugins may return something to the client.

Query is Processes with a Hint

A plugin of this kind will process a query, and will always call the next plugin. But 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 …

Unregistrered Plugins

There is another, special class of plugins that don’t handle any DNS data at all, but influence how CoreDNS behaves in other way. Take for instance the bind plugin that control 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 implement all the logic.

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

Plugin Documenation

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




There are to 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 files consists out 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 this Server should be authoritative for. After this 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 the port number to listen on. This defaults to port 53 (the standard port for DNS). Specifying the port is done by listing after the zone separated with 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:

.:1054 {


.:1054 {


Will generate an error on startup. 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 if the configuration, by prefixing a zone name with a scheme, use:

  • 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 plugin that should be chained for this specific Server. In its most simple form you can add a Plugin but just using its name in a Server Block:

. {

The chaos plugin makes CoreDNS answer queries in the CH class - this can be useful to identify 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 it 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 that have more configuration options, have a Plugin Block, which just as a Server Block is enclosed in an opening and closing brace.

. {
    plugin {
       # Plugin Block

If we all combine all this and have the following Corefile, that setup 4 zones, serving on two different ports. {
} {
} {

.:53 {
    proxy .

When parsed by CoreDNS will result in 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’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 the somewhat cryptic error:

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

What happens here? health is seen as zone (and the start of a Server Block). The parser expect 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’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 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 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 nam with two addresses, and (the IPv6) ::1.

Next, create this minimal Corefile the 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 did. Further it logs the query type, query class and the query name. Next 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. Then the set of flags on the reply: qr,aa,rd,ra. The size of the reply in byes (121) and the duration it took to get the reply.


CoreDNS can be configured to forward traffic to an recursor. We currently have two plugin 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 file out 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 choose 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 Authoritative Serving from Files section on what this log line conveys.

Forwarding Domains To Different Upstreams

A likely scenario that you may have 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. A way that may work (depends on the plugin’s implementation) and a way guaranteed to work.

I.e. take this Corefile as an example:

. {
    forward . /etc/resolv.conf

The intent is to grab all possible queries (the 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 here being 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).

But the above use case is a very valid one, so how do you implement this in CoreDNS? The quick (example Corefile below) answer is: multiple Server Blocks, each for the domains you want to route on. Doing so, result in this Corefile: {
    forward .

. {
    forward . /etc/resolv.conf

This leave the domain routing to CoreDNS, which also handles special cases like DS queries. Having two, instead of one Server Block has no negatives 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. So 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, this 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’ source also has some background and talks about styling the

The canonical example plugin, is the example plugin. It’s github repository shows the most minimal code (with tests!) that is needed to create plugin.

It has:

  1. setup.go and setup_test.go that implement that parse the 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) handled the logic of handling the query, and example_test.go 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 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;
  • dns.ResponseWriter that is basically, the client’s connection;
  • *dns.Msg 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 codes 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 outputing is: log.Printf("[LEVEL] ..."), and LEVEL can be: INFO, WARNING or ERROR.

In general, logging should be left to the higher layers by 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 Health section detailing on 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* - one line description, i.e. NAME DASH DESCRIPTION
  • Subsection titled: “Description” has a longer description and all the options the plugin supports.
  • Subsection titled: “Syntax”, syntax and supported directives.
  • Subsection titled: “Examples”
  • Optional Subsection titled: “See Also”, that refences external documented, like IETF RFCs.
  • Optional Subsection titled: “Bugs” that lists omission of things that not work yet.

More sections are of course possible.


We use the Unix manual page style:

  • The name of plugin in the running text should be italic: plugin.
  • all CAPITAL: user supplied argument, in the running text references this use strong text: **: EXAMPLE.
  • Optional text: 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 fantansy domain name is registred by someone and will actually serve web content (which you may like or not).


In a perfect world the following would be true for plugin: “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 middlware, 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 synthesis 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 off another plugin such as file or auto (or even proxy). This means reverse handles some special reverse cases and all other request are handled by the backing plugin. This is exactly what “fallthrough” does. To keep things explicit we’ve opted that plugins implement 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’ 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 it’s OWNERS file.
  • Next, 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 plugin 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.