DKIM Body Hash Did Not Verify: 5 Root Causes [Solved]
Fix DKIM body hash verification failures by comparing canonicalization configs, footer rewrites, and key rotation errors with step-by-step tests.

On this page
TL;DR — Key takeaways
- Body hash failures occur when the email body changes after DKIM signing—typically from mailing list footers, forwarding services, or content filters.
- Canonicalization setting mismatches (simple vs relaxed) cause verification failures even when message content is unchanged.
- Key rotation without propagating the new public DNS record creates a race condition where recipients verify with the old selector.
- Testing with command-line tools like opendkim-testmsg reveals the exact line where body modification occurred.
- Switching from simple/simple to relaxed/relaxed canonicalization prevents most whitespace and encoding-related failures.
You check the bounce report and see dkim=fail with a body hash mismatch. The signature validated yesterday. Nothing changed in your mail server config. But recipients now reject your messages or flag them as spam.
Body hash verification ties the DKIM signature to the exact content of the email body. When that hash breaks, you lose sender reputation fast. The failure usually points to one of five root causes: canonicalization mode conflicts, content rewriting by intermediaries, stale DNS records after key rotation, clock skew breaking timestamp windows, or software bugs in signing libraries. This guide compares each scenario, shows you how to isolate the culprit, and recommends fixes based on your mail flow architecture.
What the Body Hash Actually Verifies
DKIM signs two things: selected headers and the message body. The body hash is a SHA-256 digest of the email body after canonicalization. That hash goes into the bh= tag of the DKIM-Signature header. When the receiving MTA verifies the signature, it computes its own body hash from the received message and compares it to the signed value.
A mismatch means one of two things. Either the body was altered in transit, or the canonicalization algorithm applied during verification differs from the one used during signing. The c= tag in the DKIM-Signature header specifies two modes: one for headers, one for the body. You'll see c=relaxed/relaxed or c=simple/simple.
Simple canonicalization is fragile. It treats the body byte-for-byte, so a single added space or line break invalidates the hash. Relaxed canonicalization collapses whitespace, ignores trailing spaces, and normalizes line endings. Most production systems use relaxed/relaxed because mail servers and content filters routinely tweak formatting.
Canonicalization Mismatches: Simple vs Relaxed
Check your current mode by inspecting the c= tag in a sent message's DKIM-Signature header. If you see c=simple/simple and you route mail through Google Groups, Mailman, or any forwarding service, switch to relaxed/relaxed. For OpenDKIM, set Canonicalization relaxed/relaxed in opendkim.conf. For Postfix with DKIM milter integration, the setting lives in the milter config file, not main.cf.
- simple/simple: Fastest to compute, but fails if any relay touches whitespace or line endings. Use only for direct server-to-server delivery with no intermediaries.
- relaxed/simple: Tolerates header rewriting but still fragile for body changes. Rare in production; offers no practical benefit over relaxed/relaxed.
- simple/relaxed: Protects header integrity byte-for-byte while allowing body whitespace normalization. Useful when you control the body but not header-rewriting proxies.
- relaxed/relaxed: Standard choice for production. Survives mailing list software, forwarding services, and most content filters. Slightly higher CPU cost, but negligible on modern hardware.
Content Rewriting by Mailing Lists and Forwarders
Test by sending a message to an address you control, then manually forwarding it to a third address. Compare the DKIM verification result for the direct delivery versus the forwarded copy. If the first verifies and the second fails, body rewriting is happening at the forward.
- Mailing list footers: Mailman, Listserv, and Google Groups add unsubscribe links and list info to the body. These additions happen after DKIM signing. Use ARC (Authenticated Received Chain) to preserve the original signature through the list server.
- Forwarding services: Gmail, Outlook, and hosting control panels often offer forwarding. Some rewrite message bodies to add forwarding notices. If you control the forwarder, disable body modifications. If not, rely on SPF alignment for DMARC pass.
- Content filters: Spam filters and DLP gateways may strip attachments, convert HTML to plain text, or remove scripts. Sign after content filtering runs, not before. Place DKIM signing as the last step in your outbound mail pipeline.
- Automated signatures: CRM tools and ticketing systems append agent signatures or tracking pixels. These count as body modifications. Sign at the application layer after appending signatures, or use subdomain keys per application.
Key Rotation and DNS Propagation Timing
A key rotation mistake I've seen: updating the private key on the mail server and the DNS record at the same time, then restarting. For the next hour, half the recipients got the old DNS record and verification failed. The fix is patience. Publish first, wait, then switch.
- Check DNS TTL: dig TXT default._domainkey.yourdomain.com and note the TTL value in the response. Your propagation window is TTL plus resolver cache behavior.
- Test with multiple resolvers: Query 8.8.8.8, 1.1.1.1, and your ISP's resolver. If results differ, DNS propagation is incomplete.
- Rotate during low-traffic windows: Fewer in-flight messages means fewer verification failures during the overlap period.
- Monitor Authentication-Results headers: Grep mail logs for dkim=fail reason='key not found' to detect premature key switches.
Clock Skew and Timestamp Window Failures
DKIM signatures include a timestamp (t=) and optional expiration (x=). If the signing server's clock is wrong, the signature may appear to be from the future or already expired when it reaches the recipient. Most validators allow a small clock skew window—typically 300 seconds—but larger drifts cause verification failure.
Body hash errors and timestamp errors present differently in Authentication-Results headers. A timestamp problem shows dkim=policy or dkim=permerror with reason='signature expired'. A body hash problem shows dkim=fail with reason='body hash did not verify'. But in practice, mismatched server clocks sometimes trigger cryptographic edge cases in signing libraries that surface as body hash errors.
Check your mail server's clock with timedatectl or date -u. Compare it to an NTP pool server. If the drift exceeds 60 seconds, configure NTP or chrony. Most cloud instance types sync time automatically, but bare metal and on-premises VMs need explicit NTP setup.
Isolating Failures with Command-Line Verification
For bulk analysis, parse mail logs for Authentication-Results headers and count dkim=fail by reason code. Patterns emerge. If 90% of failures come from one recipient domain, their verification settings are overly strict. If failures are evenly distributed, your signing config is the problem.
- Extract the bh= value from the DKIM-Signature header. It's base64-encoded.
- Decode it: echo 'bh_value' | base64 -d | xxd
- Canonicalize the body per the c= setting (manual whitespace collapsing for relaxed).
- Compute the hash: openssl dgst -sha256 -binary canonicalized_body.txt | base64
- Compare the computed hash to the bh= value. If they differ, the body was modified.
Recommended Fixes by Scenario
Backup plan: if DKIM body hash failures persist despite fixes, ensure SPF and DMARC alignment cover you. A DMARC policy of p=quarantine with pct=10 lets you test impact before enforcing. Alignment on either SPF or DKIM is sufficient for DMARC pass, so one broken authentication method won't crater your deliverability.
- Canonicalization: relaxed/relaxed for production, simple/simple only for isolated direct-send systems.
- Key rotation: overlap keys for at least 2x DNS TTL; monitor logs for 'key not found' errors before removing the old key.
- Content filters: sign after all body modifications; test by sending through your entire outbound pipeline.
- Mailing lists: implement ARC; configure list software to sign with its own domain key after rewriting.
- Monitoring: parse Authentication-Results headers from major providers; track dkim=fail rate as a percentage of sent mail.
Quick troubleshooting checklist
- Check Authentication-Results headers for specific body hash error codes
- Verify DNS TXT record matches the private key selector in use
- Test canonicalization mode against your content filters and forwarding chain
- Scan outbound messages for auto-appended footers or disclaimers
- Run opendkim-testmsg on a failed message to isolate the modification point
- Compare signed body hash with computed hash from received message
- Document your key rotation schedule and DNS TTL settings
- Set up SPF and DMARC alignment as fallback authentication layers
FAQ
What does 'DKIM body hash did not verify' mean?
The error means the cryptographic hash of the email body computed by the receiving server does not match the hash value in the DKIM-Signature header. This happens when the message body was modified after signing—by a mailing list, forwarder, or content filter—or when canonicalization settings differ between sender and receiver.
Can email forwarding cause DKIM body hash failures?
Yes. Many forwarding services and mailing lists rewrite message bodies by adding footers, stripping attachments, or converting line endings. These changes invalidate the body hash. Use relaxed canonicalization and avoid signing with simple/simple if your messages pass through forwarding chains.
How do I test DKIM signing before sending production email?
Send a test message to a Gmail or Outlook address you control, view the full headers, and check the Authentication-Results line. For offline testing, use opendkim-testmsg with a saved .eml file. Verify the body hash manually with openssl dgst -sha256 on the canonicalized body.
Related articles
- Hosting OperationsSelf-Hosted App Deployment Fails? Check DNS, SSL, Reverse Proxy, and Logs FirstTroubleshoot failed self-hosted app deployments by checking DNS, SSL, reverse proxy routing, container status, logs, and ports.
- Hosting OperationsSelf-Hosted PaaS on a VPS: What to Check Before Installing Coolify, Dokploy, or CapRoverA hosting support checklist for preparing a VPS before installing self-hosted PaaS tools like Coolify, Dokploy, or CapRover.
- Hosting OperationsLinux Server Security Lessons from the Arch Linux Malware Package IncidentPractical Linux server security checklist for VPS admins after package malware concerns, with safe checks, rollback steps, and support guidance.
- Hosting OperationsAWS Lightsail Hong Kong VPS Latency: Practical Hosting Guide for IndonesiaLearn how to test AWS Lightsail Hong Kong VPS latency, compare regions, migrate safely, and troubleshoot hosting performance.
- Hosting OperationsCloudflare Tomorrow Watchlist: A Practical Hosting Operations GuidePractical Cloudflare troubleshooting checklist for DNS, SSL, caching, WAF, origin health, safe testing, and rollback planning.
- Hosting OperationsNetwork Safety Checklist for AI Agent Skills in Hosting OperationsAudit AI agent skills safely with network checks, secret protection, sandbox testing, rollback steps, and hosting support troubleshooting guidance.