If DKIM fails while SPF passes, the message reached the recipient with a valid return-path but a broken cryptographic signature. SPF only checks whether the sending IP is authorized for the envelope domain, so it can pass on the same message where DKIM fails, because DKIM verifies that the signed headers and body arrived exactly as your server sent them. A DKIM fail (not none) almost always means one of two things: the signature could not be verified against the published key, or the content the signature covered was altered in transit.
Reads public DNS only. Nothing is stored unless you save the domain to an account.
This guide covers the signature-level causes, not DKIM alignment. If your DKIM result is actually pass but DMARC still fails, that is a different problem covered in DKIM alignment failures. Here we fix the signature itself.
First, read the exact DKIM result
Open the raw message and find the Authentication-Results header. The DKIM verdict tells you which branch to investigate.
dkim=passmeans the signature verified. If DMARC still fails, it is an alignment issue, not a signature issue.dkim=failmeans the receiver found a signature but could not verify it. This is the case this guide solves.dkim=nonemeans noDKIM-Signatureheader was present at all, so nothing was signed.dkim=temperrorordkim=permerrorusually points at DNS: the key could not be fetched, or the record is malformed.
A typical failing header looks like this:
dkim=fail (body hash did not verify) header.d=yourdomain.com header.s=selector1
The text in parentheses is the single most useful clue on the page. "Body hash did not verify" and "signature did not verify" send you down completely different paths. If you are new to parsing this header, see how to read the Authentication-Results header.
Cause 1: the body was modified in transit (body hash mismatch)
DKIM computes a hash of the message body (the bh= value) and a hash of selected headers. If the body changes by even one byte after signing, the bh= no longer matches and you get a DKIM body hash mismatch. SPF is untouched by this because SPF never looks at content.
The usual culprits:
Mailing lists and discussion groups
List servers (Mailman, Google Groups, listservs) frequently append a footer, add a [list-name] tag to the subject, or convert the body encoding. Any of these breaks the body hash. This is expected behavior for lists and is precisely why DMARC handling for lists is hard. The list should re-sign with its own domain (ARC helps preserve the original result), but your signature will legitimately fail after the modification.
Forwarding that rewrites content
Some forwarders and security gateways rewrite links (URL defense/click protection), inject "external sender" banners, or add disclaimers. Each edit changes the body. Note that plain SMTP forwarding breaks SPF, while content-rewriting forwarding breaks DKIM. The two failure modes are covered separately in why email forwarding breaks SPF.
Corporate footers and signatures added by a gateway
An appliance that stamps a legal disclaimer onto every outbound message after your MTA signs it will break your own DKIM. The fix is ordering: sign last, after every content-modifying hop inside your own infrastructure.
Cause 2: the header set changed
DKIM also hashes a chosen list of headers named in the h= tag. If a signed header is altered or a second copy is added, the signature fails even when the body is intact. The most common trigger is a header that appears twice (for example two Subject lines or a duplicated From) because DKIM signs a specific instance. Downstream systems that rewrite the Subject to add a tag will break signatures that included Subject in h=.
Cause 3: the selector or key is wrong (DNS-side failure)
If verification fails before any content check, the receiver could not match your signature to a public key. Look at the s= (selector) and d= (domain) in the DKIM-Signature header, then query the key yourself:
selector1._domainkey.yourdomain.com TXT
Common findings:
Selector not found
The DKIM-Signature names a selector that has no TXT record. This happens after migrating providers, rotating keys before publishing the new one, or a typo in the selector name. A DKIM key lives at <selector>._domainkey.<domain>, so the label must match exactly.
Wrong key published
The record exists but holds an old or mismatched public key, so the signature made with the current private key cannot verify. This is the classic outcome of rotating the private key at your provider without updating DNS. Keep rotation coordinated on a schedule, as described in DKIM key rotation.
Key truncation
DNS TXT strings are limited to 255 characters each, and a 2048-bit key exceeds that. The record must be split into multiple quoted strings inside one TXT record, which the resolver concatenates. If your DNS host silently dropped the second chunk or you pasted only part of the key, the p= value is incomplete and verification fails. Re-publish the full key and confirm the reassembled p= matches what your sender expects. Setup details are in how to set up DKIM.
Revoked key
A record with an empty p= (v=DKIM=1; p=) is an intentional revocation. If you see this on a key you still use, someone emptied it.
Cause 4: the l= body length tag
DKIM supports an optional l= tag that signs only the first N bytes of the body. It exists so that a footer appended after that length does not break the hash. In practice l= is risky: it lets an attacker append arbitrary content below the signed portion without invalidating the signature, and some receivers penalize or ignore it. If your signer uses l= and the body still fails, the message was likely modified within the signed region, or the body was shortened below the declared length. The safer configuration is to sign the whole body and remove l=.
Symptom to cause table
| Authentication-Results says | Most likely cause | Fix |
|---|---|---|
body hash did not verify | Footer, list tag, disclaimer, or link rewriting after signing | Sign last; expect list failures; use ARC |
signature did not verify | A signed header changed or was duplicated | Trim h=; sign last |
key not found / no key for signature | Selector missing in DNS | Publish <selector>._domainkey TXT |
permerror / invalid key | Truncated or malformed p= | Re-publish full key in split TXT strings |
pass but DMARC fails | Not a signature problem | Fix alignment, not the signature |
The verification loop
Work this loop until the header shows dkim=pass:
- Send a test message to a mailbox you control and open the raw source.
- Read the DKIM reason string in
Authentication-Results. - Note the
d=ands=from theDKIM-Signatureheader. - Query
<s>._domainkey.<d>and confirm thep=key is present and complete. - If the key is fine, the failure is content. Send the same message directly (no list, no forwarder) and re-check. If it passes direct but fails via the list, the modification is the cause.
- Move any content-modifying step (footers, disclaimers, link rewriting) to run before signing, so DKIM signs the final body.
- Re-test.
How this differs from a DMARC alignment failure
This is the distinction that trips up most people. DKIM authentication asks "is the signature cryptographically valid." DKIM alignment asks "does the d= domain in a valid signature match the From: domain." You can have a perfectly valid dkim=pass that still fails DMARC because it was signed by your provider's domain instead of yours. That is an alignment fix (publish a key on your own domain and sign with d=yourdomain.com), not a signature fix. If your result is pass and DMARC still complains, stop here and read DKIM alignment failures. If your result is fail, the causes above are what you need.
Frequently asked questions
Why does DKIM fail but SPF passes on the same email?
SPF checks the sending IP against the envelope-from domain and ignores message content. DKIM verifies a cryptographic hash of the headers and body. A relay can be an authorized IP (SPF pass) while a footer or list tag alters the body and breaks the DKIM hash. They test different things, so one passing and the other failing is normal.
What does "body hash did not verify" mean?
It means the message body changed after your server signed it. The bh= value in the signature no longer matches the body the receiver hashed. Common causes are mailing-list footers, appended disclaimers, link rewriting by a gateway, or encoding conversion in transit.
Can I fix DKIM failures caused by mailing lists?
Not by changing your own signature, because the list legitimately modifies the message. The list operator should re-sign with the list domain and use ARC to carry your original authentication result forward. For your own sending, ensure DKIM is the last step so nothing inside your infrastructure edits the body after signing.
How do I know if the problem is the key or the content?
Read the reason string. "Key not found" or a permerror points at DNS, so check the selector and the full p= value. "Body hash did not verify" or "signature did not verify" points at content, so test the message sent directly versus through the list or forwarder that is altering it.