Defaults.Exposed

DKIM signature not valid

“DKIM signature not valid” means the receiver found your public key, ran the verification, and the maths didn’t come out. That’s a different verdict from “no key for signature”, where the DNS lookup failed, and from “body hash did not verify”, where the body changed after signing. If you’ve checked your DNS twice and the key is right there, this verdict is telling you the key was read without trouble. The fault is in the message, in the bytes of the key itself, or in which key the signer is using.

What the verdict rules out

A DKIM-signed message carries a DKIM-Signature header with four tags that matter here: d= names the signing domain, s= names the selector, bh= is a hash of the body, and b= is the signature over that body hash plus a chosen set of headers. The receiver builds the DNS name <selector>._domainkey.<domain>, fetches the TXT record there, recomputes both hashes and checks b= against the public key. The whole procedure is RFC 6376.

Three things can go wrong in that chain, and receivers word each of them differently. If the lookup returns nothing, the key can’t be found, so the selector is wrong or missing. If the body hash the receiver computes doesn’t match bh=, something rewrote the body. If both of those succeed and the signature still doesn’t check out, you get the plain verdict this page is about.

The wording narrows the search before you’ve touched anything. A plain “signature not valid” points at the key material or the signed headers, and clears the selector.

Read the Authentication-Results line first

Open the full headers of a failing message and find Authentication-Results. The receiver writes its DKIM verdict there, and the bracketed reason is the part to read. dkim=fail (no key for signature) means the lookup at <selector>._domainkey.<domain> found no usable record, and finding the selector your signer uses is the fix.

dkim=fail (body hash did not verify) means the body changed in transit. Microsoft writes that reason as a fail, and Gmail often writes the same diagnosis as dkim=neutral (body hash did not verify). Both wordings mean the body changed, and the gateway or list that changed it is covered under DKIM.

A dkim=fail with no body-hash reason, or with a reason along the lines of “signature did not verify”, is the case for the rest of this page. Work through the five causes in the order below. The order runs from most common to least, and the first one isn’t in your DNS at all.

Cause 1: something modified the message after it was signed

A security gateway appends a disclaimer. A compliance system stamps a footer on outbound mail after the signer has already run. A mailing list adds “[listname]” to the subject line. Each of those changes either the body or a signed header, the hashes no longer match, and the signature fails even though your DNS is perfect.

The tell is the pattern of failures. Direct mail to a test mailbox passes, while mail that went through the gateway, the list or an archiving hop fails. If that’s what you see, move the signing step after the modification or stop modifying, and look again at the reason string on a message that took the long route, because it will usually name the body hash once you’re reading the right message.

Cause 2: the published key isn’t the key you generated

A 2048-bit RSA public key is longer than the 255-character limit of a single TXT string, so it has to be published as multiple quoted strings that receivers concatenate. DNS control panels handle that badly. Some truncate the paste at 255 characters and say nothing. Some split the string in the wrong place. Some inject whitespace, a stray quote or a line break into the p= value, so the record looks fine at a glance while the key inside it is rubbish.

Look the record up yourself and compare it with the key the signer generated, character for character:

dig TXT selector._domainkey.example.com

Put the p= value from the answer next to the public key from your signer’s console. If the published one is shorter, has a quote or a space in the middle, or differs at any character, re-publish it, and this time check what the control panel saved rather than what you pasted. Every receiver fails the same way on a mangled key, which is the other tell for this cause: there’s no gateway pattern, all mail fails.

The free scan sits beside that lookup rather than replacing it. It reports your SPF record, your DMARC policy and what it observes at the DKIM selectors it probes, which tells you whether SPF and DMARC are holding the line while the signature is broken. It can’t see a selector it doesn’t probe, so a blank DKIM line there doesn’t mean you aren’t signing, and the dig output above stays the test for your key.

That blind spot is visible in the census itself. Of the 75,823,110 domains where the September 2026 census could run its DKIM check, 37,999,462 (50.1%) answered at a probed selector, and the check recorded no result for 240,777,792 others.

Cause 3: the signer moved to a new key and DNS didn’t follow

After a key rotation, a provider migration or an ESP reconnect, the signer holds a new private key while DNS still serves the old public key. Each signature is then checked against a public key that no longer matches, and all of them fail.

The tell is timing: failures start on the day of the change and affect all mail from that signer. Re-copy the current record from the provider console, rather than from your own notes or a ticket from last year, and publish it under the selector the signer now uses. Never delete an old selector until traffic signed with it has drained; a queue can hold messages for days, and removing the old record early fails them on delivery.

Causes 4 and 5: the selector, then the signing settings

If the reason string was “no key for signature”, the receiver has already told you which cause you have. Confirm the s= tag in the failing message’s DKIM-Signature header, query <selector>._domainkey.<domain> with that exact selector as TXT, and publish the record the signer expects at that exact name. A record published under the wrong domain, or under _domainkey with a typo, produces the same lookup failure, and so does a selector the signer rotated away from last month.

The fifth cause lives in the signer’s settings rather than in DNS. Canonicalisation decides how much reformatting a signature tolerates. simple fails on changes a human can’t see: a re-wrapped line, a re-folded header, a trailing space added by a relay. Set c=relaxed/relaxed in the signer, which tolerates the whitespace re-wrapping and header re-folding that intermediate servers do routinely.

Do not use the l= body-length tag. It was meant to let footers through, and it does something worse: it lets anyone append content to a signed message without breaking the signature. It still doesn’t survive most gateway modifications either, so it buys risk and no reliability. Leave it out and let relaxed canonicalisation carry the load.

A valid signature that still fails DMARC

Once the signature verifies, check alignment. DMARC only counts a DKIM pass when the d= domain aligns with the domain in the From header. A signature that verifies as d=yourcompany.gappssmtp.com or d=yourtenant.onmicrosoft.com passes DKIM and still fails DMARC, because the signing domain belongs to the provider. That’s the usual ending for a domain whose owner switched on signing at the provider without adding their own domain, and custom-domain signing puts it right; the walkthrough is under gappssmtp signing.

Key size matters as well: use 2048-bit, not 1024-bit. 1024-bit keys sit below current cryptographic recommendations and some receivers score them down. The only cost of 2048 is the TXT splitting problem in cause 2, and CNAME delegation removes it.

Nothing rejects your mail for a broken signature on its own. Receivers treat a broken signature the way they treat a missing one, and a missing one is the ordinary state of a low-graded domain: of the 234,955,475 domains graded F in the September 2026 census, 96.9% fail the dkim-exists check. The damage lands through DMARC. When SPF doesn’t pass with alignment either, the message fails DMARC and whatever policy you published applies to it.

Turning DKIM off is the wrong escape. Since the 2024 bulk-sender rules, Gmail and Yahoo expect DKIM from volume senders, and an enforcing DMARC policy needs DKIM in practice, because SPF breaks under forwarding and DKIM is the leg that survives it.

Retire the whole class with CNAME delegation

Where your provider offers CNAME delegation, use it. Instead of pasting a public key into a TXT record, you publish a CNAME at <selector>._domainkey.<domain> pointing at a name the provider controls, and the provider serves the key from their side. The 255-character split stops being your problem, because you don’t paste the key at all. Rotation stops being your problem, because the provider changes the key at the target name and your CNAME keeps pointing at it. Causes 2 and 3 go away in one change.

Set it up once, confirm the signature verifies on a direct test message, then read the header on a message that took the gateway route. If both pass and d= matches your From domain, the signature is doing its job.

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.