What dkim=fail (body hash did not verify) means, how bh=, l= and c= canonicalization work, the footers, gateways, re-encoding and forwarding that break DKIM after signing, and how to find the hop and fix it.
Home › Guides › DKIM body hash did not verify
Last updated 18 October 2026
dkim=fail (body hash did not verify) means the message body that arrived is not the body that was signed. The receiver hashed the body again and got a different value from the one stored in the signature's bh= tag. Your DNS key record isn't the problem: the body hash check doesn't use the key at all. Something changed the message after it was signed.
The usual suspects, in rough order:
The fix is to sign last: add every footer and rewrite before the message is signed, or move DKIM signing to the last server you control. Then use c=relaxed/relaxed, add ARC for forwarding paths, and keep an aligned SPF pass as the second route to DMARC.
Receivers report DKIM in the Authentication-Results header with one of the result values defined in RFC 8601 (section 2.7.1). The text in parentheses is a comment from the receiver; it carries the reason.
| Result | Meaning (RFC 8601) | What to look at |
|---|---|---|
pass | Signed, and the signature passed verification. | For DMARC, also check that header.d aligns with the From domain. |
fail | Signed, but the signature failed verification. | The comment. body hash did not verify: the body changed. signature did not verify: a signed header changed, or the key doesn't match. |
neutral | Signed, but the signature had syntax errors or couldn't be processed; also used for other failures not covered elsewhere. | The comment. A receiver can report a broken signature as neutral so it is treated as if unsigned. |
none | Not signed. | Whether a DKIM-Signature header is there at all, or was stripped on the way. |
policy | Signed, but some aspect of the signature wasn't acceptable to the receiver. | The receiver's own rules, for example about algorithms. |
temperror | A temporary error, such as being unable to fetch the public key right now. | DNS for <selector>._domainkey.<domain>; a later try may work. |
permerror | An error that won't fix itself, such as a required header field being absent. | The DKIM-Signature syntax and the key record. |
RFC 6376 (section 6.1.3) defines the two failure reasons you see most. The verifier first compares the body hash with bh=; on a mismatch it SHOULD ignore the signature and return PERMFAIL (body hash did not verify). Then it checks the b= signature with the public key; on a mismatch it returns PERMFAIL (signature did not verify). Receivers differ in which of fail, neutral or permerror they put on the result line for a given problem, so always read the comment.
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com; s=s1; h=from:to:subject:date:message-id; bh=<base64 hash of the body>; b=<base64 signature>
RFC 6376 section 3.7 says signer and verifier compute two hashes:
c=, cut to the length in l= if there is one, hashed, and stored in base64 as bh=.h=, plus the DKIM-Signature header itself (including bh=, with b= left empty), are canonicalized and hashed. That hash is what the private key signs into b=.So a changed body breaks bh=, and a changed signed header (a [EXTERNAL] tag added to the Subject, say) breaks b=. Two details catch people out:
l= tag (body length) limits the hash to the first l octets of the canonicalized body; without it, the whole body is signed. Anything after that point isn't covered, and l=0 means the body isn't signed at all. RFC 6376 section 8.2 warns that l= lets an intermediary append content of its own, tells signers to be extremely wary of it, and says verifiers might ignore signatures that use it.The c= tag names the header and body algorithms, separated by a slash. If it is missing, the default is simple/simple. If only one name is given, it applies to the header and the body stays simple, so c=relaxed means relaxed/simple.
| Algorithm | What it tolerates (RFC 6376 section 3.4) |
|---|---|
Header simple | Nothing. Header fields must arrive exactly as signed, including case and whitespace. |
Header relaxed | Header names in a different case, re-folded (rewrapped) header lines, runs of spaces or tabs, and whitespace at the end of a value or around the colon. |
Body simple | Only extra empty lines at the end of the body. |
Body relaxed | Whitespace at the end of lines, runs of spaces or tabs inside a line (reduced to one space), and extra empty lines at the end. |
Neither body algorithm tolerates added or removed text, lines broken in different places, changed characters or a different transfer encoding. Relaxed canonicalization protects against whitespace changes, not against footers.
Canonicalization also assumes the message is already in standard form, with CRLF line endings. RFC 6376 section 5.3 says bare CR or LF must be converted to CRLF before signing, and that conversion of 8-bit text to 7-bit in transit breaks signatures, so signers SHOULD encode 8-bit content as quoted-printable or base64 first.
simple body canonicalization.d= aligned to the From domain helps DMARC, so check which one failed.Take the copy that arrived at the receiving mailbox (open the original or view the message source), and paste the headers into the Email Header Analyzer. Find the receiver's own Authentication-Results line:
Authentication-Results: mx.receiver.example; dkim=fail (body hash did not verify) header.d=example.com header.s=s1; spf=pass smtp.mailfrom=example.com; dmarc=pass header.from=example.com
header.d and header.s: the signing domain (d=) and selector (s=) of the signature that failed.d= and s= and read its c= value. Check the raw DKIM-Signature header for an l= tag.ARC-Authentication-Results and ARC-Seal headers. Each ARC set records what that intermediary saw when the message arrived there. If i=1 says dkim=pass for your domain and the final receiver says fail, the change happened at that intermediary or after it.Seeing signature did not verify, permerror or no key instead? Then check the key: the DKIM Checker finds published selectors and checks key length, and the DKIM Record Explainer decodes the key record tag by tag. For permerror or a missing key, how to fix DKIM no key for signature covers the causes step by step. For signature did not verify, also check the signed headers: a Subject tag or rewritten From breaks it too, as how to fix DKIM signature did not verify explains. For a body hash failure the key is fine, so changing or rotating it won't help.
DKIM has to be applied to the message exactly as it will leave your systems. RFC 6376 section 5.3 puts it this way: the signer must sign the message as it is expected to be received by the verifier.
_domainkey), or move the footer or disclaimer to the platform that signs, so it is added before signing.If your signer offers it, set c=relaxed/relaxed. It survives header rewrapping and whitespace changes that break simple. It won't save a message that gets a footer, so treat it as a complement to Fix 1. Avoid using l= to let footers through: the appended text is unsigned, many footers are inserted inside the MIME or HTML structure rather than at the very end anyway, and RFC 6376 says verifiers might ignore signatures that use it.
When your own security gateway rewrites inbound mail before the mailbox platform checks DKIM, the mailbox sees a broken signature for mail that was fine on arrival. Check authentication at the first hop, or have the gateway add ARC and configure the mailbox platform to trust that sealer, if both support it.
ARC (RFC 8617, an Experimental RFC) lets each intermediary record the authentication results it saw (ARC-Authentication-Results), sign the message as it passed through (ARC-Message-Signature) and seal the chain (ARC-Seal, with cv=none, pass or fail). A receiver MAY, as local policy, accept those earlier results when deciding whether the message passes DMARC (RFC 8617 section 7.2.1). You can't make other people's forwarders or lists use ARC; that is why the next fix matters. For what forwarding and lists do to DMARC as a whole, see why DMARC fails after forwarding or on mailing lists.
DMARC passes if either aligned SPF or aligned DKIM passes. For mail delivered directly, a passing SPF check on a MAIL FROM domain under yours keeps DMARC passing even when something breaks the body hash, as in the example above. Set up a custom return-path at each sender that offers one; the DMARC alignment guide explains how, and the SPF Record Checker confirms the record. SPF doesn't survive forwarding, so this is a safety net, not a replacement for working DKIM.
dkim=pass with header.d set to your domain, and dmarc=pass.d= domain.Need SPF, DMARC and MX checks across a whole client list? Run the Bulk SPF / DMARC / MX Auditor on Apify ($2 per 1,000 domains).
Read DKIM results, signatures and ARC sets with the Email Header Analyzer, find published selectors with the DKIM Checker, decode a key record with the DKIM Record Explainer, review your policy with the DMARC Record Checker and see each source's DKIM result in the DMARC Report Analyzer. Related guides: DMARC fail with SPF pass (alignment), DMARC for Microsoft 365 and DKIM for Microsoft 365.
The receiver hashed the message body and the result did not match the bh= value in the DKIM-Signature header, so the body changed after it was signed. Footers, disclaimers, link rewriting, banners, re-encoding and mailing lists are the usual causes.
No. The body hash is compared with bh= without using the public key. A wrong, missing or rotated key shows up as signature did not verify, no key, or permerror instead. Fix a body hash failure on the mail path, not in DNS.
Only those caused by whitespace: trailing spaces, runs of spaces or tabs, and extra empty lines at the end. It does not tolerate added text such as a footer, changed line breaks or a different transfer encoding. The default when c= is missing is simple/simple, and c=relaxed alone means relaxed/simple.
It is not recommended. l= leaves everything after the given length unsigned, so anyone along the path can append content, and RFC 6376 says verifiers might ignore signatures that use it. Add footers before signing instead.
It can, if SPF passes for a MAIL FROM domain aligned with the From address, or if another DKIM signature for an aligned domain still verifies. Forwarded mail usually loses SPF, so fixing the DKIM break is the reliable answer.
Runbooks, checklists and helpdesk templates: IT Admin Packs on Gumroad.