Signing a Delegated Subdomain

If you are already familiar with DNSSEC this is quite easy: How to sign a delegated subdomain zone. For the sake of completeness I am showing how to generate and use the appropriate DS record in order to preserve the chain of trust for DNSSEC.

This blogpost is part of a series about DNSSEC. Refer to this list for all articles.


You already have a DNSSEC signed zone, in my example: Beside hostnames within this domain you have a delegated zone with its own nameservers (= NS records). Now you must not only sign this new zone, in my example:, but you must also preserve the chain of trust. This is done by the delegation signer DS record which is placed in the parent zone. I assume that you already have the delegated zone working, i.e., NS records for it, zone file (SOA), etc.

Signing the Subdomain Itself

(Just a recap from my previous blogposts.) That is: Generating the KSK and ZSK, adjusting its ownership to be readable by BIND, and inserting the NSEC3 parameters in order to use NSEC3 rather than NSEC:


Using the DS Record

Note that this process is exactly the same as for your primary domain. For your domain you already have sent the DS record to your parent zone (such as .de or .com). Now it’s much easier since you’re owning both, the domain (in my case: AND the subdomain ( That is:

1) Generate the DS record from the KSK you created for your subdomain. Use the small  dnssec-dsfromkey program with the -2 option to have only the SHA-256 output (refer to IANA: Delegation Signer (DS) Resource Record (RR) Type Digest Algorithms):

This resource record has an owner name of the delegated subdomain ( and the following four fields (refer to RFC 4034 “Resource Records for the DNS Security Extensions” section 5: The DS Resource Record):

  • the key tag 17463 which identifies the KSK,
  • the algorithm used for the KSK 10 = RSASHA512 as used while creating the KSK above,
  • the digest type 2 = SHA-256 and
  • the digest of the KSK (and some more fields) itself.

2) Place this single DS record into your parent zone. After a reload you’re done. ;)


Note the “ad” flag for all of those queries. (Only when resolved by a DNSSEC validating resolver.) You can now query the NS records for your subdomain:

its DNSKEYs (KSK and ZSK):

as well as the DS record in the parent zone:

Querying a hostname within the delegated subdomain shows the “ad” flag as well (line 7), which proves its authenticity:

Testing NSEC3 with a unvalid name to show up the NSEC3 records works as well:


That’s it! Congrats.

Some more DNSViz

Of course you can use DNSViz again to graph the chain of trust. For my test hostname this looks like that:

During my implementation of this delegated subdomain I queried DNSViz after each step about the SOA record for the the subdomain which showed these graphs:

  1. Delegated zone was neither signed nor did the DS record exist
  2. Delegated zone was signed but the DS record did not exist
  3. Completely signed and secure

Featured image “Vorhängeschlösser mit Zahlenschloss” by Marco Verch is licensed under CC BY 2.0.

8 thoughts on “Signing a Delegated Subdomain

  1. Hello,
    thank you for this great post.
    I’m owning a domain name and I’m hosting the dns server. It’s configured with DNSSEC. I’m intending to use a delegated subdomain on Google Cloud Plateform with Cloud DNS. I’ve created the subdomain DNS, and I’ve configured the delegation on the parent, as well as taking the DS record from the public key. But now the configuration is stalled to this step ‘Delegated zone was signed but the DS record did not exist’. I’ve written down the DS record myself in the parent record, so it should be there, but any request to my dns server (the parent) just returns the SOA.
    Any idea?

    1. Hello y0m,

      that sounds like your DNS server does not have the DS record. For whatever reason. Maybe you have a syntax error?

      It’s hard for me to troubleshoot you issue, since I don’t have any more information about your servers, used versions, and so on.


      1. Here are informations anonymized.
        I’m using BIND 9.14 on FreeBSD, with DNSSEC for the parent zone.
        And for the child zone, I’m using Google Cloud DNS with DNSSEC.
        Do you need more informations?
        $ORIGIN .
        $TTL 180 IN SOA (
        2019 ; serial
        21600 ; refresh (6 hours)
        1800 ; retry (30 minutes)
        1209600 ; expire (2 week)
        180 ; minimum (3 minutes)
        ) IN NS IN NS IN A IN AAAA dead::beef IN A IN AAAA dead::beef
        $include /usr/local/etc/namedb/keys/
        $include /usr/local/etc/namedb/keys/ IN DS 56789 8 2 sha256token IN NS IN NS IN NS IN NS (Google Cloud DNS with DNSSEC) 21600 IN SOA 3 21600 3600 259200 300 21600 IN NS 21600 IN NS 21600 IN NS 21600 IN NS 300 IN A

        *Google Cloud DNS informations for registrar setup, in this case: the parent zone*
        Type Data
        56789 8 2 sha256token

        1. Well, this looks good at a first glance. Especially this line:
          “ IN DS 56789 8 2 sha256token”

          When you’re on the FreeBSD machine, what’s the output of something like:
          “dig ds @localhost”
          Is it returning the DS record? It should.

          (Just to be sure: You have incremented the serial number and reloaded the zone, did you?)

          1. I’m getting this:

            $ dig ds @localhost

            ; <> DiG 9.14.1 <> ds @localhost
            ;; global options: +cmd
            ;; Got answer:
            ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 33018
            ;; flags: qr aa rd; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1
            ;; WARNING: recursion requested but not available

            ;; OPT PSEUDOSECTION:
            ; EDNS: version: 0, flags:; udp: 4096
            ; COOKIE: dafd459e746736d3204b73eb5cd2cbf60645fda2e545ddfc (good)
            ;; QUESTION SECTION:
            ; IN DS

            ;; AUTHORITY SECTION:
   180 IN SOA 2019 21600 1800 1209600 180

            ;; Query time: 0 msec
            ;; SERVER: ::1#53(::1)
            ;; WHEN: mer. mai 08 14:30:46 CEST 2019
            ;; MSG SIZE rcvd: 114

            I've always incremented the serial in the SOA, I've hidden this a bit, but right now with all the tests I've made, it's 2019050714, which is the date + a 2 digits number. I've never had to reconfig 99 times a day.
            As you can see, that's just like I would have not written the DS record in the parent zone.

            1. Hm, that’s strange. Maybe there is a typo in the DS record in your zone? Or any other typo? Is “named-checkconf -z” okay? Is it showing your most current serial number?

              This is what is looks like on my DNS server, when asking for the DS of the child zone:

              weberjoh@vm24-ns0:~$ dig ds @localhost

              ; < <>> DiG 9.11.3-1ubuntu1.7-Ubuntu < <>> ds @localhost
              ;; global options: +cmd
              ;; Got answer:
              ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 28558 ;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ;; WARNING: recursion requested but not available ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 4096 ; COOKIE: be250f559c800ce6ec34c9775cd2d8b563a1099a7d76ac04 (good) ;; QUESTION SECTION: ; IN DS ;; ANSWER SECTION: 3600 IN DS 45918 10 2 29E4AE014492DEED9610422977D3AEC6CC8C42CBD08A4E5919E3FCCE 75553B21 ;; Query time: 0 msec ;; SERVER: ::1#53(::1) ;; WHEN: Wed May 08 13:25:09 UTC 2019 ;; MSG SIZE rcvd: 120 ### My DS record in the zone file is: " IN DS 45918 10 2 29E4AE014492DEED9610422977D3AEC6CC8C42CBD08A4E5919E3FCCE75553B21" Sorry, I don't have any more ideas right now...

              1. I’ve generated the DS record from the DNSKEY record this way:

                $ dig dnskey | dnssec-dsfromkey -f –
       IN DS 56789 8 1 40-DIGIT-HASH
       IN DS 56789 8 2 SHA256-TOKEN

                The named-checkconf -z does not report any error, and the line for the shows:

                zone loaded serial 2019050714 (DNSSEC signed)

                I clearly don’t understand :)

  2. Just to let you and readers know, I’ve found the origin of my problem. I’m using DNSSEC since 2015 or so, and at the time I had set up snippets to help me sign zone every time I’m making changes. A specific option in the dnssec-signzone command was removing DS records from the signed zone.

    Generate DS records for child zones from dsset- or keyset- file.
    Existing DS records will be removed.

    That was a terrible idea, I don’t even remember why I first added this options, even though I remember clearly to have taken some time to read options and what they are doing.

    My issue is fixed now. My Google Cloud DNS child zone (subzone, subdomain) is linked to my parent zone.

Leave a Reply

Your email address will not be published. Required fields are marked *