SPF passes. DKIM record checks out in cPanel's Email Deliverability page. Everything looks green — but Gmail still shows dkim=fail in the message headers, or worse, drops the mail straight into Spam with a "this message may not be from who it says" warning. If you've already confirmed the DNS record exists and still get a fail, the problem usually isn't the record at all. It's what happens to the message after it's signed.
Symptom: DKIM Fails Even Though the DNS Record Is Correct
A few ways this shows up:
- Gmail's "Show original" reports
dkim=fail (body hash did not verify). - mail-tester.com or
check-auth@verifier.port25.comflags DKIM as failed while SPF passes cleanly. - Only some outbound mail fails — direct sends are fine, but anything forwarded, auto-replied, or sent through a mailing list breaks.
- It started right after a migration, a new SMTP plugin, or turning on an auto-responder — not after any DNS change.
If dig shows the right public key at the right selector and it still fails, stop looking at DNS. Look at the message itself.
Cause: The Signature Doesn't Match What the Receiver Sees
DKIM signs two things when a message leaves your server: the headers, and a hash of the body (bh= in the DKIM-Signature header). If anything touches the message between signing and delivery — even a single trailing space — the hash no longer matches and the signature fails. That's almost always what's going on. The usual culprits, roughly in order of how often we see them:
| Cause | What's happening |
|---|---|
| Body altered after signing | A mailing list footer, disclaimer injector, or antivirus gateway appends text to the body after the MTA already signed it. |
| Re-signed by a second sender | Both cPanel/Exim and an SMTP plugin (or Google Workspace dual delivery) sign the same message with different keys; the receiver checks the wrong one. |
| Selector mismatch | The s= value in the signature doesn't match a selector that actually has a published TXT record — common after switching mail providers but leaving old DNS in place. |
| Header rewriting | Auto-forwarding, vacation responders, or an email gateway (O365 transport rules, Cloudflare Email Routing) rewrite the Subject or From header, breaking the signed header hash. |
| Truncated key in DNS | Long 2048-bit RSA keys split across multiple quoted TXT strings get cut short by a DNS panel that doesn't handle string concatenation correctly. |
| Stale cached key | You rotated the DKIM key, but the receiver's resolver is still serving the old cached TXT value from before the change. |
Fix: Trace the Signature, Then Fix What's Touching the Message
1. Pull the raw headers and read the DKIM-Signature line
In Gmail, open the message, click the three dots, and choose Show original. In Outlook, it's View > View Source or the message details pane. Find the line starting with DKIM-Signature: and note three values:
DKIM-Signature: v=1; a=rsa-sha256; d=yourdomain.com; s=default;
bh=aGVsbG8gd29ybGQ=; h=from:to:subject:date; b=...
d= is the signing domain, s= is the selector, bh= is the body hash that was calculated at signing time.
2. Confirm the selector actually has a matching TXT record
dig TXT default._domainkey.yourdomain.com +short
Swap default for whatever s= showed you. If this comes back empty, that's your whole problem — the message is signed with a selector that was never published, usually left over from a provider switch. Go to cPanel's Email Deliverability page and republish the correct key for the selector that's actually in use, or update the mail server config to sign with the selector that is published.
3. Check whether something is rewriting the body
If the selector and key check out but you're still getting body hash did not verify, something downstream is editing the message after it's signed. Test the theory: send the same message two ways — once direct from the server (via swaks or plain sendmail), once through whatever path is failing (a mailing list, a forwarding rule, an SMTP plugin). If direct passes and the other fails, you've found it.
swaks --to test@mail-tester.com --from you@yourdomain.com \
--server localhost --header "Subject: DKIM test" \
--body "plain test body"
Common fixes at this stage:
- Mailing list / newsletter software: configure it to rewrite the From address to its own domain instead of yours (this is what DMARC's "From rewriting" pattern exists for) rather than passing your signed mail through untouched-but-modified.
- WordPress SMTP plugins (WP Mail SMTP, Fluent SMTP): make sure only one thing is signing outbound mail. If the plugin relays through an API (SendGrid, Mailgun) and cPanel's Exim is also trying to sign it locally first, you get two competing signatures. Let the relay provider handle DKIM entirely and don't double-sign.
- Auto-responders / forwarding: a true SMTP forward (not a mailbox alias) almost always breaks DKIM because the forwarding server becomes a new hop. This is expected and mostly harmless if SPF and DMARC are configured with forwarding in mind — ARC headers from major providers exist specifically to handle this case.
4. Rule out dual delivery
If you migrated from cPanel mail to Google Workspace (or vice versa) and both are still configured to accept outbound mail for the domain, you can end up with two different signing paths active at once. Check Exim's configuration or your outbound relay settings and make sure only the provider your MX record actually points to is signing and sending mail.
5. Give key rotations time to clear caches
If you just rotated the DKIM key, the failure might simply be a stale DNS cache at the receiving end. TTLs on DKIM TXT records are typically 3600 seconds or more — wait it out, or lower the TTL a day before you plan to rotate keys next time.
Prevention: Keep One Signer, One Selector, and a Clean Path
- Pick exactly one system to sign outbound mail per sending path. If you use both cPanel mail and a third-party relay, use separate selectors (
default._domainkeyvsrelay._domainkey) so each signature can be verified independently instead of overlapping. - Avoid anything that appends content to the body after signing — disclaimers, banners, or "sent from my phone" injectors should run before the message hits the signing MTA, not after.
- Watch your DMARC aggregate reports (the
rua=address from your_dmarcrecord). A pattern ofdkim=failfrom your own known sending IPs is the earliest signal something in the pipeline is rewriting mail. - Re-test with mail-tester.com or
check-auth@verifier.port25.comafter any migration, plugin change, or new mailing-list integration — not just after DNS changes.
Frequently Asked Questions
My DKIM record looks identical to what cPanel shows me. Why does it still fail?
A record matching what cPanel displays only proves the key is published correctly — it says nothing about whether the message body or headers were altered after signing. Pull the raw DKIM-Signature header from a failed message and compare its selector and body hash logic against what actually left your server.
Does forwarding email always break DKIM?
Traditional SMTP forwarding (not a simple mailbox alias) almost always invalidates the original DKIM signature because the forwarding server becomes a new hop and often adds headers. This is a known limitation, which is why ARC (Authenticated Received Chain) exists — Gmail and Outlook use it to preserve trust through a forwarding hop even when the original DKIM signature no longer verifies.
Can I have two DKIM records for the same domain?
Yes, as long as they use different selectors — for example default._domainkey for cPanel mail and google._domainkey for Google Workspace. Each outbound source should sign with its own selector so the receiver checks against the right key rather than a mismatched one.
How do I know which selector a specific email actually used?
Open the raw message headers and look at the s= field inside the DKIM-Signature line. Run dig TXT <selector>._domainkey.yourdomain.com +short using that exact value to confirm a matching record is published.
I rotated my DKIM key and now everything fails. What happened?
Either the new key wasn't fully propagated when mail started using it, or the mail server started signing with the new key before the DNS change had time to clear caches at receiving servers. Wait out the TXT record's TTL, confirm with dig from an external resolver, and re-test.
