Docs / Fixes
DKIM fail / 'body hash did not verify' — how to fix it
dkim=fail has two distinct causes. 'Body hash did not verify' means the body was modified after signing (a gateway, list or appended footer) — sign with relaxed/relaxed canonicalization and stop post-signing changes. 'No key for signature' or a plain signature failure means the public key is missing, the selector is wrong, or the published key doesn't match the signing key — confirm the selector and republish the correct public key. Then check d= aligns with the From domain for DMARC.
A dkim=fail result is two different problems wearing the same label, and treating them as one is why people go in circles. The first question to answer is whether the body hash failed or whether the key couldn't be found or matched — the fixes have nothing to do with each other. One is a content-integrity problem (something changed the message); the other is a DNS-and-keys problem (the receiver can’t find or match your key). Decide which you have before you touch anything, because the wrong fix wastes hours.
How the body hash works
When your server signs a message, it normalises the body with a chosen canonicalization method, computes a
cryptographic hash of that normalised body (SHA-256), and stores the result in the bh= tag of the
DKIM-Signature header. The receiver does the same thing on arrival — normalise the body it received,
hash it, and compare to your bh=. If the two match, the body is intact; if they differ by even one
byte, you get body hash did not verify.
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com;
s=selector1; bh=47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=; b=G9k0...
The bh= tag is the body fingerprint; b= is the signature over the headers and that
fingerprint; c= is the canonicalization for header and body; d= and s= tell
the receiver which key to fetch. A body hash failure is specifically a bh= mismatch — the
headers and key were fine, but the body that arrived isn’t the body you signed. That single fact narrows the
investigation enormously: you’re looking for whatever changed the body, not for a key problem.
Body hash did not verify
DKIM signs a hash of the body. If the body that arrives differs at all from what was signed, the hash won't match and you get body hash did not verify. The cause is something modifying the message after your server signed it:
- A mailing list adding a footer or subject tag
- A security gateway or appliance rewriting content or appending a disclaimer
- An anti-virus/footer system on the path
Sign with relaxed/relaxed canonicalization so cosmetic whitespace and header-case changes don't break the signature, and stop anything in your own infrastructure from altering signed mail. For forwarders and lists that must modify messages, the right tool is ARC, which preserves the original authentication result through the change (more below).
A telltale pattern points straight at in-transit modification: the same mail passes DKIM at Gmail and Yahoo but fails at Outlook/Hotmail. That isn’t a key problem — the key works fine for the receivers that pass — it’s that one provider’s path is handling the message (often attachments or embedded images) in a way that alters the body after signing. The usual modifiers to hunt for are appended footers and disclaimers, tracking pixels injected by a gateway, URL-rewriting or “safe links” tools, antivirus/DLP appliances, and character-set or line-ending re-encoding. Any one of them rewrites a byte and breaks the hash.
Canonicalization: simple vs relaxed
Canonicalization decides how forgiving the hash is about cosmetic changes, and it’s set per message in the
c= tag as header/body. Simple is strict: it tolerates almost nothing, so
a single added newline, a trailing space, or a mail server “helpfully” reflowing a line breaks the body hash.
Relaxed normalises insignificant whitespace and header case before hashing, so the harmless
reformatting that happens routinely along a mail path no longer counts as tampering.
The practical recommendation is relaxed/relaxed for both header and body. It won’t rescue you
from a genuinely altered body — an appended footer still changes real content — but it eliminates the large class
of failures caused purely by cosmetic reformatting, which is why simple canonicalization produces so many
needless bh mismatches. Avoid the l= body-length tag as a workaround, too: it lets a
receiver hash only the first N bytes so trailing additions don’t break the signature, but it also lets an attacker
append content below your signed body, which is a real security hole. Fix the modification, don’t mask it with
l=.
The order-of-operations fix: sign last
When the modification is happening in your own infrastructure, the cause is almost always order of operations: something edits the body after the DKIM signature is applied. A footer-appender, a compliance disclaimer system, a content filter or a downstream relay runs after signing, and every byte it adds invalidates the hash you just computed. The fix is to make DKIM signing the very last step before the message leaves your control — sign after every footer, disclaimer and content transformation, not before.
In a PowerMTA or KumoMTA setup you control where signing happens, so the rule is simple: nothing touches the body downstream of the signing stage. If an upstream application or a middlebox must add content, let it do so first and sign afterward; if a downstream relay insists on rewriting, move the signing to that relay or stop it rewriting. Get the order right and a whole category of body-hash failures disappears at the source.
Find what’s modifying the body
If signing is already last in your own pipeline and the body hash still fails, something on the path is changing the message, and the way to find it is to compare the body you sent with the body that arrived. Send a test to a mailbox you control at the failing provider, open the raw received source, and diff it against the raw message you sent. The difference — an added footer line, a rewritten link, a re-encoded character, an extra blank line — is your culprit, and where it appears tells you which hop introduced it.
Work the suspects in order. First your own egress: a footer or disclaimer appliance, a content filter, a relay you
route through. Then the receiving side and any path middleboxes: corporate gateways, security scanners and
“safe-links” rewriters are common offenders, which is why a message can pass everywhere except behind one
company’s filter. Your DMARC aggregate reports help here too: they show
DKIM results across receivers, so a pattern of bh failures clustered at particular destinations points
at modification on those routes rather than a problem with your signing.
When the body legitimately must change: ARC
Some modification is unavoidable and not your fault. A mailing list that appends an unsubscribe footer or tags the subject, or a forwarder that rewrites content, genuinely changes the body — so the original DKIM signature should break, by design. You cannot fix that from the sending side, because the change happens after your message has left you, and it isn’t a flaw in your signing.
The protocol answer is ARC (Authenticated Received Chain, RFC 8617). An intermediary that modifies mail can add an ARC seal recording that the message authenticated correctly when it arrived, so the final receiver can see “this was legitimate before the list touched it” even though DKIM now fails. ARC has to be implemented by the forwarder, not the original sender — so if your own mail breaks only when it passes through a list or relay you operate, enabling ARC sealing there is the fix; if it’s a third party’s list, that’s their responsibility, and you simply shouldn’t read those downstream failures as a problem with your domain.
No key, wrong key, or mismatch
The other branch is about the key itself. Receivers read the selector from the DKIM-Signature header and look it up at selector._domainkey.yourdomain. It fails when:
- The public key was never published, or DNS hasn't propagated
- The selector in the header doesn't match the one in DNS
- The published public key doesn't match the private key that's signing
- The key is revoked (empty
p=) or a long TXT record was split incorrectly
Confirm the published key with the DKIM checker, and if you suspect a mismatch, derive the public key from your signing private key with the DKIM key converter and compare. Republish the correct value at the matching selector.
Two practical traps live here. The first is the split TXT record: a 2048-bit public key is longer than a single DNS string can hold, so it must be published as multiple quoted chunks that the DNS server concatenates. Get the chunking wrong — a stray space, a missing quote — and the assembled key is corrupt even though each piece looks right, producing a signature that won’t verify. The second is propagation and rotation: a freshly published or rotated selector needs time to propagate, and you must keep the old selector live until no in-flight mail still references it. Judging a key change in the first few minutes is how people conclude “it’s broken” when it simply hasn’t propagated yet.
Don't forget DMARC alignment
A valid DKIM signature still won't satisfy DMARC unless the signing d= domain aligns with the From-header domain. Relaxed alignment accepts a subdomain; strict requires an exact match. If DKIM passes but DMARC still fails, alignment is usually why.
Verify
Send a test to a seed inbox, re-read the Authentication-Results, and confirm dkim=pass with the body hash verifying and d= aligned. If you rotated or republished a key, allow for DNS propagation before judging the result. Because the two branches fail differently, test for both: that the signature verifies (key correct) and that the body hash matches (nothing altered the body on the way).
Why a body-hash fail hurts more than it looks
It’s tempting to shrug off a DKIM failure when SPF still passes, but the two don’t cover the same situations. SPF authorises the connecting IP, so it breaks the moment mail is forwarded — the forwarding server’s IP isn’t in your SPF record. DKIM, by contrast, travels with the message, which is exactly why it’s the leg that can carry DMARC through a forward. So a body-hash failure tends to bite hardest in precisely the case where DKIM was your only remaining authentication: forwarded and relayed mail.
That’s also why “it passes at most providers” can be falsely reassuring. The receivers where it fails are often the ones routing your mail through an extra hop, and those are the hardest deliveries to win back once reputation dips. Treat a body-hash failure as a real signal even when overall delivery looks fine, because it’s quietly removing your safety net on the messages most likely to need it.
Mistakes that send people in circles
The biggest time-waster is not splitting the two failure types first — regenerating keys for hours when the real
problem was a footer being appended after signing, or hunting for a content modifier when the public key simply
wasn’t published. Read the reason in parentheses after dkim=fail before you do anything; “body hash
did not verify” and “no key for signature” send you down opposite paths.
Two more recur often. Assuming it’s the receiver’s fault when mail fails at one provider — the modification is real and usually on that path, so investigate rather than dismiss it. And testing only one sending service: if you send through several systems, each signs (or doesn’t) independently, so a pass from one proves nothing about the others — test each. Finally, don’t treat a passing signature as permission to send anything: DKIM proves the body wasn’t altered and came from a domain holding the key, but it does nothing for consent or list quality, which is what actually keeps you out of spam once authentication is in order.
A note on key length and algorithm
While you’re in the keys, two choices are worth getting right. Key length: 2048-bit RSA is the
current baseline — 1024-bit is widely considered too weak and some receivers discount it — and 2048-bit is what
forces the multi-string TXT record that trips people up, so publish it carefully. Algorithm:
rsa-sha256 is universal and the safe default; ed25519 is smaller and modern but not yet
honoured everywhere, so if you use it, publish an RSA key alongside it as a fallback rather than relying on Ed25519
alone. Neither choice causes a body-hash failure by itself, but a too-short or unsupported key is a quiet way to
weaken signatures that otherwise verify, and it’s cheap to get right while you’re already editing DNS.
The bottom line
dkim=fail is two problems with one label, so always split it first. A body hash
failure means the body changed after signing — sign with relaxed/relaxed, make signing the last step so nothing in
your pipeline edits the body afterward, find any path middlebox that’s rewriting content, and use ARC where a
list or forwarder legitimately must modify the message. A key failure means the receiver can’t
find or match your key — confirm the selector, republish the correct public key, watch for a mis-split TXT record,
and allow for propagation.
Whichever branch you’re on, finish by confirming d= aligns with your From domain, since a perfectly
valid signature still won’t satisfy DMARC if it’s on the wrong domain. Get signing right once and it stays right;
the failures almost always trace to a body modified after the fact or a key that doesn’t match what’s published.
If you’d rather have DKIM, SPF and DMARC set up and aligned correctly from the start, our
DMARC deployment service does exactly that, and the
Auto PMTA Configurator signs with sane canonicalization and publishes matching keys when it
builds your stack.
Frequently asked questions
What does 'body hash did not verify' mean? +
DKIM signs a hash of the message body; 'body hash did not verify' (a bh mismatch) means the body that arrived doesn't match what was signed — so something modified it in transit. The usual culprits are a mail gateway, a mailing list, or a footer/disclaimer appended after signing. Signing with relaxed/relaxed canonicalization tolerates minor reformatting, but anything that genuinely changes the body will break the signature.
Why do I get 'no key for signature'? +
The receiver looked up the selector from the DKIM-Signature header at selector._domainkey.yourdomain and found nothing. Either the public key was never published, the selector in the header doesn't match the one in DNS, or the record hasn't propagated yet. Confirm the exact selector and that the TXT record exists and resolves.
DKIM still fails even though I published the key — why? +
A few common reasons: the published public key doesn't match the private key that's actually signing; the selector is wrong; the key was revoked (an empty p= value); or a long TXT record was split incorrectly in DNS so the value is corrupted. Derive the public key from your signing key and compare it byte-for-byte with what's published.
Does forwarding break DKIM? +
Forwarding that modifies the message — a mailing list adding a subject tag or footer, some gateways — breaks the body hash and so breaks DKIM. That's expected. ARC (Authenticated Received Chain) exists precisely so a forwarder can preserve and vouch for the original authentication result through the modification.
Which canonicalization should I use? +
relaxed/relaxed is the most forgiving and the usual recommendation: it tolerates harmless whitespace and header-case changes that strict canonicalization would treat as tampering. It won't save you from a genuinely altered body, but it prevents needless failures from cosmetic reformatting along the path.
It passes DKIM at Gmail but fails at Outlook — why just one provider? +
Because the failing provider’s path is modifying the body and the others aren’t. If the key were wrong it would fail everywhere; failing at only one receiver points to in-transit modification on that route — commonly how attachments or embedded images are handled, or a gateway on that path. Compare the raw received body at the failing provider against what you sent to see exactly what changed.
Should I use the l= (body length) tag to stop footers breaking DKIM? +
No. The l= tag tells receivers to hash only the first N bytes of the body, so trailing additions don’t break the signature — but it also lets an attacker append arbitrary content below your signed body and still pass DKIM. It trades a deliverability annoyance for a real security hole. Fix the thing modifying the body, or sign after it, rather than reaching for l=.
I sign the message but a footer is added afterward — how do I fix it? +
Reorder so DKIM signing is the last step. Whatever adds the footer (an appliance, a compliance tool, a downstream relay) must run before signing, not after, so the body is final when the hash is computed. If you can’t move it earlier, sign at that later stage instead. The principle is one line: nothing changes the body after it’s signed.
Related