DKIM Key Rotation: Why, When, and How to Do It Right
DKIM signs every outbound message to prove it came from you. But the private key doing that signing is a consumable, like an SSL certificate. It needs replacing. The question is how to swap it out without breaking deliverability or triggering a Friday-evening incident.
Why rotate DKIM keys at all
Short answer: keys get old. The longer answer has three parts.
Cryptographic exposure over time. A private key sitting on a server for months accumulates risk. It may leak through a compromised host, end up in a backup someone copied to a USB drive, or surface in deployment logs. The longer a key lives, the more chances someone unauthorized has to obtain it. A stolen private key lets an attacker sign mail as your domain, and DMARC will pass it as legitimate.
Vendor guidance. Google's Postmaster Tools documentation recommends rotating DKIM keys at least annually. M3AAWG (Messaging, Malware and Mobile Anti-Abuse Working Group) puts it at every six months in their best practices document. Some ESPs — Mailgun, Postmark — rotate customer keys automatically each quarter.
Upgrading to longer keys. If you are still running a 1024-bit RSA key, rotation is the natural moment to move to 2048 bits. Google has penalized short keys since 2023. RFC 8463 also added Ed25519 support for DKIM — an elliptic-curve algorithm with a much smaller key footprint than RSA. Ed25519 is not universally supported yet, but the direction is clear.
When to rotate
Two scenarios: scheduled rotation every 6–12 months, and emergency rotation when you suspect a compromise.
Rotate immediately if any of these apply:
- The server holding your private key was breached.
- You spotted DKIM-signed messages you did not send.
- An admin with key access left the organization and you cannot confirm they did not keep a copy.
- Your key is 1024 bits or shorter and you are seeing warnings in DMARC aggregate reports.
For scheduled rotation, pick a low-traffic window. Tuesday or Wednesday morning works. Not Friday, not a weekend, not the day before a big send.
The mechanics: two selectors, zero downtime
DKIM uses selectors — arbitrary strings that tell the receiving server which public key to pull from DNS. The selector appears in the s= tag of every signed message header. The receiving server fetches the key from selector._domainkey.yourdomain.com.
Rotation uses this mechanism. The sequence:
- Generate a new key pair with a new selector.
- Publish the new public key in DNS. Wait for propagation.
- Switch your mail server to sign with the new key and new selector.
- Leave the old DNS record in place for 48–72 hours (messages in retry queues are still signed with the old key).
- Delete the old DNS record. Destroy the old private key.
The old and new public keys coexist in DNS throughout the transition. Receiving servers always verify against whichever selector is in the message header, so there is no conflict and no gap in authentication.
Step 1. Generate a new key
RSA 2048-bit is the current standard. Generate with openssl:
# Generate a 2048-bit RSA private key openssl genrsa -out dkim_s2_private.pem 2048 # Extract the public key openssl rsa -in dkim_s2_private.pem -pubout -out dkim_s2_public.pem # Public key on one line (ready for the DNS TXT record) grep -v "^-" dkim_s2_public.pem | tr -d '\n'
The filename includes s2 — the new selector name. If your current selector is s1, the new one becomes s2. Next cycle you can go back to s1 (once the old record is gone) or increment to s3. Many teams use date-based names like dkim202604 — easier to audit at a glance.
If you want to try Ed25519 (RFC 8463):
# Ed25519 — compact key, fast signature openssl genpkey -algorithm Ed25519 -out dkim_ed_private.pem openssl pkey -in dkim_ed_private.pem -pubout -out dkim_ed_public.pem
Ed25519 produces a public key of 44 base64 characters instead of 392 for RSA-2048, so the DNS record stays under the 255-character per-string limit without splitting. The catch: Gmail and Yahoo support Ed25519, but Exchange and some corporate gateways do not. The safest approach is to publish both, using RSA as the primary signing key and Ed25519 as a secondary.
Step 2. Publish the new key in DNS
Add a TXT record for the new selector. Leave the old one alone for now.
; Old key (keep until after the switch) s1._domainkey.yourdomain.com. IN TXT "v=DKIM1; k=rsa; p=MIIBIjAN...old_key..." ; New key s2._domainkey.yourdomain.com. IN TXT "v=DKIM1; k=rsa; p=MIIBIjAN...new_key..."
Verify propagation before switching:
# Confirm the new record is visible dig TXT s2._domainkey.yourdomain.com +short # Confirm the old record is still there dig TXT s1._domainkey.yourdomain.com +short
On Cloudflare, propagation takes minutes. Other providers can take 15 minutes to several hours depending on TTL. If your TTL is currently at 3600 seconds, lower it to 300 a few days before rotation so caches flush quickly. Do not flip the server to the new key until dig returns the new record — otherwise you sign with a selector that receiving servers cannot resolve, and DKIM fails.
Step 3. Switch the server
The steps depend on your stack.
Postfix + OpenDKIM. Update the selector and key path in your OpenDKIM config:
# /etc/opendkim.conf Selector s2 KeyFile /etc/opendkim/keys/dkim_s2_private.pem
# Restart sudo systemctl restart opendkim sudo systemctl restart postfix
External ESP (Mailgun, SendGrid, Postmark). Most ESPs handle keys through their dashboard or API. In Mailgun it is under Sending → Domain settings → DKIM. In SendGrid: Settings → Sender Authentication. Create the new key, copy the DNS record they give you, add it to your DNS, then verify in the ESP panel. Several ESPs rotate keys automatically and notify you by email a week before the change.
Verify the switch. Send a test message to Gmail. Open the original headers. You should see the new selector:
DKIM-Signature: v=1; a=rsa-sha256; d=yourdomain.com; s=s2;
h=from:to:subject:date:message-id;
bh=...;
b=...Authentication-Results: mx.google.com;
dkim=pass header.i=@yourdomain.com header.s=s2See s=s2 and dkim=pass? Rotation done.
Step 4. Remove the old key
Wait. Messages sent before the switch can sit in receiving server retry queues for up to 72 hours. Some corporate gateways re-verify DKIM on redelivery. If the old selector is already gone from DNS by then, those messages fail authentication.
Wait 48–72 hours after the switch, then remove the old TXT record from DNS. For the private key file, use shred rather than rm:
# Securely delete the old private key shred -u /etc/opendkim/keys/dkim_s1_private.pem
Automating rotation
Manual rotation every six months is fine in theory. In practice, people forget. Skip it once and the key runs a year. Skip it again and it is two years. Automation removes the dependency on someone remembering.
A minimal rotation script for Postfix + OpenDKIM + Cloudflare DNS:
#!/bin/bash
# rotate-dkim.sh — run via cron every 6 months
DOMAIN="yourdomain.com"
NEW_SELECTOR="dkim$(date +%Y%m)"
KEY_DIR="/etc/opendkim/keys"
# 1. Generate new key
openssl genrsa -out "$KEY_DIR/${NEW_SELECTOR}_private.pem" 2048
openssl rsa -in "$KEY_DIR/${NEW_SELECTOR}_private.pem" \
-pubout -out "$KEY_DIR/${NEW_SELECTOR}_public.pem"
chown opendkim:opendkim "$KEY_DIR/${NEW_SELECTOR}_private.pem"
chmod 600 "$KEY_DIR/${NEW_SELECTOR}_private.pem"
# 2. Extract public key for DNS
PUBKEY=$(grep -v "^-" "$KEY_DIR/${NEW_SELECTOR}_public.pem" | tr -d '\n')
# 3. Add DNS record via Cloudflare API
curl -s -X POST \
"https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
--data "{
\"type\": \"TXT\",
\"name\": \"${NEW_SELECTOR}._domainkey.${DOMAIN}\",
\"content\": \"v=DKIM1; k=rsa; p=${PUBKEY}\",
\"ttl\": 300
}"
# 4. Wait for propagation
sleep 120
# 5. Switch OpenDKIM to new selector
sed -i "s/^Selector.*/Selector ${NEW_SELECTOR}/" /etc/opendkim.conf
sed -i "s|^KeyFile.*|KeyFile $KEY_DIR/${NEW_SELECTOR}_private.pem|" \
/etc/opendkim.conf
systemctl restart opendkim
systemctl restart postfix
echo "DKIM rotated to selector: ${NEW_SELECTOR}"This script is intentionally bare. Production versions should add a dig check before switching, send alerts to Slack or Telegram on failure, and log every step. Old-key cleanup belongs in a separate script triggered 72 hours later.
For CI/CD pipelines: store private keys in a secrets manager — Vault, AWS Secrets Manager, or GitHub Secrets — never in the repository. Terraform and Pulumi can manage DNS records natively, so rotation becomes part of your infrastructure code and gets the same review process as everything else.
Mistakes that break rotation
- Deleting the old DNS record before switching the server. The server keeps signing with selector
s1, but thes1record is gone. Every message fails DKIM. Withp=rejectin DMARC, they bounce. This is the most common mistake in manual rotation. - Switching the server before DNS propagates. The new record is not visible to receiving servers yet. DKIM will also fail. Particularly painful when your DNS provider has a TTL of 3600 seconds or higher.
- Forgetting subdomains. If marketing goes out from
mail.yourdomain.comand transactional fromnotify.yourdomain.com, each has its own DKIM key. All of them need rotating. - One selector across multiple domains. If three domains all sign with selector
s1, you need to update DNS for all three simultaneously. Using distinct selectors per domain avoids this coordination problem. - Not destroying the old private key. Rotation without destruction is half-measures. A stolen old key cannot sign new mail, but an attacker who intercepts a message in transit can forge the signature retroactively.
Monitoring after rotation
The first 24 hours after switching are the ones to watch. Keep an eye on:
- DMARC aggregate reports. If you have
rua=configured, the next day's reports will show whether messages are passing DKIM with the new selector. Tools like dmarcian, Postmark DMARC, or DMARC Analyzer visualize this. - Bounce rate. A sudden spike in hard bounces after the switch is a signal that something went wrong.
- Server logs. OpenDKIM writes to syslog. Look for lines containing
DKIM-Signatureand confirm signatures are being created without errors.
# Check OpenDKIM logs for the past hour journalctl -u opendkim --since "1 hour ago" | grep -i "error\|fail"
Rotation checklist
Steps in order:
- Generate a new key pair (RSA-2048 or Ed25519).
- Choose a new selector (
s2,dkim202604). - Publish the new public key in DNS.
- Verify:
dig TXT newSelector._domainkey.yourdomain.com. - Switch the server or ESP to the new selector and key.
- Send a test message and check headers:
dkim=pass. - Wait 48–72 hours.
- Delete the old DNS TXT record.
- Destroy the old private key (
shred -u). - Log the rotation date and schedule the next one in six months.
How rotation connects to deliverability
Rotation does not directly improve inbox placement. Mailbox providers do not give credit for frequent key changes. The impact is indirect but real.
A compromised key means phishing mail with your signature. Recipients report those messages as spam. Your domain reputation drops. Legitimate mail starts missing inboxes. Recovery can take weeks. Regular rotation is cheaper than that remediation.
DKIM is one layer, though. Authentication proves origin; it does not guarantee list quality. A signed message sent to a non-existent address is still a bounce. A signed message that hits a spam trap still hurts your reputation. Authentication and list hygiene work together.
Keys rotate. Lists decay too.
DKIM is in order, keys are rotating, DMARC is on reject. The technical side is solid. What about the addresses you are sending to? A list loses roughly 25% of valid contacts per year. Old addresses turn into bounces, spam traps, or catch-alls.
Upload your list to uChecker and see invalid addresses, spam traps, and risky contacts in minutes. 30 free checks to get started.
