Two things trip people up on this setup. First, Microsoft 365 does not sign with your custom domain until you explicitly enable it — a fresh tenant signs outbound mail as <tenant>.onmicrosoft.com, which authenticates fine but does not align with a From: header on your own domain, so DMARC fails. Second, the enable step validates the DNS records and refuses to run if they are not resolvable yet. Order matters: publish, wait, enable.
Why two selectors?
Microsoft rotates DKIM keys on its own schedule without asking you to touch DNS. Two published selectors make that safe: at any moment one key is actively signing and the other is the standby. When rotation happens, signing moves to the standby selector — whose key is already resolvable — and a new key is generated behind the first selector.
If you publish only selector1, everything works right up until the first rotation, at which point messages are signed with selector2 and receivers cannot find the key. The symptom is dkim=fail (no key for signature) appearing overnight on a setup that had been fine for months. Publish both.
What do the CNAMEs look like?
Both point into your tenant’s initial onmicrosoft.com domain. The general shape is:
Type: CNAME
Name: selector1._domainkey
Value: selector1-<domain-with-dashes>._domainkey.<tenant>.onmicrosoft.com
Type: CNAME
Name: selector2._domainkey
Value: selector2-<domain-with-dashes>._domainkey.<tenant>.onmicrosoft.comThe <domain-with-dashes> part is your custom domain with dots replaced by dashes. Microsoft’s own domain is a live example you can inspect right now:
dig +short CNAME selector1._domainkey.microsoft.com
selector1-microsoft-com._domainkey.microsoft.onmicrosoft.com.
dig +short CNAME selector2._domainkey.microsoft.com
selector2-microsoft-com._domainkey.microsoft.onmicrosoft.com.
# Querying TXT follows the CNAME and returns the actual key
dig +short TXT selector2._domainkey.microsoft.com
selector2-microsoft-com._domainkey.microsoft.onmicrosoft.com.
"v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC...".mail.protection.outlook.com region suffix or a hyphenated form you guessed wrong produces a CNAME that resolves to nothing, and the enable step will reject it.For a domain with a hyphen in it, note that Microsoft’s transformation only replaces dots. my-company.co.uk becomes my-company-co-uk, which is ambiguous if you try to reverse it — another reason to copy rather than derive.
Publishing the records
Both are ordinary CNAME records in whatever DNS provider hosts your zone. Points worth knowing:
- The Name field is
selector1._domainkeyon providers that append your zone automatically (Cloudflare, Namecheap, most registrars). Entering the fully-qualified name there producesselector1._domainkey.example.com.example.com. - On Cloudflare, set proxy status to DNS only (grey cloud). Cloudflare will not proxy a
_domainkeyrecord anyway, but check it — a proxied CNAME returns Cloudflare IPs, not the delegation. - TTL of 3600 is fine. You are not going to change these.
- A CNAME cannot coexist with any other record at the same name. If a TXT record is already sitting at
selector1._domainkeyfrom a previous mail provider, delete it first. - If the zone is DNSSEC-signed, nothing special is needed — check the zone validates with the DNSSEC checker if you are troubleshooting resolution failures.
Verify both resolve before touching the portal. The CNAME lookup does this in the browser, or from a shell:
for s in selector1 selector2; do
echo -n "$s: "
dig @1.1.1.1 +short CNAME "$s._domainkey.example.com"
doneEnabling signing
DKIM for custom domains is managed in the Microsoft Defender portal under email authentication settings, and can also be driven with Exchange Online PowerShell. Microsoft has moved this page around more than once, so navigate by searching for DKIM in the portal rather than following a memorized path — and check Microsoft’s current documentation if the layout differs from what a guide describes.
# Exchange Online PowerShell
Connect-ExchangeOnline -UserPrincipalName [email protected]
# See current state and the selector CNAMEs to publish
Get-DkimSigningConfig -Identity example.com | Format-List
# Create the config if the domain has never been set up
New-DkimSigningConfig -DomainName example.com -Enabled $false
# Enable signing once both CNAMEs resolve
Set-DkimSigningConfig -Identity example.com -Enabled $trueThe enable operation performs its own DNS check. If it fails with a message about the CNAMEs not being found, the records are genuinely not resolvable from Microsoft’s vantage point yet — either they were published in the wrong host field, or you are inside the negative-cache window. Wait and retry rather than editing anything.
Verifying it works
Publication and enablement are two separate facts, and you should confirm both.
| Check | How | Expected |
|---|---|---|
| CNAMEs published | dig +short CNAME selector1._domainkey.example.com | A ...onmicrosoft.com. target |
| Key resolvable | dig +short TXT selector1._domainkey.example.com | A record containing p= with base64 data |
| Signing enabled | Get-DkimSigningConfig -Identity example.com | Enabled : True |
| Actually signing | Send a test message, read the raw headers | DKIM-Signature with d=example.com |
| Aligned for DMARC | Read Authentication-Results on the delivered message | dkim=pass [email protected] and dmarc=pass |
That last row is the one that matters. Send a message to an external mailbox you control and check the delivered headers — the email header analyzer will parse them, or read them directly:
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com;
s=selector1; h=from:to:subject:date; bh=...; b=...
Authentication-Results: mx.google.com;
dkim=pass [email protected] header.s=selector1;
spf=pass smtp.mailfrom=example.com;
dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=example.comIf d= still shows <tenant>.onmicrosoft.com, signing for the custom domain is not enabled — the CNAMEs being present is not enough. If the signature is present with d=example.com but the receiver reports failure, work through body hash did not verify, since a transport rule or a disclaimer appliance appending a footer after signing is the usual cause in Exchange environments.
Troubleshooting the common failures
| Symptom | Most likely cause | What to do |
|---|---|---|
| Portal refuses to enable, "CNAME not found" | Records not resolvable from Microsoft’s side yet, or the wrong host field | Verify with dig @1.1.1.1 +short CNAME selector1._domainkey.example.com, then retry after the negative-cache window |
d= still shows <tenant>.onmicrosoft.com | CNAMEs published but signing never enabled for the custom domain | Run Get-DkimSigningConfig and confirm Enabled : True |
dkim=fail (no key for signature) after months of working | Microsoft rotated to a selector you never published | Publish the missing second CNAME — both must exist permanently |
| DNS provider rejects the CNAME | A leftover TXT or CNAME already occupies that name | Delete the stale record; CNAMEs cannot coexist with other types at the same name |
dkim=fail (body hash did not verify) | A transport rule or appliance modifies the body after signing | See body hash did not verify |
DKIM passes but dmarc=fail | Signing domain does not match the From: domain | Confirm d= is your custom domain, not the tenant or a vendor domain |
A note on hybrid and third-party routing: if outbound mail leaves through an on-premises Exchange server or a third-party security gateway rather than directly from Exchange Online, the signature is applied by whichever system Microsoft hands the message to last. Mail that never traverses Exchange Online is not signed by it at all, regardless of what the portal says. Trace an actual message’s Received: chain before concluding the configuration is wrong.
The rest of the authentication set
- SPF for a Microsoft 365 tenant is normally
v=spf1 include:spf.protection.outlook.com -all— validate the final record and the lookup count with the SPF checker, or build it with the SPF generator. - Publish DMARC once DKIM is aligned — start at
p=none, see DMARC record not found and the staged plan in p=none vs quarantine vs reject. - Confirm your MX records point at the tenant with the MX lookup.
- Run everything at once with the deliverability test.
- If you do not know which selector a message used, how to find your DKIM selector covers reading it from headers.