Introduction
DKIM (DomainKeys Identified Mail) adds a cryptographic signature to outgoing email that recipients can verify against a public key stored in DNS. When the DKIM DNS record is wrong, missing, or contains a malformed key, receiving mail servers cannot verify the signature. The result is email sent to spam folders or rejected outright, often with no bounce message to alert the sender.
Symptoms
Email fails DKIM verification checks, landing in spam or being rejected:
``` # DKIM verification fails in received headers Authentication-Results: mx.google.com; dkim=fail header.i=@example.com header.b=XXXXXXX
# Bounce messages 550 5.7.1 DKIM verification failed
# DKIM lookup returns nothing dig default._domainkey.example.com TXT +short # (empty)
# Online DKIM checkers report errors Error: No DKIM record found for selector "default" at default._domainkey.example.com Warning: DKIM public key is malformed Error: DKIM key is too small (1024-bit minimum recommended) ```
Checking mail logs shows DKIM failures:
postfix/smtp[12345]: warning: DKIM verification failed
opendkim[1234]: s=default d=example.com; bad signature
opendkim[1234]: key retrieval failed (s=default, d=example.com)Common Causes
- 1.DKIM record never created - Mail server signing emails but no DNS record published
- 2.Wrong selector name - Mail server using "selector1" but DNS has "default" record
- 3.Selector record deleted - Key rotation removed old record without adding new one
- 4.Malformed public key - Syntax errors, line breaks, or encoding issues
- 5.Key too small - 512-bit or 1024-bit keys rejected by major providers
- 6.TXT record too long - Split record not reassembled correctly by some resolvers
- 7.CNAME pointing to wrong location - Third-party DKIM service misconfigured
Step-by-Step Fix
Step 1: Identify the DKIM Selector
The selector is specified in the DKIM-Signature header of sent emails:
```bash # Check a sent email for the DKIM signature header grep -i "DKIM-Signature" /var/log/mail.log
# Or examine the raw email headers # DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com; # s=default; h=...
# The 's=' tag indicates the selector # s=default means look for: default._domainkey.example.com # s=selector1 means look for: selector1._domainkey.example.com ```
Query the DKIM record for that selector:
```bash # Query for the specific selector dig default._domainkey.example.com TXT +short dig selector1._domainkey.example.com TXT +short dig google._domainkey.example.com TXT +short
# Check authoritative nameserver dig @ns1.example.com default._domainkey.example.com TXT ```
Step 2: Verify the Public Key
If the record exists, verify its format:
```bash # Get the DKIM record dig default._domainkey.example.com TXT +short
# Expected format: "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC..."
# Verify key can be extracted and examined dig default._domainkey.example.com TXT +short | sed 's/"//g' | sed 's/.*p=//' > /tmp/publickey.pem echo "-----BEGIN PUBLIC KEY-----" > /tmp/dkim_pub.pem cat /tmp/publickey.pem >> /tmp/dkim_pub.pem echo "-----END PUBLIC KEY-----" >> /tmp/dkim_pub.pem openssl rsa -in /tmp/dkim_pub.pem -pubin -text -noout ```
Step 3: Check Mail Server DKIM Configuration
Verify the mail server is actually signing with the correct selector:
```bash # Postfix with OpenDKIM cat /etc/opendkim.conf | grep -i selector # Selector default # KeyTable /etc/opendkim/key.table
# Check key table cat /etc/opendkim/key.table default._domainkey.example.com example.com:default:/etc/opendkim/keys/example.com/default.private
# Verify private key exists ls -la /etc/opendkim/keys/example.com/default.private
# Extract public key from private key openssl rsa -in /etc/opendkim/keys/example.com/default.private -pubout -out /tmp/public.key ```
```bash # For Exim grep -i dkim /etc/exim4/exim4.conf.template
# For Sendmail with milter grep -i InputMailFilters /etc/mail/sendmail.mc ```
Step 4: Generate or Regenerate DKIM Keys
Generate new DKIM keys if needed:
```bash # Create keys directory sudo mkdir -p /etc/opendkim/keys/example.com
# Generate 2048-bit key (minimum recommended) sudo opendkim-genkey -s default -d example.com -b 2048 \ -D /etc/opendkim/keys/example.com
# This creates: # /etc/opendkim/keys/example.com/default.private (private key) # /etc/opendkim/keys/example.com/default.txt (DNS record ready to use)
# View the DNS record cat /etc/opendkim/keys/example.com/default.txt ```
For manual key generation:
```bash # Generate private key openssl genrsa -out default.private 2048
# Extract public key openssl rsa -in default.private -pubout -out default.public
# Convert to single-line format for DNS grep -v "^--" default.public | tr -d '\n' ```
Step 5: Create the DKIM DNS Record
For BIND zone files:
```bash # Edit zone file sudo vi /etc/bind/db.example.com
# Add DKIM record - the format from opendkim-genkey output default._domainkey.example.com. IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5Y..."
# For long keys (2048-bit), may need to split: default._domainkey.example.com. IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5Y..." "qweR..."
# Increment SOA serial and reload named-checkzone example.com /etc/bind/db.example.com sudo rndc reload example.com ```
For Cloudflare:
curl -X POST "https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records" \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
--data '{
"type": "TXT",
"name": "default._domainkey",
"content": "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5Y...",
"ttl": 3600
}'For Microsoft 365 (using admin center):
# Microsoft provides selector names like selector1 and selector2
# Get the CNAME values from M365 admin center
Add-DnsServerResourceRecord -CName -Name "selector1._domainkey" `
-ZoneName "example.com" -HostNameAlias "selector1-example-com._domainkey.example.onmicrosoft.com"Step 6: Handle Long DKIM Records
2048-bit keys create long TXT records that may need special handling:
```bash # Check record length dig default._domainkey.example.com TXT +short | wc -c
# If over 255 characters, split in zone file: default._domainkey.example.com. IN TXT ( "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC" "5YqLqJzqZvGJkLQzNjMZcXeRHxJKvJFZMhTzGfKPzZvMHxL" "qjzLJzQxGfKPzZvMHxLqjzLJzQxGfKPzZvMHxLqjzLJzQxGfKPz" )
# Some providers require concatenated string: # "v=DKIM1; k=rsa; p=ABCD...EFGH...IJKL" ```
Step 7: Verify DKIM End-to-End
Test the DKIM record from external resolvers:
dig @8.8.8.8 default._domainkey.example.com TXT +short
dig @1.1.1.1 default._domainkey.example.com TXT +shortSend test email and check verification:
```bash # Send test email echo "DKIM test body" | mail -s "DKIM Test" recipient@gmail.com
# In Gmail, show original message and look for: # Authentication-Results: mx.google.com; # dkim=pass header.i=@example.com ```
Use online DKIM validators:
# Test with dkimvalidator.com, mxtoolbox.com, or similar
# Send email to auto-dkim@dkimvalidator.com for analysisCheck OpenDKIM is running correctly:
```bash sudo systemctl status opendkim sudo journalctl -u opendkim -n 50
# Test signing manually opendkim-testkey -d example.com -s default -vvv ```
Common Pitfalls
- Using wrong selector - Mail server configured for "default" but DNS has "selector1"
- Forgetting to restart mail server - New key not loaded after configuration change
- Publishing private key - Accidentally put private key in DNS instead of public
- Key encoding issues - Line breaks or spaces breaking the key format
- Multiple DKIM records for same selector - Creates ambiguity
- Not testing from external DNS - Internal DNS shows correct record but not propagated
Best Practices
- Use 2048-bit keys minimum; 1024-bit is considered weak
- Rotate DKIM keys annually or after any security incident
- Use descriptive selector names that indicate key version (e.g., "20240101")
- Test DKIM after any DNS or mail server change
- Monitor DMARC reports for DKIM failures
- Keep a backup of private keys in secure storage
Related Issues
- DNS DMARC Record Error
- DNS SPF Record Configuration
- Email Deliverability Problems
- DNS TXT Record Issues