DNSSEC Explained: Verify the Chain of Trust Yourself
DNSSEC is the only thing that lets a resolver prove a DNS answer really came from the zone owner and was not changed on the way. It does not encrypt anything and it does not stop phishing, but when it is configured corre
DNSSEC is the only thing that lets a resolver prove a DNS answer really came from the zone owner and was not changed on the way. It does not encrypt anything and it does not stop phishing, but when it is configured correctly, a spoofed or tampered answer gets rejected instead of cached. When it is configured wrong, your whole domain can go dark for every validating resolver at once. Both outcomes are visible with nothing more than curl.
I build PixelValut, a set of browser-side developer and security tools, and I wanted to see how DNSSEC looks on real domains, including my own. I wrote this with help from AI tools and checked every command and claim myself. Every output below is from commands I ran today, November 2, 2026, against Google Public DNS over HTTPS.
What does DNSSEC actually protect?
Direct answer: DNSSEC adds digital signatures to DNS records so validating resolvers can verify that an answer is authentic and unmodified. Trust flows from the root zone through each parent's DS record to the child's DNSKEY. It protects integrity, not privacy, and a broken chain makes validating resolvers return SERVFAIL for the whole domain.
The chain of trust in one table
| Record | Lives in | What it holds | What it proves |
|---|---|---|---|
DNSKEY (flag 257, KSK) |
Your zone | Key-signing public key | Signs the DNSKEY set |
DNSKEY (flag 256, ZSK) |
Your zone | Zone-signing public key | Signs every other record set |
RRSIG |
Your zone | Signature over one record set, with validity dates | That record set is authentic |
DS |
Parent zone (for example .com) |
Hash of your KSK | The parent vouches for your key |
NSEC / NSEC3
|
Your zone | Signed proof of what does not exist | A "no such name" answer is genuine |
The resolver walks it top-down: root key, then the .com DS and DNSKEY, then your DS in .com, then your DNSKEY, then the RRSIG on the answer.
Step 1: Is the domain signed at all?
Ask for the DS record. It lives in the parent, so it answers "has the owner told the registry about a key?"
curl -s 'https://dns.google/resolve?name=cloudflare.com&type=DS'
{"Status":0,"AD":true,
"Answer":[{"name":"cloudflare.com.","type":43,"TTL":358,
"data":"2371 13 2 32996839A6D808AFE3EB4A795A0E6A7A39A76FC52FF228B22B76F6D63826F2B9"}]}
Read the data left to right: key tag 2371, algorithm 13 (ECDSA P-256 with SHA-256), digest type 2 (SHA-256), then the digest. "AD":true means the resolver validated this answer.
Now my own domain:
{"Status":0,"AD":false,
"Authority":[{"name":"com.","type":6,"data":"a.gtld-servers.net. nstld.verisign-grs.com. ..."}]}
No DS, AD false, and only the .com SOA in the authority section. PixelValut is not signed today, and neither are my other two domains. That is the default for most domains, and I cover below why I have not flipped the switch blindly.
Step 2: Fetch the keys
curl -s 'https://dns.google/resolve?name=cloudflare.com&type=DNSKEY'
256 3 13 oJMRESz5E4gYzS/q6XDrvU1qMPYIjCWzJaOau8XNEZeqCYKD5ar0IRd8KqXXFJkqmVfRvMGPmM1x8fGAa2XhSA==
257 3 13 mdsswUyr3DPW132mOi8V9xESWE8jTo0dxCjjnopKl+GqJxpVXckHAeF+KkxLbxILfDLUT0rAK9iUzy1L53eKGQ==
Two keys: flag 256 is the zone-signing key, 257 is the key-signing key. Protocol is always 3. Algorithm 13 again.
Step 3: Verify the DS matches the key yourself
This is the check registrars and DNS hosts get wrong during migrations. The DS digest is SHA-256 over the owner name in DNS wire format followed by the DNSKEY record data. Twenty lines of Python do it:
import base64, hashlib, struct
def wire(name):
out = b''
for label in name.rstrip('.').split('.'):
out += bytes([len(label)]) + label.lower().encode()
return out + b'\x00'
def keytag(rdata):
ac = 0
for i, b in enumerate(rdata):
ac += b if i & 1 else b << 8
ac += (ac >> 16) & 0xFFFF
return ac & 0xFFFF
name = 'cloudflare.com'
for flags, key in [(256, 'oJMRESz5...'), (257, 'mdsswUyr...')]: # full keys from step 2
rdata = struct.pack('!HBB', flags, 3, 13) + base64.b64decode(key)
print(flags, 'keytag', keytag(rdata),
'ds-sha256', hashlib.sha256(wire(name) + rdata).hexdigest().upper())
256 keytag 34505 ds-sha256 CDBA2D58AAED7206DB27D0CDE48F279A1F8354C4AE9B443A8B42FC81F4A8A265
257 keytag 2371 ds-sha256 32996839A6D808AFE3EB4A795A0E6A7A39A76FC52FF228B22B76F6D63826F2B9
The KSK's key tag is 2371 and its computed digest matches the DS in .com exactly. The chain from .com to cloudflare.com holds.
Step 4: Read a signature and its expiry
Add do=1 to get the RRSIG alongside the answer:
curl -s 'https://dns.google/resolve?name=cloudflare.com&type=A&do=1'
104.16.133.229
104.16.132.229
a 13 2 300 1793715903 1793535903 34505 cloudflare.com. itGllCYEe/mUZ3CTef...
| Field | Value | Meaning |
|---|---|---|
| Type covered | a |
Signs the A record set |
| Algorithm | 13 |
ECDSA P-256 / SHA-256 |
| Labels | 2 |
cloudflare + com
|
| Original TTL | 300 |
Five minutes |
| Expiration | 1793715903 |
Nov 3, 2026 14:25:03 UTC |
| Inception | 1793535903 |
Nov 1, 2026 12:25:03 UTC |
| Key tag | 34505 |
The ZSK from step 2 |
The signature is valid for only about 50 hours. Cloudflare signs on the fly, so that is fine. On a zone you sign yourself, an expiration date that passes without re-signing is one of the most common real outages: everything resolves on Monday and nothing resolves for validating users on Tuesday. Convert the timestamps with date -u -d @1793715903 or any Unix timestamp converter and alert well before expiry.
Step 5: See what failure looks like
dnssec-failed.org is deliberately broken for testing:
{"Status":2,"AD":false,
"Comment":"DNSSEC validation failure. ...",
"extended_dns_errors":[{"info_code":9,"extra_text":"No DNSKEY matches DS RRs of dnssec-failed.org"}]}
Status 2 is SERVFAIL. Extended DNS Error 9 is "DNSKEY Missing": the parent's DS points at a key the zone does not publish. Repeat the query with cd=1 (checking disabled) and the record comes back:
{"Status":0,"AD":false,"CD":true,"Answer":[{"name":"dnssec-failed.org.","type":1,"data":"96.99.227.255"}]}
That pair, SERVFAIL normally but an answer with cd=1, is the signature of a DNSSEC problem, not a server outage. It is the first test I run when a client says "the site is down for some users."
Step 6: Proof of non-existence
Ask for a name that does not exist:
Status: 0 (NOERROR), AD: true
nonexistent-zz9.cloudflare.com. NSEC \000.nonexistent-zz9.cloudflare.com. RRSIG NSEC TYPE128
Cloudflare returns NOERROR with a signed NSEC record that claims the name exists but has no A record. This "compact denial" avoids revealing the zone's real names. Classic NSEC lists neighboring real names, which allows zone walking. NSEC3 hashes the names to make that harder. If you sign your own zone, pick NSEC3 or compact denial.
Threat scenarios
| Scenario | Without DNSSEC | With DNSSEC (validating resolver) |
|---|---|---|
| Cache poisoning of a resolver | Forged A record cached and served | Signature check fails, answer rejected |
| On-path attacker rewrites DNS on public Wi-Fi | Victim gets attacker's IP | Validating stub or resolver rejects it |
| Registrar account takeover, NS changed | Attacker controls the zone | Attacker can also replace DS; DNSSEC does not help |
| Typo-squat domain | Works normally | Works normally; it is a different, validly signed domain |
| Signatures expire or DS mismatches after migration | Not applicable | Whole domain fails for validating users |
The last row is why I have not enabled DNSSEC on my own domains in a hurry. The risk is operational, not cryptographic. My DNS migration audit guide covers the record inventory, and DNSSEC adds one hard rule: remove the DS at the registrar and wait out its TTL before moving to a DNS host that will not sign with the same keys.
Browser-side verification workflow
- Check DS and DNSKEY with the PixelValut DNS Lookup tool, which queries public resolvers from your browser.
- Query the same name through DoH with
do=1and confirmADis true. - Compare the DS key tag with the KSK tag, or recompute the digest with the script above.
- Convert RRSIG expiration times and confirm they are days away, not hours, on self-signed zones.
- Retest with
cd=1whenever you see SERVFAIL. - For deeper analysis, DNSViz draws the whole chain graphically.
DNSSEC pairs well with the CAA records and CT monitoring setup: CAA is only as trustworthy as the DNS answer the CA receives, and signed DNS makes that answer harder to forge.
Rollout checklist
- [ ] Confirm your DNS host supports DNSSEC signing and automatic re-signing
- [ ] Choose algorithm 13 (ECDSA P-256) unless your registrar cannot accept it
- [ ] Enable signing, then publish the DS at the registrar exactly as the host provides
- [ ] Verify
AD: truefrom two public resolvers - [ ] Recompute or compare the DS key tag against the KSK
- [ ] Set an alert on RRSIG expiry for self-managed zones
- [ ] Document the "remove DS first" step in your migration runbook
- [ ] Re-check after any registrar or DNS provider change
Frequently asked questions
Does DNSSEC encrypt my DNS traffic?
No. DNSSEC signs answers so they can be verified. It does not hide them. For privacy you need DNS over HTTPS or DNS over TLS between the client and the resolver.
How do I know if my domain uses DNSSEC?
Query the DS record for your domain. If the answer section is empty, the domain is unsigned. If a DS exists and a validating resolver returns AD: true, the chain is working.
What is the difference between a KSK and a ZSK?
The key-signing key (flag 257) signs only the DNSKEY set and is the one referenced by the parent's DS. The zone-signing key (flag 256) signs all other records. Splitting them lets you rotate the ZSK without touching the registrar.
Why does my site fail only for some users after enabling DNSSEC?
Only validating resolvers enforce DNSSEC. Users on Google Public DNS, Cloudflare 1.1.1.1 or many ISP resolvers will get SERVFAIL if the chain is broken, while others still resolve. Test with and without cd=1.
Can DNSSEC stop phishing domains?
No. A look-alike domain can be signed perfectly. DNSSEC proves an answer belongs to the domain you asked for, not that the domain is the one you meant.
What happens if I move DNS hosts with DNSSEC enabled?
If the old DS stays at the registrar and the new host signs with different keys or does not sign, validation fails. Remove the DS, wait for its TTL to expire, migrate, then re-enable signing and publish the new DS.
Follow & Connect
- YouTube: PixelValut (@pixel-valut)
- TikTok: PixelValut (@pixelvalut007)
- GitHub: Muhammad Ali Mughal (@ali111887)
- LinkedIn: Muhammad Ali Mughal
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.