A DKIM key is a key pair: the private half stays on the mail server and signs every outgoing message, the public half sits in DNS under a name called a selector, and receivers fetch it to check the signature. Use a 2048-bit RSA key - 1024 is the minimum the standard still allows and is no longer considered adequate, 4096 causes DNS trouble for no practical gain. Add an Ed25519 key alongside it if your server can sign with both. Rotate by publishing a new selector, switching signing to it, and leaving the old public key in DNS for at least a week before removing it, because messages signed with the old key are still being verified after you stop using it. That is the whole procedure; the rest of this post is the detail that makes it go smoothly.
The background - why DKIM exists, how it relates to SPF and DMARC - is in SPF, DKIM and DMARC explained. This post assumes you know what DKIM is for and want to manage keys properly.
What a signature contains#
When your server signs a message, it adds a header like this:
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com; s=s2026a; t=1791446400; h=from:to:subject:date:message-id:mime-version; bh=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN/XKdLCPjaYaY=; b=Ys9Bd7pLq2T...| Tag | Meaning |
|---|---|
d= | The signing domain - the one DMARC compares with From: |
s= | The selector - which key to fetch |
a= | Algorithm: rsa-sha256 or ed25519-sha256 |
c= | Canonicalisation for header/body: relaxed tolerates whitespace changes |
h= | The headers that were signed, in order |
bh= | A hash of the body |
b= | The signature itself |
t= / x= | Signing time and optional expiry |
The receiver takes s= and d=, builds the name s2026a._domainkey.example.com, fetches the TXT record there, and verifies b= with the public key. If it verifies and the body still hashes to bh=, the signature passes.
Two signer settings are worth checking. Use relaxed/relaxed canonicalisation: simple breaks on harmless whitespace changes made by servers along the way. And make sure From: is in h=, which the standard requires; good signers also list it twice ("oversigning") so an attacker cannot add a second From: header without breaking the signature.
Avoid the l= tag if your server offers it. It limits the signed body to a byte count so that footers added later do not break the signature - which also means anyone can append content to your signed message and it still verifies.
Selectors and why you have several#
The selector is just a label that lets one domain publish many keys at once. Each sending system gets its own:
s2026a._domainkey IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkq..."s2026e._domainkey IN TXT "v=DKIM1; k=ed25519; p=11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo="pm._domainkey IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqG..."selector1._domainkey IN CNAME selector1-example-com._domainkey.provider.example.The first two are your own mail server's RSA and Ed25519 keys. The third belongs to a transactional service. The fourth is a CNAME into a provider's zone - some providers (Microsoft 365 is the well-known example) ask you to CNAME their selectors so they can rotate keys on their side without asking you to touch DNS.
Selectors cost nothing and do not conflict, so name them so you can tell what they are a year from now. Common schemes encode the date (s2026a, 202610), the system (mail, app) or both. Stalwart's recent versions generate selectors from a template whose default looks like v1-rsa-20261008 - version, algorithm, date - which is a good scheme to copy if you generate keys yourself.
Key types and sizes#
| Key | DNS record size | Receiver support | Use |
|---|---|---|---|
| RSA 1024 | Fits one TXT string | Universal | Legacy only; replace it |
| RSA 2048 | Two TXT strings | Universal | The default choice |
| RSA 4096 | Large; UDP size issues | Universal in theory | Not worth it |
| Ed25519 | Tiny | Partial | Alongside RSA, never instead |
RFC 8301 says signers must use RSA keys of at least 1024 bits and should use at least 2048; verifiers must not consider keys under 1024 bits valid. 1024-bit RSA is within reach of well-funded attackers, and some large receivers treat it as weak. 2048 is the sensible floor.
4096-bit keys are not more useful in practice. The public key alone is around 700 characters of base64, the TXT response can exceed the traditional 512-byte UDP DNS limit, and some DNS providers' editors cannot store it. Resolvers can fall back to TCP, but you are adding failure modes for security nobody needs yet.
Ed25519 (RFC 8463) is the modern choice: short keys, short signatures, fast. The problem is verification support - not every large receiver verifies Ed25519 signatures, and a signature a receiver does not understand is ignored rather than failed. That is why the practice is dual signing: sign each message with both an RSA and an Ed25519 key. Receivers that understand Ed25519 can use it; everyone else uses RSA. Stalwart does this by default for new domains.
Publishing keys in DNS#
The record format:
v=DKIM1; k=rsa; p=<base64 public key>v=DKIM1 is optional but conventional and must come first if present. k= defaults to rsa; Ed25519 keys need k=ed25519. p= holds the key. Optional tags: t=y marks the domain as testing DKIM (receivers should not treat failures differently from unsigned mail - remove it once things work), and t=s requires d= to match the identity exactly, not a subdomain.
A 2048-bit RSA key is around 400 characters of base64, and a single DNS TXT string can hold at most 255. The record is therefore stored as several strings, which receivers concatenate:
s2026a._domainkey IN TXT ( "v=DKIM1; k=rsa; " "p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx3...first part..." "...second part...IDAQAB" )Most web DNS editors split long values automatically. Some require you to paste the quoted strings yourself; a few silently truncate. After publishing, fetch it and compare:
$ dig +short TXT s2026a._domainkey.example.com"v=DKIM1; k=rsa; " "p=MIIBIjANBgkq...first..." "...second...IDAQAB"Several quoted strings in the answer are fine. A key that is shorter than what your server gave you, or with spaces or quote characters inside the base64, is not.
If you generate keys by hand rather than letting the mail server do it, openssl does the job:
# RSA 2048: private key, then the public key as DKIM wants it$ openssl genrsa -out s2026a.private 2048$ openssl rsa -in s2026a.private -pubout -outform der | openssl base64 -A# Ed25519: DKIM wants the raw 32-byte public key, not the DER wrapper$ openssl genpkey -algorithm ed25519 -out s2026e.private$ openssl pkey -in s2026e.private -pubout -outform der | tail -c 32 | openssl base64 -ARotating keys without breaking mail#
Why rotate at all: a private key that leaks lets anyone sign as your domain until you notice; old keys tend to be weak ones; and staff who once had server access leave. Commonly cited industry guidance (M3AAWG's) is to rotate at least every six months. Many organisations do it yearly; once a year done reliably beats quarterly on paper.
The safe sequence:
- Generate a new key under a new selector. Never reuse a selector name for a different key - receivers and resolvers cache the old record for its TTL, and signatures with the new key would fail against the cached old one.
- Publish the new public key and verify it from outside with
dig. Wait at least the record's TTL so every resolver can see it. - Switch signing to the new key. From this moment new mail carries
s=with the new selector. - Leave the old public key published for at least seven days. Mail signed with the old key may sit in queues, be retried, be forwarded, or be verified late by a receiver re-scanning it.
- Revoke the old key. Either delete the record, or publish it with an empty key (
v=DKIM1; p=), which RFC 6376 defines as revoked. An emptyp=is slightly more informative to anyone debugging later. - Destroy the old private key once you are sure nothing still signs with it.
The common failure is doing steps 3 and 5 together - switching signing and deleting the old record at once - which fails every message still in flight. The other is doing step 3 before step 2 has had time to settle, which fails the first hour of mail.
Automatic rotation in Stalwart
Recent Stalwart versions can manage this lifecycle themselves: each domain has a DKIM management setting that is either manual or automatic. Automatic mode rotates keys on a schedule (90 days by default), keeps a superseded key's DNS record published for 7 days after cutover, and deletes the key material 30 days after that. It needs automatic DNS management - API access to your DNS provider - because it has to publish and later remove records itself. Without that, rotation stays manual: generate a new signature in the web admin, publish its record, switch, and remove the old one later, following the sequence above. Version details matter here, so check the documentation for the release you run.
Emergency rotation after a leak#
The schedule above is for routine rotation. If a private key may have leaked - a server compromise, a backup left in a public bucket, a former administrator who kept a copy - the order changes, because now the old key is the danger.
- Generate and publish a new key under a new selector, exactly as before, and switch signing to it as soon as the record resolves from a public resolver. Do not wait a full TTL; minutes matter more than a few failed signatures.
- Revoke the old key immediately with an empty
p=. Some legitimate mail still in flight will fail DKIM. That is the price, and it is small: SPF usually still passes for it, and DMARC needs only one aligned pass. - Check DMARC aggregate reports for the following weeks. Messages signed with the old selector from IP addresses you do not recognise are the evidence that the key was used.
- Find out how it leaked before the next key goes the same way. Rotation does not help if the new private key sits in the same exposed place.
Everyone who manages a domain should know where its DKIM private keys live and who can read them. If the answer is "in a backup archive that several people can download", treat that archive with the same care as the key itself.
Keys held by other senders#
Your own mail server is rarely the only system signing for your domain. The newsletter platform, the helpdesk, the invoicing tool and the transactional email service each sign with a key they hold, under a selector you published for them. Some points about those:
- You cannot rotate them yourself unless the provider offers it. Many providers rotate on their side through the CNAME arrangement described above, which is a good reason to prefer the CNAME method when offered.
- Remove selectors for services you stop using. A key whose private half belongs to a vendor you no longer pay can still sign valid mail for your domain. Delete the record when the contract ends.
- Keep an inventory. A short list of selector, system, owner and date published saves an afternoon of
digarchaeology. DMARC reports help build it: every passing DKIM signature in them names its selector. - Watch for providers signing with their own domain. If a service signs with
d=provider.examplerather than your domain, its signature passes but does not align, and DMARC will depend on SPF alone for that mail. Most services can sign with your domain once you publish their record.
When DKIM fails#
The Authentication-Results header on a received message names the failure:
- `dkim=fail (body hash did not verify)` - the body changed after signing. A mailing list footer, a security gateway rewriting links, an antivirus appending a notice, or
simplebody canonicalisation meeting a whitespace change. - `dkim=fail (signature verification failed)` - the headers changed, or the published key does not match the private key. After a rotation, this usually means the wrong record was published.
- `dkim=neutral` or `permerror (no key)` - the receiver could not find a key at the selector. Check the name for a doubled domain, and check that the selector in
s=matches what you published. - `dkim=permerror (key syntax)` - the record is malformed: a missing
p=, stray quotes inside the base64, or a truncated key. - `dkim=pass` but `dmarc=fail` - the signature passed for a domain that does not align with
From:, often a provider's own domain. See DMARC reports and policy rollout for alignment.
Mailing lists breaking signatures is expected and not yours to fix; ARC (Authenticated Received Chain) headers exist so lists can vouch for the original result.
DKIM on a RE:NODE Mail Server#
The Mail Server line runs Stalwart, and its web admin generates DKIM keys for each domain you add and shows the records to publish. You publish them wherever your DNS is hosted - there is no zone editor on our side - along with MX, SPF and DMARC. Keys live in the server's data store, so the backup slots on your plan (one to four, by tier) cover them along with the mailboxes. If you send through a relay set in the web admin, Stalwart still signs with your keys first. Stalwart mail server setup covers adding the domain and reading the record list.
FAQ#
What DKIM key size should I use?
RSA 2048. It is universally supported and fits in DNS with ordinary string splitting. Add Ed25519 alongside it if your server can dual-sign. Avoid 1024 for new keys and avoid 4096, which causes DNS problems for no practical benefit.
Can I have more than one DKIM record?
Yes, one per selector, as many as you like. Each sending system should have its own. What you cannot have is two different keys at the same selector name.
How long should I keep the old key after rotating?
At least seven days after you stop signing with it, longer if you send mail that may be retried or forwarded for a while. Then revoke it with an empty p= or delete it.
Do I need to rotate keys if nothing has leaked?
You will not know if something has leaked. Rotating every six to twelve months limits how long a quietly stolen key stays useful, and it keeps the procedure familiar for the day you need it in a hurry.
Why does my Ed25519 signature show no result at Gmail?
Because not every receiver verifies Ed25519 yet. An unrecognised signature is ignored, not failed. That is why you sign with RSA as well - the RSA signature carries the result.




Comments
Completely anonymous: no account, no email, no cookie. We store the name you type, the text and the time - nothing else. Links are limited and markup is not rendered.