dkim

DKIM Fails but SPF Passes: Why It Happens and How to Fix the Signature

When DKIM fails but SPF passes, the sending IP was authorized but the signature broke. This guide diagnoses the signature-level causes: body hash mismatch from list footers and forwarding, altered headers, missing selectors, truncated keys, and l= body-length quirks. Includes a symptom-to-cause table, a step-by-step verification loop, and how signature failures differ from DMARC alignment failures so you fix the right thing.

Jul 3, 20268 min read

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=pass means the signature verified. If DMARC still fails, it is an alignment issue, not a signature issue.
  • dkim=fail means the receiver found a signature but could not verify it. This is the case this guide solves.
  • dkim=none means no DKIM-Signature header was present at all, so nothing was signed.
  • dkim=temperror or dkim=permerror usually 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 saysMost likely causeFix
body hash did not verifyFooter, list tag, disclaimer, or link rewriting after signingSign last; expect list failures; use ARC
signature did not verifyA signed header changed or was duplicatedTrim h=; sign last
key not found / no key for signatureSelector missing in DNSPublish <selector>._domainkey TXT
permerror / invalid keyTruncated or malformed p=Re-publish full key in split TXT strings
pass but DMARC failsNot a signature problemFix alignment, not the signature

The verification loop

Work this loop until the header shows dkim=pass:

  1. Send a test message to a mailbox you control and open the raw source.
  2. Read the DKIM reason string in Authentication-Results.
  3. Note the d= and s= from the DKIM-Signature header.
  4. Query <s>._domainkey.<d> and confirm the p= key is present and complete.
  5. 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.
  6. Move any content-modifying step (footers, disclaimers, link rewriting) to run before signing, so DKIM signs the final body.
  7. 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.

Check your own domain

Run a free scan and get your grade with the exact records to fix.

Scan a domain

Related guides

DKIM Fails but SPF Passes: Fix the Signature