Setting up DMARC on Microsoft 365
Microsoft 365 will tell you DKIM is enabled, your SPF record will look correct, and your mail will still fail DMARC. This is the most common M365 outcome and it has one specific cause that the setup checklists do not mention.
The trap: the default signature does not align
When you enable DKIM in Microsoft 365 without configuring it for your custom domain,
outbound mail is signed with the tenant domain — d=yourtenant.onmicrosoft.com —
rather than the domain in your From: header.
DMARC does not care that DKIM passed. It cares whether the signing domain aligns with the domain your recipient sees. Microsoft’s own alignment table is unambiguous:
From: address |
Signing / MAIL FROM domain | Relaxed | Strict |
|---|---|---|---|
user@contoso.com |
contoso.com |
pass | pass |
user@contoso.com |
bounces.contoso.com |
pass | fail |
user@contoso.com |
contoso.onmicrosoft.com |
fail | fail |
contoso.onmicrosoft.com is a different organisational domain from contoso.com, so it
fails even relaxed alignment. Your DMARC result then rests on SPF alone — and the moment
anything forwards the message, SPF breaks too and DMARC fails outright.
The fix: Defender portal → Email & collaboration → Policies & rules → Threat policies
→ Email authentication settings → DKIM, and enable signing for your custom domain.
That publishes two CNAMEs (selector1._domainkey and selector2._domainkey) which
point back at Microsoft, and rotates keys for you. Until those exist and signing is
switched on, you do not have aligned DKIM.
→ Why DMARC fails when SPF and DKIM pass
Order of operations
- SPF at the domain root:
v=spf1 include:spf.protection.outlook.com -all. Add any other senders, and watch the ten-lookup limit. → SPF: too many DNS lookups - DKIM for the custom domain — the step above. Not the default.
- DMARC, at your DNS host. There is no admin-centre control for this on custom domains, and no PowerShell cmdlet; Microsoft is explicit about that.
Host: _dmarc
Type: TXT
Value: v=DMARC1; p=none; rua=mailto:dmarc@example.com
Your onmicrosoft.com domain needs its own record
Every tenant has one, SPF and DKIM are configured on it already, and DMARC is not. If you never send from it — most tenants do not — park it explicitly:
v=DMARC1; p=reject
That one is added inside the Microsoft 365 admin center: Settings → Domains → select the
*.onmicrosoft.com domain → DNS records → Add record.
Reports: expect less than you think
Two Microsoft-specific limits, both worth knowing before you interpret a quiet inbox as good news.
Microsoft never sends failure (ruf) reports. Not for any domain, regardless of what
your record asks for. An empty ruf mailbox is expected.
Microsoft only sends aggregate (rua) reports if your MX points directly at Microsoft
365. If you route inbound mail through a third-party security gateway first, or run a
hybrid setup where mail lands on-premises before a connector passes it to Microsoft,
Microsoft sends no aggregate reports at all. Your reports then cover a smaller slice
of your mail than you assume — which matters most at the moment you are deciding whether
it is safe to move to p=reject.
And if your rua= address is on a different domain from the one being monitored, that
other domain has to authorise it with a _report._dmarc record, or every conforming
receiver discards the reports silently.
→ Your DMARC reports may be going nowhere
Two more Microsoft-specific behaviours
Enforcement affects your own outbound mail. If your policy is p=quarantine or
p=reject, outbound M365 mail that fails DMARC at the destination is routed through
Microsoft’s high-risk delivery pool — a separate, lower-reputation IP range. There is no
override for this. It is another reason to fix alignment before raising the policy
rather than after.
dmarc=bestguesspass in a header is not a pass. Microsoft infers a result when the
sending domain has no DMARC record at all. Seeing it on your own mail means your record
is not being found — treat it as missing, not as passing.
Rollout
Microsoft recommends starting with a low-volume subdomain and saving the parent domain
for last, which is good advice. Their documentation also recommends stepping pct= from
10 to 100 — skip that part. RFC 9989
removed pct=, because implementations never agreed on what a partial value meant. Move
the policy itself instead: p=none → p=quarantine → p=reject.
Note that a DMARC record covers subdomains by inheritance, including ones that do not exist — but each sending subdomain still needs its own SPF and DKIM, or it inherits an enforcing policy it cannot satisfy.
Check the domain — it reads what receivers actually see, which is the only way to confirm the DKIM CNAMEs resolve and the signing domain aligns.