#dns #api

app constellation-server

Pluggable authoritative DNS server. Entries can be added & removed from an HTTP REST API.

21 stable releases

✓ Uses Rust 2018 edition

new 1.8.0 Jun 14, 2019
1.7.0 May 29, 2019
1.6.1 Apr 10, 2019
1.5.1 Jan 27, 2019
0.0.1 Apr 10, 2018

#7 in Web programming

Download history 40/week @ 2019-02-28 3/week @ 2019-03-07 3/week @ 2019-03-14 67/week @ 2019-03-21 21/week @ 2019-03-28 40/week @ 2019-04-04 12/week @ 2019-04-11 1/week @ 2019-04-18 29/week @ 2019-04-25 4/week @ 2019-05-02 41/week @ 2019-05-09 24/week @ 2019-05-16 50/week @ 2019-05-23 26/week @ 2019-05-30 105/week @ 2019-06-06

142 downloads per month

MPL-2.0 license

3.5K SLoC


Build Status Dependency Status Buy Me A Coffee

Pluggable authoritative DNS server. Entries can be added & removed from an HTTP REST API.

Constellation is a small authoritative server that lets you manage DNS entries from an HTTP REST API, in a generic way. It can be plugged to your existing infrastructure to manage DNS records for users of your service, eg. to configure outbound email records that cannot be easily wildcarded in a traditional DNS server (DKIM, DMARC, SPF records).

DNS entries are stored in Redis. The DNS database can thus be easily modified and dumped for backup purposes.

🇫🇷 Crafted in Angers, France.


Who uses it?


👋 You use Constellation and you want to be listed there? Contact me.


  • Pluggable authoritative DNS server, comes handy if you need to generate eg. email sub-domains for your users (with DKIM, DMARC and SPF records).
  • HTTP REST API to check, read, insert, modify and delete DNS records on the fly.
  • Persistence layer in Redis. This means you can run multiple Constellations hitting against the same database on the network. You can even shard Redis if you need fault tolerance on the DNS data store.
  • Geo-DNS to serve records on a location basis. For instance, serve the IP to your US server for all North America users, and fallback to Europe for the rest. Based on MaxMind GeoLite2 free database, that is automatically updated when necessary.

How to use it?


Constellation is built in Rust. To install it, either download a version from the Constellation releases page, use cargo install or pull the source code from master.

Install from source:

If you pulled the source code from Git, you can build it using cargo:

cargo build --release

You can find the built binaries in the ./target/release directory.

Install libssl-dev (ie. OpenSSL headers) before you compile Constellation. SSL dependencies are required for the Geo-DNS database updater.

Install from Cargo:

You can install Constellation directly with cargo install:

cargo install constellation-server

Ensure that your $PATH is properly configured to source the Crates binaries, and then run Constellation using the constellation command.

Install from Docker Hub:

You might find it convenient to run Constellation via Docker. You can find the pre-built Constellation image on Docker Hub as valeriansaliou/constellation.

Pre-built Docker version may not be the latest version of Constellation available.

First, pull the valeriansaliou/constellation image:

docker pull valeriansaliou/constellation:v1.8.0

Then, seed it a configuration file and run it (replace /path/to/your/constellation/config.cfg with the path to your configuration file):

docker run -p 53:53/udp -p 8080:8080 -v /path/to/your/constellation/config.cfg:/etc/constellation.cfg -v /path/to/your/constellation/res/:/var/lib/constellation/ valeriansaliou/constellation:v1.8.0

In the configuration file, ensure that:

  • dns.inets is set to [] (this lets Constellation DNS be reached from outside the container)
  • http.inet is set to (this lets Constellation REST API be reached from outside the container)
  • geo.database_path is set to /var/lib/constellation/geo/ (this is where the GeoIP database is stored)

Constellation will be reachable by DNS resolvers from udp://localhost:53; while its internal REST API will be reachable from http://localhost:8080.

Also, do not forget to initialize the GeoIP database in the ./res/geo/ folder (refer to the part on how to Initialize GeoIP below).


Use the sample config.cfg configuration file and adjust it to your own environment.

Available configuration options are commented below, with allowed values:


  • log_level (type: string, allowed: debug, info, warn, error, default: error) — Verbosity of logging, set it to error in production


  • inets (type: array[string], allowed: IPs + ports, default: [, [::]:53]) — Hosts and UDP/TCP ports the DNS server should listen on
  • tcp_timeout (type: integer, allowed: seconds, default: 2) — Timeout of DNS over TCP connections
  • nameservers (type: array[string], allowed: domain names, default: no default) — Name server domains for all served domains
  • soa_master (type: string, allowed: domain names, default: no default) — SOA master domain for all zones served by this name server (name of primary NS server)
  • soa_responsible (type: string, allowed: email addresses as domain names, default: no default) — SOA responsible email for all zones served by this name server
  • soa_refresh (type: integer, allowed: seconds, default: 10000) — SOA record refresh value
  • soa_retry (type: integer, allowed: seconds, default: 2400) — SOA record retry value
  • soa_expire (type: integer, allowed: seconds, default: 604800) — SOA record expire value
  • soa_ttl (type: integer, allowed: seconds, default: 3600) — SOA record TTL value
  • record_ttl (type: integer, allowed: seconds, default: 3600) — DNS records TTL value


Specify your zone name eg. as: [[dns.zone.'crisp.email']] for zone base: crisp.email.


  • database_path (type: string, allowed: folder path, default: ./res/geo/) — Path to the folder containing the GeoIP database
  • database_file (type: string, allowed: file name, default: GeoLite2-Country.mmdb) — File name for the GeoIP2 MMDB database in the database folder (either free GeoLite2 or paid GeoIP2; disable geo.update_enable if you want to use a custom database)
  • update_enable (type: boolean, allowed: true, false, default: true) — Whether to enable GeoIP database updater or not
  • update_interval (type: integer, allowed: seconds, default: 864000) — Interval for which to refresh GeoIP database in seconds (1 week or more is recommended)
  • update_url (type: string, allowed: HTTP URL, default: https://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz) — URL to the compressed GeoIP MMDB file (supported: tar.gz), that is downloaded on refresh


  • inet (type: string, allowed: IPv4 / IPv6 + port, default: [::1]:8080) — Host and TCP port the HTTP API server should listen on
  • workers (type: integer, allowed: any number, default: 2) — Number of workers for the HTTP API server to run on
  • record_token (type: string, allowed: secret token, default: no default) — Record secret token for management API access (ie. secret password)


  • host (type: string, allowed: hostname, IPv4, IPv6, default: localhost) — Target Redis host
  • port (type: integer, allowed: TCP port, default: 6379) — Target Redis TCP port
  • password (type: string, allowed: password values, default: none) — Redis password (if no password, dont set this key)
  • database (type: integer, allowed: 0 to 255, default: 0) — Target Redis database
  • pool_size (type: integer, allowed: 0 to (2^32)-1, default: 8) — Redis connection pool size
  • max_lifetime_seconds (type: integer, allowed: seconds, default: 20) — Maximum lifetime of a connection to Redis (you want it below 5 minutes, as this affects the reconnect delay to Redis if a connection breaks)
  • idle_timeout_seconds (type: integer, allowed: seconds, default: 600) — Timeout of idle/dead pool connections to Redis
  • connection_timeout_seconds (type: integer, allowed: seconds, default: 5) — Timeout in seconds to consider Redis dead and reject DNS and HTTP API queries
  • cache_expire_seconds (type: integer, allowed: seconds, default: 60) — Time in seconds after which a locally-cached record expires and should be refreshed from Redis (this should be kept low)

Initialize GeoIP

As Constellation does not distribute a GeoIP database in its repository, you will need to fetch it from MaxMind before you run Constellation for the first time (Constellation will refuse to start otherwise).

Execute the provided script:


Note that once Constellation started from the GeoIP database you have manually initialized, it will keep the database up-to-date by checking and applying updates automatically in the background. This is a one-time operation.

Run Constellation

Constellation can be run as such:

./constellation -c /path/to/config.cfg

DNS records management (HTTP REST API)

To check, read, insert, modify and delete DNS records, you need to use the Constellation HTTP REST API, that listens on the configured http.inet interface from your config.cfg file.

API overview

Endpoint URL:

HTTP http://constellation.local:8080/zone/<zone_name>/record/<record_name>/<record_type>/


  • zone_name: The zone name (ie. base domain), eg. crisp.email
  • record_name: The record name to read or alter (ie. sub-domain or base domain), eg. inbound.@ for the inbound.crisp.email FQDN, or @ for the crisp.email FQDN
  • record_type: The DNS record type to read or alter for the record_name; either: a, aaaa, cname, mx, txt or ptr (open an issue if you need support for another record type)

Request headers:

  • Add an Authorization header with a Basic authentication where the password is your configured http.record_token.

Geo-DNS regions:

If you want to serve records to the nearest server using the Geo-DNS feature, you will need to set regions via the API, where:

  • Americas

    • nnam: Northern North America
    • snam: Southern North America
    • nsam: Northern South America
    • ssam: Southern South America
  • Europe

    • weu: Western Europe
    • ceu: Central Europe
    • eeu: Eastern Europe
    • ru: Russia
  • Middle East

    • me: Middle East
  • Africa

    • naf: Northern Africa
    • maf: Middle Africa
    • saf: Southern Africa
  • Asia

    • in: India
    • seas: Southeast Asia
    • neas: Northeast Asia
  • Oceania

    • oc: Oceania

Geo-DNS blackhole:

If you want to return an empty DNS response for blocked countries using the Geo-DNS feature, you will need to set blackhole via the API, to a list of blackholed ISO-3166 country codes (eg. FR for France).

API routes

Check if a DNS record exists

HTTP HEAD http://constellation.local:8080/zone/<zone_name>/record/<record_name>/<record_type>/

Example request:

HEAD /zone/crisp.email/record/@/mx HTTP/1.1
Authorization: Basic OlJFUExBQ0VfVEhJU19XSVRIX0FfU0VDUkVUX0tFWQ==

Example response:

HTTP/1.1 200 OK
Get a DNS record

HTTP GET http://constellation.local:8080/zone/<zone_name>/record/<record_name>/<record_type>/

Example request:

GET /zone/crisp.email/record/@/mx HTTP/1.1
Authorization: Basic OlJFUExBQ0VfVEhJU19XSVRIX0FfU0VDUkVUX0tFWQ==

Example response:

HTTP/1.1 200 OK
Content-Type: application/json

{"type":"mx","name":"@","ttl":600,"blackhole": null,"regions": null,"values":["1 inbound.crisp.email","10 inbound-failover.crisp.email"]}
Write a DNS record (or overwrite existing)

HTTP PUT http://constellation.local:8080/zone/<zone_name>/record/<record_name>/<record_type>/

Example request (standard):

PUT /zone/crisp.email/record/@/mx HTTP/1.1
Authorization: Basic OlJFUExBQ0VfVEhJU19XSVRIX0FfU0VDUkVUX0tFWQ==
Content-Type: application/json; charset=utf-8

{"values":["1 inbound.crisp.email","10 inbound-failover.crisp.email"],"ttl":600}

Example request (Geo-DNS):

PUT /zone/crisp.email/record/@/mx HTTP/1.1
Authorization: Basic OlJFUExBQ0VfVEhJU19XSVRIX0FfU0VDUkVUX0tFWQ==
Content-Type: application/json; charset=utf-8

{"regions":{"nnam":["10 inbound-geo.nnam.crisp.email"],"snam":["10 inbound-geo.snam.crisp.email"],"nsam":["10 inbound-geo.nsam.crisp.email"],"ssam":["10 inbound-geo.ssam.crisp.email"],"weu":["10 inbound-geo.weu.crisp.email"],"ceu":["10 inbound-geo.ceu.crisp.email"],"eeu":["10 inbound-geo.eeu.crisp.email"],"ru":["10 inbound-geo.ru.crisp.email"],"me":["10 inbound-geo.me.crisp.email"],"naf":["10 inbound-geo.naf.crisp.email"],"maf":["10 inbound-geo.maf.crisp.email"],"saf":["10 inbound-geo.saf.crisp.email"],"in":["10 inbound-geo.in.crisp.email"],"seas":["10 inbound-geo.seas.crisp.email"],"neas":["10 inbound-geo.neas.crisp.email"],"oc":["10 inbound-geo.oc.crisp.email"]},"values":["1 inbound.crisp.email","10 inbound-failover.crisp.email"],"ttl":600}

Example response:

HTTP/1.1 200 OK
Delete a DNS record

HTTP DELETE http://constellation.local:8080/zone/<zone_name>/record/<record_name>/<record_type>/

Example request:

DELETE /zone/crisp.email/record/@/mx HTTP/1.1
Authorization: Basic OlJFUExBQ0VfVEhJU19XSVRIX0FfU0VDUkVUX0tFWQ==

Example response:

HTTP/1.1 200 OK

🔥 Report A Vulnerability

If you find a vulnerability in Constellation, you are more than welcome to report it directly to @valeriansaliou by sending an encrypted email to valerian@valeriansaliou.name. Do not report vulnerabilities in public GitHub issues, as they may be exploited by malicious people to target production servers running an unpatched Constellation instance.

⚠️ You must encrypt your email using @valeriansaliou GPG public key: 🔑valeriansaliou.gpg.pub.asc.

🎁 Based on the severity of the vulnerability, I may offer a $200 (US) bounty to whomever reported it.


~477K SLoC