DKIM Fail: Body Hash Did Not Verify, Signature Failed and Other Causes
DKIM fails when the message changed after signing (body hash did not verify), the signed headers changed, or the receiver can't find a matching public key.
On this page
DKIM fails when the receiver can't verify the signature on a message. The three usual reasons: the message body was changed after it was signed ("body hash did not verify"), a signed header was changed, or the receiver couldn't fetch a public key that matches the signature. A body hash failure almost always means something between your sending system and the recipient (a gateway, a disclaimer rule, a mailing list) altered the message.
You'll see the result in the Authentication-Results header of the delivered message. Microsoft documents it like this:
dkim=fail (body hash did not verify) header.d=contoso.com
Gmail commonly reports the same problem as neutral rather than fail:
dkim=neutral (body hash did not verify) header.i=@example.com header.s=s1
Either way, the signature didn't verify, and it won't count toward DMARC.
How DKIM verification works
When a message is signed, the sending server does two things:
- It hashes the message body and puts that hash in the
bh=tag. - It hashes the headers listed in
h=, plus the DKIM-Signature header itself (includingbh=), and signs that with its private key. The result goes inb=.
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com; s=s1;
h=from:to:subject:date:message-id; bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;
b=AuUoFEfDxTDkHlLXSZEpZj79LICEps6eda7W3deTVFOk4yAUoqOB4nujc7YopdG5...
The receiver repeats the process in the same order:
- It fetches the public key from DNS at
s1._domainkey.example.com(selector, then_domainkey, then thed=domain). - It hashes the body it received and compares that to
bh=. If they differ, you get body hash did not verify. - It checks
b=against the headers it received using the public key. If that fails, the signature itself didn't verify.
So the error tells you where to look. A body hash failure means the body changed. A signature failure with a matching body hash means a signed header changed, or the key in DNS doesn't match the key that signed.
Find out exactly what failed
- Send a test message to a Gmail or Outlook.com mailbox you control and view the original message.
- Find the
Authentication-Resultsheader and note the DKIM result, the reason text in parentheses, and theheader.dorheader.idomain. - Find the
DKIM-Signatureheader and noted=,s=,c=andh=. - Paste the full headers into the email header analyzer if you'd rather not read them raw. How to read an email header explains the rest.
Check whether there's more than one signature. Many providers sign once with their own domain and once with yours. Only the one with your domain matters for DMARC, so make sure the failure you're chasing is on that one.
In Microsoft 365 you may also see compauth=pass reason=108. Microsoft documents reason 108 as DKIM failing because of body modification by legitimate earlier hops: its way of saying it noticed the breakage and let the message through on other evidence.
Common causes of "body hash did not verify"
1. Something added a footer or disclaimer after signing
The classic case. Your application or email provider signs the message, then an outbound mail gateway, a compliance system or a transport rule appends a legal disclaimer or an "external email" banner. The body no longer matches bh=. Look for any server in the Received chain after the one that signed.
2. A mailing list modified the message
Lists often add a footer ("To unsubscribe from this list...") or rewrite the body. DKIM breaks for every subscriber. You can't fix this from the sender side, which is why DMARC policies can cause trouble for domains whose users post to lists. See DMARC policy: none vs quarantine vs reject.
3. A relay re-encoded the message
If a server in the path converts the content transfer encoding (8-bit to quoted-printable, for example) or rewraps long lines, the body bytes change even though the text looks identical to a reader. Relays that only speak 7-bit SMTP are a known source.
4. Your own code signs before it finishes the message
If you sign in application code, make sure signing is the very last thing that happens. Adding a tracking pixel, rewriting links or rebuilding the MIME structure after signing breaks the hash every time. Line endings matter too: the message must use CRLF line endings as sent, or the hash won't match.
5. Simple canonicalization
Canonicalization is how much tolerance the signature allows for trivial changes. c= defaults to simple/simple if not set, and simple body canonicalization breaks on whitespace changes that relaxed would forgive. If you control the signer and see intermittent body hash failures, c=relaxed/relaxed is the usual setting.
Common causes of signature failures and key errors
6. A signed header was changed
A gateway that adds [EXTERNAL] or [SPAM] to the Subject line, or rewrites the From address, breaks the signature if that header is in h=. The From header is always signed, since RFC 6376 requires it.
7. The public key doesn't match the private key
Usually after a key rotation where the new private key went live before the new public key was published, or someone updated one side and not the other. Every message fails until they match again.
8. The key isn't in DNS, or it's broken
The selector was deleted, a CNAME to your provider points at nothing, or the key got mangled on the way in. Long 2048-bit keys exceed the 255-character limit for a single TXT string, so they have to be split into multiple quoted strings; some DNS consoles do this for you and some truncate the key instead. An empty p= in the key record means the key has been revoked. Check the record with the SPF, DKIM and DMARC checker.
9. A temporary DNS problem
If the receiver's DNS lookup for your key times out, you'll see a temperror rather than a failure. One-offs aren't worth worrying about. Repeated temperrors point at your DNS provider.
10. An expired signature or a weak key
If the signature includes an expiration (x=) and the message is verified after it, the signature can be treated as invalid. Separately, Google requires keys of at least 1024 bits for mail to personal Gmail accounts and recommends 2048.
Avoid the l= tag, which limits the signature to the first part of the body. It's sometimes suggested as a fix for appended footers, but it lets anyone add content below the signed portion of your message without breaking the signature. That's a gift to spoofers.
How to fix it, step by step
- Identify the failing signature (domain and selector) from the headers.
- Check the key in DNS at
selector._domainkey.yourdomain. Make sure it exists, isn't truncated, and matches what your provider shows in its setup screen. - If the body hash fails, find what modified the message. Compare the
Receivedchain to where signing happens. Anything after the signer that adds content is the suspect. - Move signing to the last hop you control. If a gateway must add a disclaimer, have the gateway sign (or re-sign) after adding it, or drop the disclaimer for that mail stream.
- Stop modifying signed headers after signing, or configure the tagging system to skip outbound mail.
- Use relaxed canonicalization if you control the signer.
- For rotations, publish the new public key first, wait for DNS to propagate, then switch signing. Keep the old key published until mail signed with it has been delivered.
How to confirm it's fixed
Send fresh test messages to Gmail and Outlook.com mailboxes and check for dkim=pass with your domain in header.d or header.i. Then check that DMARC passes too, since that's what the fix is for. Your DMARC aggregate reports should show the source's DKIM result change from fail to pass within a day or two.
How to keep it from coming back
- Treat any new gateway, disclaimer or security product on outbound mail as a DKIM change and test it before rollout.
- Keep an inventory of selectors and which service uses each one.
- Watch your DMARC reports for DKIM failures on your own domain, which show up before customers notice.
- Make sure every source also has aligned SPF where possible, as a backup when a signature breaks. If DMARC is failing outright, DMARC fail covers the alignment side, and SPF, DKIM and DMARC explained covers how the pieces fit.
When to get help
If DKIM fails intermittently and you can't find what's touching the message, or the failure only happens at one receiver, it's worth having someone trace the path with you.
FAQ
What does "body hash did not verify" mean?
The body the receiver got doesn't match the hash the sender put in the signature. Something changed the message body after it was signed, usually a footer, a disclaimer or re-encoding.
Why does DKIM pass at one provider and fail at another?
Usually the path is different. A message to an outside provider may pass through a gateway or relay that internal mail skips, or one receiver tolerates a change the other doesn't.
Does a DKIM failure mean my mail will be rejected?
Not by itself. Under the DKIM spec, a signature that fails verification is generally treated like no signature at all. It becomes a problem when DKIM was your only aligned pass, because then DMARC fails, and your DMARC policy decides what happens next.
Does forwarding break DKIM?
Plain forwarding usually doesn't, because the signature travels with the message. Forwarding that modifies the message, like a mailing list adding a footer, does.
Sources
- RFC 6376: DomainKeys Identified Mail (DKIM) Signatures
- RFC 8601: Message Header Field for Indicating Message Authentication Status
- Microsoft Learn: Anti-spam message headers in Microsoft 365
- Google: Email sender guidelines
- RFC 9989: Domain-Based Message Authentication, Reporting, and Conformance (DMARC)
Related guides
- DMARC Fail: Why DMARC Fails and How to Fix ItDMARC fails when neither SPF nor DKIM passes for a domain aligned with your From address. Usually a sender lacks custom DKIM. Here's how to find and fix it.
- DMARC Policy: p=none vs quarantine vs reject (and Getting to Enforcement)p=none only monitors, p=quarantine asks receivers to junk failing mail, p=reject asks them to refuse it. Start at none, fix your senders, then tighten.
- DMARC Record Syntax and Tags ExplainedA DMARC record is a TXT record at _dmarc.yourdomain with tags for policy, reporting and alignment. Here's what each tag does and what's changed under DMARCbis.
- DMARC Reports: How to Read Aggregate (rua) and Failure (ruf) ReportsDMARC aggregate reports are daily XML summaries of every IP sending as your domain and whether it passed. Here's how to read them and what to do with them.
- SPF 10 DNS Lookup Limit, Void Lookups and SPF FlatteningSPF allows 10 DNS lookups per check. Go over and receivers return permerror, so SPF fails. Here's what counts, how void lookups work, and how to fix it.
- SPF, DKIM and DMARC Explained: How They Work TogetherSPF checks the sending server, DKIM checks a signature, and DMARC checks that one of them passed for your From domain and says what to do if not.
Still stuck?
Some deliverability problems take an experienced eye to untangle. Tell me what you're seeing and I'll tell you what I'd check first. The first 30-minute call is free.