DNS server with per-client targeted responses
Go HTML Other
Latest commit 602643d Feb 13, 2018
Failed to load latest commit information.
applog Refactor log and type helpers; move healthcheck code to health/ (wip) Dec 12, 2016
countries query logging wip Jul 19, 2016
dns Finish redoing zone_test.go Dec 9, 2017
geodns-influxdb Add missing geodns-influxdb files Feb 13, 2017
health Add health check test json May 27, 2017
monitor Make monitor work again Feb 20, 2017
querylog Make query logging (globally) configurable Nov 13, 2016
server Return 0 records if all are unhealthy Mar 11, 2017
service service: Read extra args from env/ARGS Nov 17, 2016
targeting Fix ASN targeting support Feb 13, 2018
templates Upgrade go-bindata to latest version Jun 15, 2014
typeutil Refactor log and type helpers; move healthcheck code to health/ (wip) Dec 12, 2016
vendor Try using dep tool, update dependencies May 27, 2017
zones Improve reader_test to find geoip2 databases Feb 7, 2018
.gitignore v3 refactoring wip Feb 18, 2017
.travis.yml Fix GeoIP database for Travis-ci test May 28, 2017
CHANGES.md Update CHANGES.md for 2.7.0 Feb 13, 2017
Gopkg.lock dep 0.1.0 May 28, 2017
Gopkg.toml dep 0.1.0 May 28, 2017
LICENSE Add licensing information Mar 2, 2013
Makefile Cleanup FindLabels API, internal API for checking health status Mar 10, 2017
README.md Make query logging (globally) configurable Nov 13, 2016
build Improve build script Feb 18, 2017
config.go Guess the geoip2 directory if it's not explicitly configured Dec 9, 2017
config_test.go Check that the sample configuration parses Dec 12, 2016
dayduration.go Minor golint cleanups Jun 6, 2013
geodns.go wip: geoip2 fixes, tests May 27, 2017
http.go Cleanup FindLabels API, internal API for checking health status Mar 10, 2017
http_test.go Finish redoing zone_test.go Dec 9, 2017
templates.go Build templates.go with new esc Nov 14, 2016
templates_devel.go Use 'esc' and 'go generate' instead of 'go-bindata' Jun 5, 2015
util.go v3 refactoring wip Feb 18, 2017


GeoDNS in Go

This is the DNS server powering the NTP Pool system and other similar services. It supersedes the pgeodns server. Build Status


If you already have go installed, just run go get to install the Go dependencies. GeoDNS requires Go 1.4 or later.

You will also need the GeoIP C library, on RedHat derived systems that's yum install geoip-devel.

If you don't have Go installed the easiest way to build geodns from source is to download Go from http://code.google.com/p/go/downloads/list and untar'ing it in /usr/local/go and then run the following from a regular user account:

export PATH=$PATH:/usr/local/go/bin
export GOPATH=~/go
go get github.com/abh/geodns
cd ~/go/src/github.com/abh/geodns
go test
go build
./geodns -h

Sample configuration

There's a sample configuration file in dns/example.com.json. This is currently derived from the test.example.com data used for unit tests and not an example of a "best practices" configuration.

For testing there's also a bigger test file at:

mkdir -p dns
curl -o dns/test.ntppool.org.json http://tmp.askask.com/2012/08/dns/ntppool.org.json.big

Run it

After building the server you can run it with:

./geodns -log -interface 127.1 -port 5053

To test the responses run

dig -t a test.example.com @127.1 -p 5053


dig -t ptr @127.1 -p 5053

or more simply put

dig -x @127.1 -p 5053

The binary can be moved to /usr/local/bin, /opt/geodns/ or wherever you find appropriate.

Command options

Notable command line parameters (and their defaults)

  • -config="./dns/"

Directory of zone files (and configuration named geodns.conf).

  • -checkconfig=false

Check configuration file, parse zone files and exit

  • -interface="*"

Comma separated IPs to listen on for DNS requests.

  • -port="53"

Port number for DNS requests (UDP and TCP)

  • -http=":8053"

Listen address for HTTP interface. Specify as to only listen on localhost.

  • -identifier=""

Identifier for this instance (hostname, pop name or similar).

It can also be a comma separated list of identifiers where the first is the "server id" and subsequent ones are "group names", for example region of the server, name of anycast cluster the server is part of, etc. This is used in (future) reporting/statistics features.

  • -log=false

Enable to get lots of extra logging, only useful for testing and debugging. Absolutely not recommended in production unless you get very few queries (less than 1-200/second).

  • -cpus=1

Maximum number of CPUs to use. Set to 0 to match the number of CPUs available on the system. Only "1" (the default) has been extensively tested.

WebSocket interface

geodns runs a WebSocket server on port 8053 that outputs various performance metrics. The WebSocket URL is /monitor. There's a "companion program" that can use this across a cluster to show aggregate statistics, email for more information.

Runtime status

There's a page with various runtime information (queries per second, queries and most frequently requested labels per zone, etc) at /status.

StatHat integration

GeoDNS can post runtime data to StatHat. (Documentation)

Country and continent lookups

See zone targeting options below.

Weighted records

Most records can have a 'weight' assigned. If any records of a particular type for a particular name have a weight, the system will return max_hosts records (default 2).

If the weight for all records is 0, all matching records will be returned. The weight for a label can be any integer as long as the weights for a label and record type is less than 2 billion.

As an example, if you configure, weight 10, weight 20, weight 30, weight 40

with max_hosts 2 then .4 will be returned about 4 times more often than .1.

Configuration file

The geodns.conf file allows you to specify a specific directory for the GeoIP data files and other options. See the geodns.conf.sample file for example configuration.

The global configuration file is not reloaded at runtime.

Most of the configuration is "per zone" and done in the zone .json files. The zone configuration files are automatically reloaded when they change.

Zone format

In the zone configuration file the whole zone is a big hash (associative array). At the top level you can (optionally) set some options with the keys serial, ttl and max_hosts.

The actual zone data (dns records) is in a hash under the key "data". The keys in the hash are hostnames and the value for each hostname is yet another hash where the keys are record types (lowercase) and the values an array of records.

For example to setup an MX record at the zone apex and then have a different A record for users in Europe than anywhere else, use:

    "serial": 1,
    "data": {
        "": {
            "ns": [ "ns.example.net", "ns2.example.net" ],
            "txt": "Example zone",
            "spf": [ { "spf": "v=spf1 ~all", "weight": 1 } ],
            "mx": { "mx": "mail.example.com", "preference": 10 }
        "mail": { "a": [ ["", 100], ["", 50] ] },
        "mail.europe": { "a": [ ["", 0] ] },
        "smtp": { "alias": "mail" }

The configuration files are automatically reloaded when they're updated. If a file can't be read (invalid JSON, for example) the previous configuration for that zone will be kept.

Zone options

  • serial

GeoDNS doesn't support zone transfers (AXFR), so the serial number is only used for debugging and monitoring. The default is the 'last modified' timestamp of the zone file.

  • ttl

Set the default TTL for the zone (default 120).

  • targeting

  • max_hosts

  • contact

Set the soa 'contact' field (default is "hostmaster.$domain").

Zone targeting options


country continent

region and regiongroup

Supported record types

Each label has a hash (object/associative array) of record data, the keys are the type. The supported types and their options are listed below.

Adding support for more record types is relatively straight forward, please open a ticket in the issue tracker with what you are missing.


Each record has the format of a short array with the first element being the IP address and the second the weight.

[ [ "", 10], ["", 5] ]

See above for how the weights work.


Same format as A records (except the record type is "aaaa").


Internally resolved cname, of sorts. Only works internally in a zone.




The target will have the current zone name appended if it's not a FQDN (since v2.2.0).


MX records support a weight similar to A records to indicate how often the particular record should be returned.

The preference is the MX record preference returned to the client.

{ "mx": "foo.example.com" }
{ "mx": "foo.example.com", "weight": 100 }
{ "mx": "foo.example.com", "weight": 100, "preference": 10 }

weight and preference are optional.


NS records for the label, use it on the top level empty label ("") to specify the nameservers for the domain.

[ "ns1.example.com", "ns2.example.com" ]

There's an alternate legacy syntax that has space for glue records (IPv4 addresses), but in GeoDNS the values in the object are ignored so the list syntax above is recommended.

{ "ns1.example.net.": null, "ns2.example.net.": null }


Simple syntax

"Some text"

Or with weights

{ "txt": "Some text", "weight": 10 }


An SPF record is semantically identical to a TXT record with the exception that the label is set to 'spf'. An example of an spf record with weights:

{ "spf": "v=spf1 ~all]", "weight": 1 }

An spf record is typically at the root of a zone, and a label can have an array of SPF records, e.g

  "spf": [ { "spf": "v=spf1 ~all", "weight": 1 } , "spf": "v=spf1", "weight": 100]


An SRV record has four components: the weight, priority, port and target. The keys for these are "srv_weight", "priority", "target" and "port". Note the difference between srv_weight (the weight key for the SRV qtype) and "weight".

An example srv record definition for the _sip._tcp service:

"_sip._tcp": {
    "srv": [ { "port": 5060, "srv_weight": 100, "priority": 10, "target": "sipserver.example.com."} ]

Much like MX records, SRV records can have multiple targets, eg:

"_http._tcp": {
    "srv": [
        { "port": 80, "srv_weight": 10, "priority": 10, "target": "www.example.com."},
        { "port": 8080, "srv_weight": 10, "priority": 20, "target": "www2.example.com."}

License and Copyright

This software is Copyright 2012-2015 Ask Bjørn Hansen. For licensing information please see the file called LICENSE.