Lately I’ve grown more and more into building things myself, like GitLab. Today, I changed the DNS server to a self-built one, and shared my experience (PS: Now in order to realize the root domain CDN, I used Route 53 instead):

The self-built DNS in this article refers to the authoritative DNS, that is, the DNS configured for your own domain name, not the cache DNS configured on the client.

Advantages and Disadvantages

First of all, let me talk about the fatal disadvantages of using a self-built DNS server:

  1. If your server is down that day, the entire domain name related services will be down. Even if your mail receiving server is a third-party, you will not be able to receive mail.
  2. Basically, it must be a VPS with an open port and a fixed IP (and preferably at least two IPs) (of course it can be two hosts, just make sure that the configuration files are exactly the same), High requirements for service providers
  3. Individuals generally have insufficient experience in DNS operation and maintenance, which can easily lead to configuration errors
  4. Third-party DNS providers basically have DDOS defense, but your server may not have it. An attacker can directly attack your server through L7 DNS Flood, and then go back to the first problem.

Advantages of using a self-built DNS server:

  1. The degree of DIY is extremely high, almost all DNS functions can be configured, but they are all very complicated
  2. Can be built on an existing server without extra cost

In the end, I chose to use PowerDNS software (which is actually used by many service providers that provide DNS services). I installed its recently released version 4.0. Some features supported by this version:

  • EDNS Client Subnet
  • IPv6

Wait, that’s just what came to my mind. At the same time, PowerDNS supports many types of resolution records (at least the most I have seen so far): A, AAAA, AFSDB, ALIAS (also ANAME), CAA, CERT, CDNSKEY, CDS, CNAME, DNSKEY, DNAME, DS, HINFO, KEY, LOC, MX, NAPTR, NS, NSEC, NSEC3, NSEC3PARAM, OPENPGPKEY, PTR, RP, RRSIG, SOA, SPF, SSHFP, SRV, TKEY, TSIG, TLSA, TXT, URI, etc., and there are no columns that are not commonly used out, see all supported records. To be honest, there are some unpopular records that many resolvers do not support, but I need to use them, such as LOC, SSHFP and TLSA. Don’t know what this pile of records is for? See Wikipedia.

Briefly Describe the Installation Process

For detailed installation method see official documentation, you need to install pdns-server first, and then install pdns-backend-$backend . Backend is something you can choose by yourself. Commonly used ones are BIND and Generic MySQL. If you need GEODNS, you can use GEOIP. All lists see here . If you want to control the background of the web version, it may be more convenient to use MySQL. Both BIND and GEOIP are fine if it is only controlled by file. I use the GEOIP version. The GEOIP version is highly scalable and uses YAML files, which is more flexible and elegant. This article will talk about the GEOIP version: Install on Ubuntu (available in the system software source):

$ sudo apt install pdns-server
$ sudo apt install pdns-backend-geoip

Then modify the configuration file:

$ rm /etc/powerdns/pdns.d/* # delete Example

Install a Newer Version of PowerDNS

Many features, such as CAA records, require the new version of PowerDNS. Please go to the official website to configure the software source.

Install Geolocation Database

Note that you should already have the MaxMind GeoIP Lite database, if not, install it as follows:

Important update⚠️: Since April 1, 2018, the GeoIP database in DAT format cannot be automatically downloaded by the software. Please [go to the official website to manually download the corresponding database]( /). It needs to be in Binary format.

Create a file /etc/GeoIP.conf with:

# The following UserId and LicenseKey are required placeholders:
UserId 999999
LicenseKey 000000000000
# Include one or more of the following ProductIds:
# * GeoLite2-City - GeoLite 2 City
# * GeoLite2-Country - GeoLite2 Country
# * GeoLite-Legacy-IPv6-City - GeoLite Legacy IPv6 City
# * GeoLite-Legacy-IPv6-Country - GeoLite Legacy IPv6 Country
# * 506 - GeoLite Legacy Country
# * 517 - GeoLite Legacy ASN
# * 533 - GeoLite Legacy City
ProductIds 506 GeoLite-Legacy-IPv6-Country
DatabaseDirectory /usr/share/GeoIP

Then install geoipupdate, execute sudo apt install geoipupdate && mkdir -p /usr/share/GeoIP && geoipupdate -v , your database has been downloaded.

Configure PowerDNS

Create a file /etc/powerdns/pdns.d/geoip.conf with:

geoip-database-files=/usr/share/GeoIP/GeoLiteCountry.dat /usr/share/GeoIP/GeoIPv6.dat # Select IPv4 and IPv6 country modules
geoip-zones-file=/share/zone.yaml # The location of your YAML configuration file, anywhere

Create that YAML file, and then start writing Zone, here is an example (IPv6 is not required, all IPs should be filled in with external IPs, this article takes the country as an example, the order of the juxtaposed content does not matter):

# @see:
- domain:
  ttl: 300 # Default TTL duration
##### Default NS
      - a: # Your server's first IPv4 address
          ttl: 86400
      - aaaa: # Your server's first IPv6 address
          content: ::1
          ttl: 86400 # Second IPv4 address of your server (if not, same as above)
      - a:
          ttl: 86400
      - aaaa: # Second IPv6 address of your server (if not, same as above)
          content: ::2
          ttl: 86400
##### Root domain # records under the root domain name
      - soa:
          content: 86400 3600 604800 10800
          ttl: 7200
      - ns:
          ttl: 86400
      - ns:
          ttl: 86400
      - mx:
          content: 100 # weight [space] hostname
          ttl: 7200
      - mx:
          content: 100
          ttl: 7200
      - mx:
          content: 100
          ttl: 7200
      - a: # If you want to use the default TTL, you don't need to distinguish between the content and ttl fields
      - aaaa: 2001:470:fa6b::1
##### Servers list Your server list &beijing
      - a:
      - aaaa: ::1:1 &newyork
      - a:
      - aaaa: ::2:1 &japan
      - a:
      - aaaa: ::3:1 &uk
      - a:
      - aaaa: ::4:1 &france
      - a:
      - aaaa: ::5:1
##### GEODNS partition resolution
    # @see:
    # @see:\_3166-1\_alpha-3
    # unknown also is default
    # default *newyork # resolves to US by default
    # continent *japan # Asia to Japan *japan # Oceania resolves to Japan *france # Europe resolves to France *france # Africa to France
    # nation *beijing # China parsing Beijing *uk # UK resolves to UK
    #GEODNS [ '', '', '']

This configuration is equivalent to parsing to the partition. Due to some problems in this parsing at present, GEODNS cannot be set under the root domain name and subdomain at the same time. I have submitted feedback on this bug. (https://github .com/PowerDNS/pdns/issues/4276). If you want to only set the parsing precision at the continent level, just write one less level. If you need to be accurate to the city, you can write one more level, but you need to add the GeoIP city database to the configuration file. However, the city version of the free city database is not accurate, and you also need to purchase a commercial database, which is an additional cost.

Configure the Domain Name

Go to your domain name registrar, enter the background to modify the settings, and add a subdomain name server record to the domain name, as shown in the figure:

Add subnameserver record
Add subnameserver record

Since the NS to be set is under your own server, you must register your NS server IP address with the upper-level domain name (such as .com) on the domain name registrar, so that the upper-level domain name can resolve the IP of the NS and build your own DNS For example, there is a NS of its own under

$ dig ns +short

Then look at its upper-level domain name org:

$ dig org ns +short

Find any server and query the authoritative record (I don’t need +short ):

$ dig ns

It can be seen that the NS server in this org has already returned the records of This is why you need to fill in the IP address in the domain name registrar. However you’d better return the same NS and the same IP on the DNS server under your domain name as well. Finally, don’t forget to change the NS record of the domain name.

Some Advanced Writing of YAML

In the YAML I just used, I have actually used the advanced YAML writing method, that is, &variable sets variables, *variable uses variables, which is very similar to the LINK record under CloudXNS, for example, under CloudXNS you can write: 600 IN A 600 IN A 600 IN AAAA ::1 600 IN AAAA ::2 600 IN LINK

Then in your YAML record you can write: &www
  - a:
  - a:
  - aaaa: ::1
  - aaaa: ::2 *www

This is a high-level way of writing YAML without any additional support.

Add DNSSEC support

For details, you can Reference Documentation, run the following command:

$ mkdir /etc/powerdns/key
$ pdnsutil secure-zone
$ pdnsutil show-zone

The result returned by the last command is the record you need to set at the domain name registrar. It is not recommended to set all of them. Just set ECDSAP256SHA256 - SHA256 digest. Finally, check the settings online Test address 1 Test address 2, there may be a few days of caching time. My inspection results

Some Other Interesting Stuff

You can write this in YAML, to make it easier for you to debug:

  - TXT:
      content: "IP%af: %ip, Continent: %cn, Country: %co, ASn: %as, Region: %re, Organisation: %na, City: %ci"
      ttl: 0

These variables can be used as your GEODNS criteria, as well as checking your GEOIP database. Then, check the posture correctly:

$ random=`head -200 /dev/urandom md5` ; dig ${random}.ip.example txt +short
"IPv4:, Continent: as, Country: chn, ASn: unknown, Region: unknown, Organisation: unknown, City: unknown"

The IP address is the DNS cache server address (if you enable EDNS Client Subnet and the cache server supports it, then it is your own IP, but if you use, you will see that the last digit of your IP is 0), if you If you specify to check from your own server locally, then return your own IP address directly. Since I only have the country database installed, everything is Unknown except for the continent and country.

Advanced Use

Establish Distributed DNS

In general, it is a DNS resolution server of a Master and a Slave, but in this case, there may be problems with DNSSEC, so I set up two Master servers, automatically synchronize records, and set same DNSSEC Private Key , nothing seems to go wrong (after all, all records, including SOA, are exactly the same), my server’s current configuration

$ dig

Among them are two IPv4 and two IPv6, of which is an IP address using Anycast technology, which is provided by three servers behind it. belongs to the host of another service provider, so there is a backup after it hangs, which is more stable.

Anycast or Unicast?

A distributed DNS like mine is actually a combination of Unicast and Anycast. One problem is that connecting to one of them in one place will be faster, but the other will be slower. Only with a DNS cache server that supports asynchronous query or with GeoIP, it is possible to connect to the fastest DNS authoritative server, otherwise it is a random connection, and if a server hangs, then the corresponding IP of the server is obsolete. Anycast is an IP corresponding to multiple hosts, but I have no conditions to use it. This may cost more for individuals. Either you have an AS number and let the host provider give you access, or your host provider provides cross-regional access. Load Balancing IP. My VPS is in two different hosting companies, and I don’t have AS, so I can’t use Anycast. I think Anycast should be used for DNS services, because the IP corresponding to the DNS server cannot be GEODNS (because this is the root domain name that is resolved for you). After using Anycast, the fastest connection rate can basically be guaranteed, and a server has an IP address. Still usable. Additionally, DNS must forward both TCP and UDP port 53.

Automatically Switch when Downtime

How to realize automatic switchover when downtime? The process to achieve this is: Monitoring service finds downtime -> Sends a downtime request to the server -> The server processes the downtime, resolves to an alternate IP/pauses parsing monitoring service finds that the service is normal -> Sends a normal service request to the server -> The server processes the service normally, and the recovery analysis can create two YAML files, one is used by default, and the other is used when the server is down. When the monitoring service finds that the server is down, reload another YAML file, and then this is Downtime mode.