← Back home

2026.07 / MAIL AUTHENTICATION 034

Rotate DKIM Keys in Docker Mailserver Without Breaking DMARC

Rotating a DKIM key should not mean replacing one DNS value and hoping cached resolvers catch up. The safe operation is an overlap: create a second selector, publish its public key, wait until it resolves authoritatively and publicly, switch Docker Mailserver to sign new messages with it, and leave the old selector available while earlier messages can still be verified.

This guide uses example.com, an old selector named mail, and a date-based replacement named dkim202607. They are placeholders. Do not publish a real private key, unredacted signing configuration, server address, API token, or local credential path while troubleshooting.

Publish and verify the new public key before the first message is signed with the new selector. Remove the old public key last, not first.

First: decide whether rotation is actually needed

DKIM keys do not have the short fixed lifetime of a TLS certificate. Docker Mailserver’s current documentation explicitly says that it does not automate rotation and that routine rotation may offer little benefit to a typical small deployment. Rotate deliberately when a private key may have been exposed, when replacing a weak legacy key, after a responsibility or hosting change, or when your own security policy requires it. A suspected compromise deserves an accelerated response; an arbitrary calendar date does not justify a rushed DNS cutover.

For RSA, Docker Mailserver currently generates 2048-bit keys by default. Google requires at least 1024 bits for mail sent to personal Gmail accounts and recommends 2048 bits where DNS supports it. Docker Mailserver discourages unnecessarily large 4096-bit RSA keys because compatibility and DNS response size matter. For a normal replacement, retain the documented 2048-bit default unless your pinned version and recipients justify another choice.

The no-downtime sequence

inventory current signer and selector
  → back up persisted mail configuration
  → generate a separate private/public key pair
  → publish new-selector._domainkey.example.com
  → verify the exact public record from several resolvers
  → configure the signer to use the new selector
  → restart or reload Docker Mailserver
  → prove a real outbound message passes DKIM and DMARC
  → keep the old selector during the overlap
  → retire the old DNS key only after the transition

Each arrow is an acceptance gate. If one fails, stop and roll back the signing configuration while both DNS selectors still exist.

1. Inventory the current signature and signer

Start with a recently delivered message from the domain. In its raw headers, locate DKIM-Signature and record these tags:

Also record the recipient’s Authentication-Results. A healthy baseline should show dkim=pass, with the DKIM d= domain aligned to the visible From: domain so DMARC can use that pass. DKIM can pass cryptographically yet fail DMARC alignment if it signs as an unrelated domain.

Next identify the implementation used by the pinned Docker Mailserver release. Modern DMS can sign through OpenDKIM or Rspamd; their file layouts and reload behaviour differ. Inspect non-secret settings and the version-specific CLI help rather than pasting the whole environment:

docker compose images mailserver
docker compose exec mailserver setup config dkim help

docker compose exec mailserver sh -lc \
  'postconf -m | sort; pgrep -a opendkim || true; pgrep -a rspamd || true'

The official DMS command setup config dkim generates keys with good defaults, but DMS documents that it does not provide a complete key-rotation workflow. Do not assume rerunning the default command creates a safe second selector; read the help for the exact image you run and preserve the current key.

2. Back up the persisted configuration

Before generating anything, take a file-level backup of the host directory mounted as /tmp/docker-mailserver/ or whatever config destination your Compose file uses. Include signing keys, OpenDKIM or Rspamd maps, account configuration, and the Compose manifest. Keep this backup encrypted and off the public repository.

# Render configuration for local review; do not paste secret output
 docker compose config

# Show mounts without dumping environment variables
 docker inspect mailserver \
   --format '{{range .Mounts}}{{println .Source " -> " .Destination}}{{end}}'

# Use your established encrypted backup process for the discovered config source
# and perform a restore rehearsal before treating that backup as valid.

The Docker Mailserver backup and restore runbook covers the wider recovery set. Rotation should be reversible at the configuration layer; it should never require deleting mail volumes.

3. Create a new selector without overwriting the old one

Choose a selector that is distinct and operationally clear. A date such as dkim202607 is more useful than calling every key mail. Generate the new pair using the mechanism supported by the exact DMS image and active signer. If that release’s helper cannot create an additional selector safely, follow the linked OpenDKIM or Rspamd configuration model instead of forcing the default generator.

The required end state is simple:

old private key: retained temporarily for rollback
new private key: readable only by the signing service
old public DNS:  mail._domainkey.example.com
new public DNS:  dkim202607._domainkey.example.com

Keep private-key permissions restrictive. Never copy private key material into a DNS panel: DNS receives only the public p= value. If the domain is one of several hosted by the same mail server, repeat the key and DNS work per signing domain; a selector name may be reused across domains, but each full DNS owner name is separate.

4. Publish the new public key before switching

Create a TXT record at the full selector owner name:

dkim202607._domainkey.example.com.  TXT  "v=DKIM1; k=rsa; p=<public-key-value>"

DNS dashboards differ. Some append the zone automatically, so the name field may need only dkim202607._domainkey. Some split a long TXT value into quoted character strings; DNS clients concatenate those strings. What matters is the resolved value, not how the dashboard visually wraps it.

Query the authoritative nameserver and independent public resolvers:

# Discover authoritative servers
dig +short NS example.com

# Ask one authoritative server directly
dig +short TXT dkim202607._domainkey.example.com @ns1.example-dns.invalid

# Compare public recursive answers
dig +short TXT dkim202607._domainkey.example.com @1.1.1.1
dig +short TXT dkim202607._domainkey.example.com @8.8.8.8

Replace the intentionally invalid nameserver placeholder with one returned by the first query. Check that there is one coherent DKIM record and that the public key exactly matches the generated public half. Do not switch signing merely because the DNS control panel says “saved.”

5. Switch only the active signing selector

After the new DNS record resolves, update the active signer so example.com uses dkim202607 and its matching private key. Preserve every other domain mapping. This is especially important on a multi-domain server: replacing an entire Rspamd domain map or OpenDKIM signing table can make the target domain work while silently stopping signatures for another domain.

Validate the edited configuration using the signer’s supported tools, then perform the documented reload or a controlled Compose restart:

docker compose restart mailserver
docker compose ps
docker compose logs --since 5m mailserver

Do not use docker compose down -v. DKIM rotation is a signing-configuration change, not a reason to remove persisted mail data. If startup or signing logs show a permission, selector, or key-path error, restore the previous map and selector while investigating.

6. Verify the message, not just DNS

Send a harmless new message through the same authenticated submission path used in production to an external mailbox you control. Inspect the raw message rather than trusting inbox placement alone. The acceptance evidence should include:

  1. A single expected DKIM-Signature with d=example.com and s=dkim202607.
  2. The recipient reports dkim=pass.
  3. DMARC reports pass, with the DKIM domain aligned to the visible From domain or an independently aligned SPF pass.
  4. The old selector is no longer used for newly submitted messages.
  5. Other hosted domains still receive their expected signatures.
  6. Postfix and the active signing service show no key-read or milter errors.

A DNS lookup proving that the new TXT record exists is necessary but insufficient. It does not prove Docker Mailserver selected the new key, signed the message, or aligned the signing domain with DMARC. The recipient’s raw authentication results close that loop.

7. Keep the old selector for an overlap period

RFC 6376’s rollover example publishes old and new public keys concurrently. New mail is signed with the new selector at the start of the transition; the old public key is removed at the end, after messages using it are no longer expected to be in transit or verified. Docker Mailserver’s documentation gives the same operational rule: keep the previous key and selector valid until the last message signed with that key reaches its expiry.

There is no universal number of hours that fits every deployment. Consider the maximum x= signature lifetime if your signer emits it, delayed queues, retries, forwarding, and your rollback policy. During overlap, monitor DMARC aggregate reports and recipient headers for the new selector. When retiring the old selector, remove only its public DNS record and archive or securely destroy its private key according to policy. Do not delete the new key, another domain’s key, or unrelated mail state.

Emergency rotation after suspected compromise

If the old private key may be exposed, shorten the overlap. Publish and verify the replacement, switch signing, and then revoke or remove the compromised public key as soon as the new path is proven. RFC 6376 represents explicit revocation with an empty p= value, but notes that revocation and removal have no defined semantic difference to verifiers. Follow your DNS and incident-response policy, preserve evidence privately, and accept that old signatures may stop verifying: preventing continued abuse is more important than a leisurely rollover.

Common rotation mistakes

Compact acceptance checklist

  1. The current selector, signing domain, signer, key size, and baseline authentication results are recorded.
  2. Persisted DMS configuration and signing material have a tested private backup.
  3. A distinct new selector and 2048-bit public/private pair exist without overwriting the old pair.
  4. The new public key resolves correctly from authoritative and public DNS.
  5. The active signer maps the intended domain—not every domain blindly—to the new selector.
  6. Docker Mailserver returns healthy after reload or restart.
  7. A real external message shows the new selector, dkim=pass, and aligned dmarc=pass.
  8. Other hosted domains continue signing correctly.
  9. The old public selector remains available for the planned transition period.
  10. DMARC reports and logs are reviewed before the old key is retired.

Use Docker Mailserver’s current DKIM, DMARC, and SPF documentation for the commands and paths supported by your pinned image. The standards basis for concurrent selectors is RFC 6376 section 3.1. Google’s email sender guidelines document the current minimum and recommended DKIM key lengths for mail sent to Gmail.