DMARC Engine
Home/Blog/Fix: DKIM signature did not verify
Blog

Fix: DKIM signature did not verify

"DKIM signature did not verify" is at least six different problems wearing one error string: body hash mismatch, no key in DNS, malformed record, key mismatch after rotation, expired signature, or a key too short. Here is how to read the exact reason in the brackets and apply the one correct fix for each.

17 April 2026 · 13 min read

Fix: DKIM signature did not verify

Fix: "DKIM signature did not verify"

You sent a message, checked the headers (or read a DMARC failure report), and found a line that says something close to:

Authentication-Results: mx.google.com;
  dkim=fail (body hash did not verify) header.i=@yourdomain.com header.s=selector1

or, from a different receiver:

dkim=permerror (no key for signature) header.s=s1 header.d=yourdomain.com

A DKIM signature that "did not verify" is not one problem. It is at least five different problems wearing the same error string, and the fix for each is completely different. A body hash mismatch means the message changed in transit. A permerror with no key means DNS is wrong. A keyfail or signature mismatch means the wrong key signed it, or the key is too short for the receiver. An expired key means the signature is technically valid but past its sell-by date. Treat them as one bug and you will spend an afternoon re-rotating keys to fix a mailing-list rewrite that no key rotation can ever fix.

This article walks through each failure mode, shows you how to read the specific evidence in the headers and in your DMARC reports, and gives you the actual fix. It is DKIM-specific throughout; if you want a primer on what DKIM is and how the signing process works, What is DKIM is the place to start, and DKIM selectors explained covers how selectors map to published keys.

Before anything else, run the domain and selector through the DKIM checker. Most of the time it tells you in ten seconds which of the five problems you have, because it pulls the published key out of DNS and validates its structure. If the checker says "no record" you have a DNS problem; if it says the key parses fine, your problem is almost certainly in the message itself.

First, read the exact reason in the brackets

Receivers almost always tell you why, and the words matter. The Authentication-Results header (and the equivalent fields in an aggregate or failure report) carry a short reason in brackets after dkim=fail. Map the reason to the cause:

  • body hash did not verify (sometimes bh fail or body hash mismatch): the body, or a header that was part of the signed content, changed after signing. The key is fine. This is the single most common DKIM failure in the wild.
  • signature did not verify without "body hash": the cryptographic signature over the headers does not match the published public key. Usually a key mismatch (wrong key signed it, or the wrong public key is in DNS), or a signed header was altered.
  • no key for signature / dkim=permerror: the d= domain and s= selector in the signature do not resolve to a usable public key in DNS. A DNS, selector, or publishing problem.
  • key syntax error / invalid public key: the DNS record exists but is malformed (wrapped wrong, split badly across strings, has stray characters).
  • signature expired: the signature carried an x= expiry timestamp that has passed. Valid signature, too old.
  • insufficient key length / weak key: the published key is shorter than the receiver now accepts (the live case here is 512-bit and increasingly 1024-bit keys being downgraded).

Get the exact bracketed reason first. Everything below is keyed to it.

Cause 1: Body hash did not verify (the message changed in transit)

DKIM signs two things: a hash of the body (the bh= value in the signature) and a hash of a chosen set of headers. If a single byte of the signed body changes between your signing server and the receiver, the body hash the receiver computes will not match the bh= you signed, and you get body hash did not verify. The crypto is working perfectly; it is doing exactly its job, which is to detect that the body was modified.

What modifies a body in transit:

  • Mailing lists and discussion groups. They add a footer ("You are subscribed to..."), prepend [list-name] to the subject (a signed header), or convert the message format. Any of these breaks the body hash, the header hash, or both. This is by design and no DKIM change fixes it; the answer is ARC and SPF, covered in DKIM alignment and forwarding and Forwarding and DMARC.
  • Antivirus and security gateways that append a "Scanned by..." banner or rewrite links (URL defence rewriting) change the body. If your own outbound gateway does this after signing, you are breaking your own DKIM.
  • Auto-forwarders that re-encode the message, or "send as" relays.
  • Encoding changes, for example a relay that re-wraps long lines or converts quoted-printable to base64.

How to diagnose it specifically:

  1. Send a clean test message directly from the signing system to a mailbox you control at a major receiver (a Gmail or Outlook test address works). Look at Authentication-Results. If DKIM passes here but fails when the same domain goes through a list or gateway, the body is being modified downstream, not at signing.
  2. In your DMARC reports, look at which sources fail DKIM. If your direct sending IP passes and only forwarders or list servers fail, this is body modification, full stop. Read Reading your first DMARC report to find the source IP column.
  3. Check whether your own outbound path adds anything after signing. The order matters: signing must be the last thing that touches the message.

The fixes, in order of preference:

  • Sign last. If your own gateway appends footers or rewrites URLs, make sure DKIM signing happens after those modifications, not before. In many setups the application signs, then the security appliance mangles. Reverse that.
  • Lean on alignment redundancy. DMARC passes if either DKIM or SPF aligns. If DKIM breaks on a forwarded path but the path preserves your envelope sender, aligned SPF can still carry you. It often will not for forwarding (the envelope is rewritten too), which is why the real answer for relayed mail is ARC.
  • ARC (Authenticated Received Chain). When a list or forwarder is ARC-aware, it records that DKIM did pass when it received the message, and a downstream receiver that trusts the chain can honour that. See ARC explained.
  • Do not try to "fix" a body hash failure by rotating keys, lengthening keys, or republishing DNS. None of those touch the body. If the reason says body hash, the key is innocent.

One subtle trap: the canonicalisation algorithm. DKIM signatures declare a canonicalisation in the c= tag, for example c=relaxed/relaxed (header/body). relaxed body canonicalisation tolerates trailing whitespace and blank-line changes; simple tolerates almost nothing. If your signer uses simple body canonicalisation and any relay so much as adds a trailing space, the body hash breaks. Switching the signer to relaxed/relaxed removes a whole class of fragile failures. Check your signer's configuration if you see intermittent body-hash failures on paths that "should" be clean.

Cause 2: No key for signature (DNS or selector problem)

dkim=permerror with no key for signature means the receiver took the s= selector and d= domain from the signature, built the DNS name <selector>._domainkey.<domain>, queried it, and got nothing usable. The signature names a key that DNS cannot produce.

Work the lookup the way the receiver does. If the signature header says s=s1; d=yourdomain.com, then the public key must live at:

s1._domainkey.yourdomain.com.   TXT   "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEB..."

Query it directly:

dig +short TXT s1._domainkey.yourdomain.com

What you find tells you the fix:

  • Empty result. The record was never published, or was published under the wrong selector name. Compare the s= in the actual signature with the selector your provider told you to publish. A frequent error is publishing selector1 while the sender signs with s1, or vice versa. The selector in DNS must match the selector in the signature byte for byte. DKIM selectors explained covers why providers use rotating selectors and why this mismatch is so common after a provider migration.
  • Record exists at the wrong host. Many DNS panels auto-append the zone. If you typed s1._domainkey.yourdomain.com into a panel that already adds .yourdomain.com, you created s1._domainkey.yourdomain.com.yourdomain.com. Enter only the host label, s1._domainkey.
  • CNAME that does not resolve. Providers like SendGrid and many ESPs have you publish a CNAME from your selector to their host so they can rotate keys for you. If that CNAME is proxied (Cloudflare orange cloud), or points at the wrong target, the chain breaks and the receiver sees no key. Set DKIM CNAMEs to DNS-only and confirm the target resolves with dig.
  • Right record, but you are checking the wrong selector. A domain can have many selectors live at once (different services sign with different selectors). The failure is for the selector in that message's signature, not whichever selector you remember setting up. Always read s= from the failing message.

Paste the selector and domain into the DKIM checker; it does this lookup and validation for you and flags an empty or malformed record immediately. For publishing the record correctly the first time, DKIM setup and the DNS record reference have the exact host and value formats.

Cause 3: Key syntax error (the record exists but is malformed)

Sometimes the lookup returns a record, but the receiver reports key syntax error or invalid public key, or the DKIM checker says it found a record that will not parse. The classic causes:

  • The TXT record was split badly. DNS TXT records are limited to 255 characters per string, so a 2048-bit key spans multiple quoted strings. They must be concatenated with no extra characters. Some panels mangle this; some require you to enter the whole value and split it for you. If you manually split and introduced a space inside the base64 p= value, the key is corrupt.
  • Line breaks or whitespace inside p=. Copy-pasting a key from a provider's web UI can drag in a newline. The p= value must be one unbroken base64 string.
  • Wrong record type. The key is published as something other than TXT, or the provider gave you a CNAME and you pasted the key value as a TXT.
  • Stray v=DKIM1 ordering or missing tags. The record should begin v=DKIM1; k=rsa; p=.... A missing k= is tolerated by some receivers and not others.

A healthy 2048-bit record looks like this (one logical value, even if your panel shows it as two quoted strings):

selector1._domainkey.yourdomain.com.  TXT  ( "v=DKIM1; k=rsa; "
  "p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyourkeydatahere...IDAQAB" )

The fix is to re-publish the value exactly as the provider supplies it, strip any whitespace inside p=, and re-validate. Do not invent line breaks. If your provider issued the key as a CNAME, publish the CNAME and let them host the TXT.

Cause 4: Signature did not verify (key mismatch)

signature did not verify (without "body hash") and the body hash did verify means the cryptographic signature over the headers does not match the public key in DNS. The body arrived intact, but the private key that signed the message is not the partner of the public key the receiver fetched. Causes:

  • Stale public key after a rotation. You rotated the signing key (new private key on the sender) but the old public key is still in DNS under the same selector, or the new public key has not propagated. The sender is signing with key B; DNS still serves key A. See DKIM key rotation: the correct rotation pattern is to publish the new key under a new selector, switch signing to it, then retire the old selector, so old in-flight mail still validates.
  • Two systems signing the same domain with the same selector. If two platforms both claim selector s1 for yourdomain.com but only one's public key is in DNS, every message from the other fails. Give each system its own selector.
  • A signed header was altered. Less common than body changes, but if a relay rewrites a header that was listed in the signature's h= tag (for example, reformatting the From display name, or a list rewriting Subject), the header hash breaks and you get a signature mismatch. The h= tag in the signature lists exactly which headers were signed; compare them against what arrived.

Diagnose by re-fetching the public key (dig +short TXT <selector>._domainkey.<domain>) and confirming, with your provider, that it is the current key the sender is using. If you recently rotated, this is almost always a propagation or wrong-selector issue, not a corrupted message.

Cause 5: Expired key or expired signature

Two different "expiry" situations both surface as failures:

  • signature expired. Some signers stamp signatures with an x= expiry. If a message is delivered (or re-evaluated after long forwarding delays) past that timestamp, the signature is rejected even though it is otherwise perfect. If you set x= very short, normal queuing or greylisting delays can push delivery past expiry. The fix is to lengthen or remove the x= window in your signer, or stop relying on signatures that expire faster than mail can plausibly be delivered.
  • A key you retired too early. During rotation, mail can sit in queues for hours or days. If you delete the old selector's DNS record the moment you switch signing, any still-in-flight message signed with the old key fails with no key. Keep the previous selector's public key published for at least a week (longer if you have slow downstream relays) after you stop signing with it.

Neither of these is a cryptography failure. They are timing failures, and the fix is to widen the windows.

Cause 6: Key too short (receiver downgraded a weak key)

If the reason mentions insufficient key length, weak key, or you see DKIM mysteriously start failing at a single large receiver while others pass, the published key is shorter than that receiver now accepts. The industry has moved off 512-bit entirely (universally rejected) and is steadily downgrading 1024-bit. A 1024-bit key that "worked last year" can be treated as dkim=neutral or fail today by a receiver that tightened its policy.

The fix is to rotate up to a 2048-bit key. The mechanics are the same safe rotation as any key change, and the size trade-offs (DNS record length, performance, and why 2048 is the right floor) are covered in DKIM key length: 1024 vs 2048. Note 2048-bit public keys exceed the 255-character TXT string limit and must span multiple strings; re-read Cause 3 so you do not corrupt the record while publishing the longer key.

Why this matters for DMARC specifically

DKIM does not pass or fail in isolation under DMARC. DMARC needs an aligned pass: the d= domain in a valid DKIM signature must match (in organisational-domain terms) the domain in the visible From: header. So there are two ways a DKIM problem hurts you:

  1. DKIM fails to verify at all (the six causes above). No valid signature, no DKIM contribution to DMARC.
  2. DKIM verifies but does not align. The signature is cryptographically perfect, but d= is the ESP's domain, not yours, so DMARC ignores it. That is a different fix entirely, covered in DKIM alignment explained.

This distinction matters because the same DMARC failure report can stem from either. If your reports show DKIM passing but DMARC still failing, you have an alignment problem, not a verification problem, and nothing in this article applies; go to the alignment guide. If DKIM is failing to verify, work the six causes here first. To see exactly which sources pass and fail, and on which check, feed your aggregate reports into the DMARC report analyzer, and confirm your policy is where you think it is with the DMARC checker.

A diagnostic order that actually works

When you are staring at a verification failure, do these in sequence rather than guessing:

  1. Read the bracketed reason. Body hash, no key, syntax, signature mismatch, expired, or weak key. This alone narrows it to one cause.
  2. Pull s= and d= from the failing signature. Not the selector you assume; the one in this message.
  3. Look up the key: dig +short TXT <s>._domainkey.<d> and run the same selector through the DKIM checker. If the key is missing or malformed, you are in Cause 2 or 3 and the message body is irrelevant.
  4. If the key is healthy, send a direct test from the signer to a controlled mailbox. Passes direct but fails via lists/forwarders means body modification (Cause 1). Fails even direct means key mismatch or expiry (Cause 4 or 5).
  5. Check your DMARC reports for the pattern: which IPs and which paths fail. Direct mail clean and only forwarders dirty is the signature of in-transit modification.

The practical takeaway

"DKIM signature did not verify" is a precise error, not a vague one. The receiver almost always tells you the real cause in brackets, and each cause has exactly one correct fix: body-hash failures come from the message changing in transit (sign last, lean on SPF alignment, adopt ARC for relayed mail); no key and syntax errors come from DNS (fix the selector, the host, or the record format); signature mismatches come from key rotation gone wrong (rotate under a new selector and let it propagate); expiries come from windows set too tight; and weak-key downgrades come from sticking with 1024-bit. Stop rotating keys to fix problems that no key change can touch, and stop re-checking the body for problems that live in DNS.

Start by running the failing selector through the DKIM checker and your aggregate reports through the DMARC report analyzer; between them they will point you at one of the six causes in a minute. If you would rather not maintain selectors, key rotations and the DNS records that go with them, our done-for-you hosted DKIM signs your mail, rotates keys safely under new selectors, and keeps the old keys published long enough that nothing in flight ever fails, while continuous monitoring alerts you the moment a verification rate drops so you fix it before it dents enforcement.

Share

See where your domain stands today

Run a free DMARC scan, then let us take you to enforced p=reject with no email outage.