"DKIM signature verification failed" (dkim=fail) means the receiving mail server found a DKIM signature on your message but could not verify it against the public key published in your DNS. It happens when the published key does not match the private key that signed the message, when the selector the signature points at does not resolve, or when the message's body or signed headers were altered in transit so the hash no longer matches. The fix is to publish the correct key at the right selector, keep the private/public key pair in sync, and stop anything that rewrites the message after it is signed.
What the Error Looks Like
The verdict appears in the Authentication-Results header, usually with a reason in parentheses that tells you which way it failed:
# Bad signature — key mismatch or altered signed headers
Authentication-Results: mx.google.com;
dkim=fail (bad signature) header.i=@example.com header.s=s1;
dmarc=fail (p=REJECT sp=REJECT dis=REJECT) header.from=example.com
# Body hash mismatch — the message body changed after signing
Authentication-Results: mx.example.net;
dkim=fail (body hash did not verify) header.i=@example.com header.s=selector1;
# Selector not published — no key found in DNS
Authentication-Results: mx.google.com;
dkim=temperror (no key for signature) header.s=s1 header.d=example.com;The two fields that matter most are header.s= (the selector) and header.d= (the signing domain). Together they tell you exactly which DNS record the receiver looked up: <selector>._domainkey.<domain>.
Paste the message into the Email Header Analyzer to read the DKIM verdict
How DKIM Verification Works
When your provider sends a message, it hashes the body and selected headers, signs that hash with a private key, and attaches the result as a DKIM-Signature header. The receiver reads the s= selector and d= domain from that header, fetches the matching public key from your DNS, and recomputes the hash. Verification passes only if both are true:
- The signature verifies — the public key in DNS is the counterpart of the private key that signed the message.
- The body hash matches — the body (and signed headers) arrived byte-for-byte as they were when signed.
Break either half and you get dkim=fail. A missing selector record produces temperror/permerror instead, because there is no key to check against.
Why It Happens
1. The published key does not match the signing key
The most common cause. The public key in DNS is out of date, was copied incompletely, or belongs to a different key pair than the one your provider is signing with. Verification fails as "bad signature" because the math does not add up.
2. The selector is wrong or not published
The signature says s=selector1 but you published the key at s1._domainkey, or you never published it at all. The receiver queries selector1._domainkey.example.com, gets nothing, and cannot verify.
3. The message body was modified in transit
A mailing list, forwarder, or security appliance appended a footer, disclaimer, or "scanned by" banner, or re-encoded the body. Any change to the signed body breaks the body hash — this is the classic body hash did not verify failure, and it is why forwarded mail so often fails DKIM.
4. The DNS record is malformed or split incorrectly
DKIM public keys are long and often exceed the 255-character limit of a single TXT string, so they must be split into multiple quoted chunks that the DNS server concatenates. A stray space, a missing quote, or a wrapped line that got mangled by the DNS UI corrupts the key.
5. The key was rotated and the old selector retired too early
You generated a new key, published the new selector, and removed the old one before all in-flight mail signed with the old selector had been delivered. Messages still referencing the retired selector can no longer be verified.
6. A signed header was changed or the domain is misaligned
If a signed header (like Subject or From) is rewritten downstream, the signature breaks. Separately, DKIM can verify but still fail DMARC if the signing d= domain does not align with the visible From: domain.
dkim=fail means a signature was present but did not verify. dkim=none means the message was not signed at all — a different problem, fixed by enabling DKIM. temperror/permerror usually means the selector record could not be fetched or was invalid. Read the verdict carefully; the fix depends on which one you have.How to Diagnose It
Start from the signature. Pull the s= selector and d= domain out of the DKIM-Signature header, then query that exact record and confirm a valid public key comes back:
# The receiver looks up <selector>._domainkey.<domain>
dig TXT s1._domainkey.example.com +short
# e.g. "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB..."
# No answer? The selector is wrong or not published.
# A short or truncated p= value? The key is malformed or was split incorrectly.
# Compare against what your provider expects you to publish
# (selector name + full public key from the sending platform's DKIM page)If the key resolves fine but you still see body hash did not verify, the DNS is not the problem — something is modifying the message after it is signed. Move to Fix C.
How to Fix It
Fix A — Publish the correct key at the right selector
Go to your sending platform's DKIM/authentication page, copy the exact selector name and public key it shows, and publish them verbatim. The record name must be <selector>._domainkey.<domain> and the value must be the complete key:
; The selector must match header.s= exactly
s1._domainkey.example.com. 3600 IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQ...IDAQAB"
; Many ESPs give you a CNAME to their managed key instead of a raw TXT
s1._domainkey.example.com. 3600 IN CNAME s1.dkim.mailer.net.If your provider gives a CNAME, publish the CNAME (not a copied TXT) so key rotation on their side is handled for you automatically.
Fix B — Fix a malformed or split key
If the key exceeds 255 characters, split it into multiple quoted strings on the same record — the DNS server concatenates them. Do not add spaces between chunks and do not break the Base64 in a way your DNS UI will mangle:
; Long keys are split into 255-char quoted chunks, concatenated by DNS
s1._domainkey.example.com. 3600 IN TXT (
"v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDs...FIRST_CHUNK"
"SECOND_CHUNK...IDAQAB" )dig and compare the returned p= value against the provider's key character-for-character.Fix C — Stop the body from changing after signing
A body hash did not verify error means the message was altered downstream. Remove list footers, appended disclaimers, or "scanned by" banners for authenticated senders, and disable any gateway that re-encodes or rewrites the body. Where forwarding is unavoidable, rely on ARC at the receiver and make sure DKIM signs a stable set of headers so a relaxed body canonicalization tolerates minor whitespace changes.
Fix D — Rotate keys safely
When rotating, publish the new selector first, switch your platform to sign with it, and keep the old selector live until no mail signed with it is still in flight (a few days). Only then remove the old record:
; 1) Publish the new selector alongside the old one
s2._domainkey.example.com. 3600 IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSq...NEWKEY...IDAQAB"
; 2) Switch signing to s2 in the sending platform
; 3) Leave s1 published for a few days, then retire itd= domain should match your visible From: domain (relaxed alignment matches the organizational domain). Sign with your own domain, not the provider's, so a DKIM pass is also a DMARC pass.Verify the Fix
DNS changes only take effect once the old record's TTL expires. After publishing, confirm the selector resolves, send a test message to a mailbox you control, and check that its headers now show dkim=pass:
# Confirm the selector now returns the full key
dig TXT s1._domainkey.example.com +short
# Send a test, then in the raw headers look for:
# dkim=pass header.i=@example.com header.s=s1
# with header.d=example.com (aligned for DMARC)You can also send to a Gmail address and use Show original to see the DKIM verdict at a glance, or paste the full source into the Email Header Analyzer.
Frequently Asked Questions
Why does DKIM pass on Gmail but fail after forwarding?
Forwarding often rewrites the body (a list footer) or re-encodes it, which breaks the body hash even though the key is fine. The original message verified; the forwarded copy did not. This is a known limitation of forwarding, mitigated by ARC at the final receiver.
The key is published but I still get "bad signature" — why?
Almost always the published public key is not the counterpart of the private key currently signing your mail, or the key was copied incompletely. Re-copy the exact key from your provider, re-check it with dig, and confirm the selector name matches header.s= exactly.
What is the difference between dkim=fail and dkim=none?
dkim=fail means a signature was present but did not verify. dkim=none means the message carried no DKIM signature at all — you fix that by enabling DKIM signing in your sending platform and publishing its selector.
How long should I keep an old DKIM selector after rotating?
A few days is usually safe — long enough for any mail signed with the old key to have been delivered and verified. Remove it only once you are confident no in-flight message still references the old selector.
Does a DKIM failure always bounce my mail?
Not by itself. Many receivers accept a dkim=fail message if SPF passes and DMARC is satisfied another way. But if DKIM was your only aligned authentication and DMARC is enforcing, the failure can cascade into a DMARC reject.