DKIM: signing, selectors, and the failures you will actually see
You forwarded a message from your own domain and the copy that arrived carries dkim=fail. Or a customer replied to say your invoice landed in spam, and the raw headers show body hash did not verify. The message is genuine, you sent it, and the receiving server is telling the reader it might be a forgery. Both are DKIM failures seen from the outside, and each wording in that header line points at a different cause with a different fix.
Read the Authentication-Results line first
Every receiving mail server that checks DKIM writes its verdict into a header called Authentication-Results. Open the raw message (most clients call it “show original” or “view source”) and find the line. A healthy one looks like this:
Authentication-Results: mx.example.net;
dkim=pass [email protected] header.s=s1 header.b=Kq3xY9Wz
header.i is the identity the signature claims, header.s is the selector, and header.b is the first few characters of the signature so you can match it to the right DKIM-Signature header when a message carries several. The word after dkim= is the verdict. The text in brackets after a failing verdict is the reason.
The DKIM-Signature header itself sits higher up in the same raw view:
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com; s=s1;
h=from:to:subject:date:message-id;
bh=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN/XKdLCPjaYaY=;
b=dGhpcyBpcyBub3QgYSByZWFsIHNpZ25hdHVyZQ==
d= is the signing domain, s= the selector, h= the list of headers that were signed, bh= a hash of the body at signing time, and b= the signature over those headers and that body hash. Every failure below is one of those tags disagreeing with the message that arrived.
“body hash did not verify”
This is the failure you see most on forwarded mail. The sender hashed the body, put the hash in bh=, and signed it. Somewhere between them and the recipient, something changed the body. The verifier recomputed the hash, got a different value, and reported:
dkim=fail (body hash did not verify) [email protected] header.s=s1
Anything that rewrites the body triggers it: a mailing list appending an unsubscribe footer, a corporate gateway inserting “CAUTION: external sender” at the top, a forwarding rule that converts the message to plain text, or a security appliance rewriting every link to route through its own scanner. The signature was valid when the message left you, and the receiver is right to say it no longer matches.
This failure doesn’t mean your key or your DNS is wrong. Ask the recipient what sits in front of their mailbox before you touch your own setup. If your own messages fail this way when sent directly, with no forwarding involved, look for something in your outbound path (a disclaimer plugin, a link-tracking rewriter) that runs after the signing step instead of before it.
“signature did not verify”
Here the body hash matched but the signature over the headers did not:
dkim=fail (signature did not verify) [email protected] header.s=s1
The cause is a signed header that changed after signing. The usual suspect is Subject, because list software loves to prepend [listname] to it. Some gateways rewrite From and To. Date and Message-ID are stable in practice, so a signature that covers only stable headers survives more hops.
The other common cause is the canonicalisation setting in c=. simple means the verifier requires the header bytes exactly as signed, down to whitespace and line folding; relaxed tolerates whitespace changes. Almost every intermediate server refolds long header lines. If your signer uses c=simple/simple, change it to c=relaxed/relaxed and a large share of these failures stop.
“no key for signature” and its cousins
The verifier read d= and s=, built the DNS name s1._domainkey.example.com, asked for a TXT record, and got nothing useful back. The wording varies by verifier:
dkim=temperror (no key for signature)
dkim=permerror (bad key)
dkim=neutral (no key)
temperror means the DNS lookup timed out or returned SERVFAIL; try again and check your nameservers. permerror means the record exists and is malformed: a p= value with a line break inside it, a stray space, quotes split in the wrong place when a long record went into a DNS control panel, or a key pasted with its PEM armour lines still attached. neutral (no key), or a plain “key not found”, means the name resolved to nothing at all.
That last one has an ordinary cause: the selector in the signature doesn’t match the selector where the key lives. Usually your mail provider rotated to a new selector and nobody published the new record. Sometimes the record sits at s1._domainkey on the parent domain while mail goes out from a subdomain, and now and then someone deleted a TXT record they didn’t recognise while tidying the zone.
So what is a DKIM selector?
A selector is the label that tells a verifier which key to fetch. One domain can publish many keys, one per sender or per rotation period, and the selector picks between them. The key lives at <selector>._domainkey.<domain> as a TXT record:
s1._domainkey.example.com. 3600 IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."
Selectors have no standard names. Each provider follows its own convention, and you will see labels such as selector1, selector2, google, default, k1, s1, mail, dkim and dated strings such as 20260901. Nothing in DNS lets anyone list the selectors under a domain; you can only ask for a name you already know. That limits how any scanner, including this one, can measure DKIM, and it’s why finding your own selector sometimes takes a minute of header reading. If you don’t know yours, finding your DKIM selector walks through pulling it out of a sent message.
Why forwarded mail looks forged even when DKIM passes
A DKIM pass on its own doesn’t settle a spoofing verdict. The receiver also runs DMARC, and DMARC asks whether the signing domain in d= aligns with the domain in the visible From: header. When a message is forwarded through a list that rewrites From: to its own address, the original signature may still verify, and the receiver still treats the message as unauthenticated for your domain because d= and From: no longer match.
The reverse also happens. A message forwarded by a plain mailbox rule keeps your From:, the body hash breaks because of a footer, and SPF fails too because the forwarding server is now the connecting IP. With DKIM broken and SPF failing, DMARC has nothing to pass on, and a domain with p=reject watches its own genuine mail bounce at the final hop. So the forwarded copy looks like a forgery, and since SPF can’t survive a forward, DKIM has to. What your policy does in that case is covered on the DMARC page, and the reason SPF breaks on every forward is on the SPF page.
How the scan reads your DKIM, and what it cannot see
The scan looks up your MX records, works out which mail provider they point at, and asks for the selectors that provider is known to publish. When the provider is one it does not recognise, it probes eight common selector names as a guess. If none of those names answers, the result is recorded as not determinable, because a key under a selector nobody guessed is invisible to everyone, and the scan won’t call that absent.
In the September 2026 census the DKIM check ran against 75,823,110 domains where a recognised provider or a guess gave it somewhere to look. Of those, 37,999,462 (50.1%) had a key at a probed selector and 37,823,648 (49.9%) had none observed there. A further 240,777,792 domains fell outside the check, either because they publish no MX record or because nothing answered at any name the scan can try. The 49.9% counts domains with no key where the scan looked, and that is a narrower claim than having no DKIM at all. Some of those domains sign under a selector the scan does not probe. Many more sit on providers where signing stays off until an administrator switches it on, and for them the figure is exactly right.
If the scan reports no key at the selectors it probed and you know your provider signs your mail, confirm the selector from a real sent message and compare it with the record your DNS publishes. If the scan reports no key and you’ve never set DKIM up, the section below covers everything you need to do.
Turning signing on and publishing the key
The signer on your outbound server and the record in your DNS have to match before a message will pass.
At the sender, find the DKIM setting in your mail provider’s admin console. Hosted providers generate the keypair for you and show either a TXT value to paste or a CNAME target to point at. Use 2048-bit RSA if offered; the DKIM RFCs have recommended that length since 2018. If you run your own mail server, generate the key with your signing milter’s tooling, set c=relaxed/relaxed, and sign at minimum From, To, Subject, Date and Message-ID.
In DNS, create the TXT record at <selector>._domainkey.yourdomain. Some DNS panels append the zone name automatically, so entering s1._domainkey.example.com as the host produces s1._domainkey.example.com.example.com; enter only s1._domainkey if the panel adds the rest. A 2048-bit key also produces a TXT value longer than 255 characters, which must be split into two quoted strings inside one record. Most panels do this for you. Some silently truncate instead, and the result is permerror (bad key).
Then test with a real message. Send one to a mailbox you control on a different provider, open the raw headers, and look for dkim=pass with header.d= set to your domain and header.s= set to the selector you just published. If you see pass on that message and fail on a forwarded copy of the same message, you’re done; the forwarding failure belongs to whoever runs the forwarder.
Rotate the key at least yearly. Publish the new selector, switch the signer to it, wait long enough for the last message signed with the old key to be delivered, and only then remove the old record. Keep both records live during the switch, otherwise you’ll cause the “no key for signature” failure yourself.
Run the scan again once the key is public and the signer is on. It will show the key at your provider’s selector alongside the state of your SPF and DMARC records.
Figures as of 5 September 2026, from the September 2026 edition of the defaults.exposed census. Census numbers move every month; the current values are on the census data page.