DKIM failures

How to diagnose and fix DKIM verification failures. Covers body hash mismatch, expired signatures, missing selectors, weak keys, and safe key rotation procedures.

Body hash did not verify (bh= mismatch)

This is the most common DKIM failure. It means the receiving mail server hashed the message body and the result did not match the bh= value stored in the DKIM-Signature header. The signature was computed on a different body than what arrived.

You will see this in the Authentication-Results header of the received message:

Authentication-Results: mx.receiver.com;
  dkim=fail (body hash did not verify) header.d=example.com header.s=s1

In your DMARC aggregate reports, this appears as a DKIM fail result for the affected sending source. If DKIM is your only aligned authentication mechanism, the message will also fail DMARC.

Common causes

The body hash fails when something modifies the message body after the sending server signed it. The most frequent culprits are:

  • Footer injection. Mailing lists (Mailman, Google Groups, Listserv) and corporate email systems that append disclaimers or unsubscribe links to outbound messages. The appended text changes the body hash.
  • Link rewriting. Security gateways (Proofpoint URL Defense, Mimecast, Barracuda) that rewrite URLs in the message body for click-time scanning. Every rewritten link changes the body.
  • Content scanning appliances. Antivirus and DLP gateways that decode, inspect, and re-encode MIME parts. Even normalizing line endings or character encoding will break the hash.
  • HTML reformatting. Some intermediate servers reformat HTML content, strip tags, or adjust character sets. Any byte-level change invalidates the signature.

How to diagnose

First, confirm the failure by examining the full message headers on the receiving side. Look for the Authentication-Results header. Then compare the bh= value in the DKIM-Signature header against a manual hash of the received body.

If you have access to the original sent message and the received message, diff the body content. The modification is usually obvious: an appended footer, rewritten URLs, or a changed Content-Transfer-Encoding.

How to fix

  • Mailing lists: Configure the list to not modify the message body, or have the list re-sign with its own DKIM key and publish a DMARC record for the list domain.
  • Security gateways: Move the DKIM signing step to after the gateway in your mail flow. If your gateway modifies outbound mail, it must be the last hop before the message leaves your infrastructure, or it must re-sign.
  • Corporate disclaimers: Add disclaimers before DKIM signing, not after. Most modern MTAs support this ordering.

Inspecting DKIM signatures

Before you can fix a DKIM failure, you need to read the signature. Here is a typical DKIM-Signature header and what each tag means:

DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
  d=example.com; s=selector1;
  h=from:to:subject:date:message-id;
  bh=abcdef1234567890abcdef1234567890abcdef12345=;
  b=MIIE... (truncated)
  • d= - The signing domain. This is what DMARC checks for alignment against the From header domain.
  • s= - The selector. Combined with d= to form the DNS lookup: selector1._domainkey.example.com.
  • a= - The signing algorithm. Usually rsa-sha256. Ed25519 (ed25519-sha256) is gaining support.
  • c= - Canonicalization method for header/body. relaxed/relaxed is the most forgiving. simple/simple requires exact byte-for-byte match.
  • h= - The list of headers included in the signature. If a header listed here is modified in transit, the signature breaks.
  • bh= - The Base64-encoded hash of the canonicalized message body.
  • b= - The actual signature value (Base64-encoded).

Retrieving the public key from DNS

Use dig to fetch the DKIM public key record:

dig TXT selector1._domainkey.example.com +short

A valid response looks like:

"v=DKIM1; k=rsa; p=MIIBIjANBgkqhki..."

If the response is NXDOMAIN or empty, the public key is not published. See the "key not found" section below.

Canonicalization: relaxed vs simple

The c= tag controls how the header and body are normalized before hashing. The two values (separated by a slash) apply to the header and body respectively.

  • simple - No normalization. The body must be byte-for-byte identical. Trailing whitespace, extra blank lines at the end, or any formatting change will break the hash.
  • relaxed - Reduces consecutive whitespace to a single space, converts header names to lowercase, and trims trailing whitespace. Much more tolerant of minor formatting changes in transit.

Always prefer c=relaxed/relaxed. The simple mode is fragile and breaks more often in real-world mail delivery where intermediate servers may normalize whitespace.

Verifying key publication

If you have opendkim-testkey installed (part of the OpenDKIM package), you can verify that a DKIM key is correctly published:

opendkim-testkey -d example.com -s selector1 -vvv

This will report whether the key was found, whether it parses correctly, and whether the key type matches. Online DKIM validators (such as the DMARCTrust domain checker) can also verify your DKIM DNS records.

DKIM signature expired (x= tag)

The optional x= tag in a DKIM-Signature header sets an expiration timestamp (Unix epoch). If the receiving server evaluates the signature after this time, it will reject it:

Authentication-Results: mx.receiver.com;
  dkim=fail (signature expired) header.d=example.com

This happens when messages sit in outbound queues longer than expected. A mail server might retry delivery for hours or days. If your x= window is too short, messages that were perfectly valid when sent will fail DKIM on delayed delivery.

When this matters

  • Outbound mail queues with aggressive retry schedules (some MTAs retry for up to 5 days)
  • Greylisting receivers that intentionally delay the first delivery attempt by 15 to 60 minutes
  • Messages forwarded through multiple hops with processing delays at each stage

How to fix

If you use the x= tag, set it generously. Seven days (604800 seconds) from the signing time is a reasonable minimum. Many organizations omit x= entirely, which means the signature never expires. This is a valid and common practice. The tradeoff is that a compromised key could be used to forge signatures indefinitely, but key rotation (covered below) mitigates that risk.

Key not found (selector mismatch)

When the receiver looks up s=selector._domainkey.d=domain in DNS and gets no result, you will see:

Authentication-Results: mx.receiver.com;
  dkim=fail (key not found) header.d=example.com header.s=s1

Common causes

  • Selector rotated, DNS not updated. You changed the signing selector in your MTA but forgot to publish the new public key in DNS.
  • Typo in the selector name. The s= value in the signature does not match the DNS record name. Even a single character difference causes a lookup failure.
  • Wrong domain in d= tag. The signing domain does not match where the DNS record is published. If your MTA signs with d=mail.example.com but the key is published under example.com, the lookup will fail.
  • DNS propagation delay. You published the record, but it has not propagated to all resolvers yet. Check with multiple DNS resolvers to confirm.
  • DNS record deleted accidentally. Someone cleaned up DNS and removed what looked like an unused TXT record.

How to diagnose

Extract the s= and d= values from the DKIM-Signature header, then query DNS directly:

# If the signature says s=s1 and d=example.com:
dig TXT s1._domainkey.example.com +short

# Check with a specific resolver to rule out propagation issues:
dig TXT s1._domainkey.example.com @8.8.8.8 +short
dig TXT s1._domainkey.example.com @1.1.1.1 +short

If all resolvers return NXDOMAIN, the record is genuinely missing. Publish it. If some resolvers return the record and others do not, wait for TTL-based propagation (usually under 24 hours, often under an hour).

How to fix

Publish the correct DKIM public key as a TXT record at selector._domainkey.yourdomain.com. The record value must include the v=DKIM1 version tag and the p= tag with the Base64-encoded public key. Example:

s1._domainkey.example.com. IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhki..."

If you use a third-party email service (Google Workspace, Microsoft 365, Mailchimp, SendGrid), follow their documentation to get the correct selector name and public key value. Each provider uses different selector naming conventions.

Key too short (RSA 1024-bit vs 2048-bit)

RSA 1024-bit DKIM keys are considered weak by modern standards. While RFC 6376 allows them, many receivers now flag or penalize messages signed with short keys:

  • Google warns about 1024-bit DKIM keys in Postmaster Tools and recommends 2048-bit.
  • Some security-focused receivers downgrade the trust level of 1024-bit signatures.
  • Future RFCs and industry practice are moving toward 2048-bit as the minimum for RSA DKIM keys.

How to check your key length

Retrieve the p= value from your DKIM DNS record and decode it. The length of the Base64-encoded public key gives a rough indication:

  • A 1024-bit RSA public key produces a p= value of approximately 216 Base64 characters.
  • A 2048-bit RSA public key produces a p= value of approximately 392 Base64 characters.

For a precise check, decode the Base64 and inspect the key:

# Extract the p= value and decode it:
echo "MIIBIjANBgkqhki..." | base64 -d | openssl rsa -pubin -inform DER -text -noout 2>/dev/null | head -1

This will output something like Public-Key: (2048 bit).

You can also use the DMARCTrust domain checker to inspect your DKIM key length for any published selector.

How to fix

Generate a new 2048-bit RSA keypair (or Ed25519 if your MTA and target receivers support it). Use the key rotation procedure described in the next section to avoid downtime. Do not simply replace the existing key in DNS, as messages signed with the old key that are still in transit will fail verification.

Note that 2048-bit RSA DKIM keys often exceed the 255-character limit of a single DNS TXT string. You will need to split the value across multiple quoted strings in the same TXT record. Most DNS providers handle this automatically, but verify the published record with dig after creation.

Key rotation without downtime

DKIM key rotation is necessary for security hygiene. Rotating keys periodically limits the exposure if a private key is compromised. The challenge is that messages signed with the old key may still be in transit when you switch. Follow this five-step process to rotate without breaking any signatures:

Step 1: generate a new keypair with a new selector

Create a new RSA 2048-bit (or Ed25519) keypair. Use a new selector name that is different from the current one. For example, if you currently use s1, generate keys for s2:

# Generate 2048-bit RSA keypair:
openssl genrsa -out dkim_s2_private.pem 2048
openssl rsa -in dkim_s2_private.pem -pubout -outform DER | base64 -w 0

The Base64 output is your p= value for the DNS record.

Step 2: publish the new public key in DNS

Add a TXT record for the new selector. Do not remove or modify the old selector record yet.

s2._domainkey.example.com. IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhki..."

Wait for DNS propagation. Verify the record is visible from multiple resolvers:

dig TXT s2._domainkey.example.com @8.8.8.8 +short
dig TXT s2._domainkey.example.com @1.1.1.1 +short

Step 3: configure your MTA to sign with the new selector

Update your MTA configuration to use the s2 selector and the new private key for all outgoing signatures. This is MTA-specific. For OpenDKIM, update KeyTable and SigningTable. For Postfix with OpenDKIM, restart the milter. For cloud email providers, update the selector in their admin console.

Step 4: keep the old public key in DNS

Messages signed with the old s1 selector may still be in mail queues, greylisting delays, or forwarding chains. Keep the old DNS record published for at least 7 days after switching. This gives enough time for virtually all in-flight messages to be delivered and verified.

Step 5: remove the old DNS record

After the grace period (7 days minimum, 14 days for extra safety), remove the old s1._domainkey TXT record from DNS. Monitor your DMARC aggregate reports for any remaining failures referencing the old selector. If you see failures, extend the grace period.

Prevention

Most DKIM failures are preventable with monitoring and good configuration defaults. Here is what to put in place:

Monitor DKIM results in DMARC aggregate reports

Your DMARC aggregate reports contain the DKIM authentication result for every message. A sudden increase in dkim=fail entries for a specific source IP or sending domain is the earliest signal that something has changed. DMARCTrust surfaces these failures in your dashboard and can alert you when failure rates spike.

Use relaxed canonicalization

Always sign with c=relaxed/relaxed unless you have a specific reason not to. This makes your signatures resilient to minor whitespace and formatting changes that occur naturally during message transport. The security difference between relaxed and simple canonicalization is negligible. The deliverability difference is significant.

Set up alerts for DKIM failure spikes

Do not wait for users to report delivery problems. Configure monitoring that triggers when the DKIM failure rate for any sending source exceeds a threshold (for example, more than 5% of messages failing DKIM in a 24-hour period). This catches selector mismatches, expired keys, and infrastructure changes before they affect a large volume of mail.

Test key rotation in staging

Before rotating DKIM keys in production, test the full procedure in a staging environment. Send test messages, verify DKIM signatures pass, confirm DNS records are visible, and validate the timing of each step. A botched key rotation in production means every outgoing message fails DKIM until you fix it.

Document your selectors

Maintain an internal record of which selectors are active, which keys they use, when they were last rotated, and which MTA or service uses each one. Organizations with multiple sending services (transactional email, marketing platforms, corporate mail) often have many active DKIM selectors. Without documentation, it is easy to accidentally delete a selector that is still in use.

Further reading

For a deeper understanding of DKIM and how it fits into the broader email authentication ecosystem, see the DKIM overview in our documentation. If your DKIM failures are causing DMARC alignment issues, the alignment troubleshooting guide covers how the d= domain in DKIM signatures must match the From header domain.

If messages are being forwarded or passing through mailing lists, the forwarding and ARC guide explains why forwarding breaks DKIM and how ARC (Authenticated Received Chain) can help preserve authentication results across intermediaries.

Was this page helpful? Send us feedback

Last updated: March 2026