DMARC Engine
Home/Blog/How to add DMARC, SPF and DKIM records on Azure DNS
Blog

How to add DMARC, SPF and DKIM records on Azure DNS

An Azure DNS-specific walkthrough of publishing TXT and CNAME records for DMARC, SPF and DKIM, covering record sets, the @ apex, TXT chunking, the CLI add-record trap, Alias records and why delegation must be set at the registrar.

18 June 2026 · 13 min read

How to add DMARC, SPF and DKIM records on Azure DNS

Azure DNS is Microsoft's authoritative DNS hosting service, and it behaves quite differently from a typical registrar's DNS panel. Records live inside record sets grouped by name and type, TTL is expressed in raw seconds, TXT values are entered as separate strings that Azure stitches together, and the apex of your zone is written as @ exactly as in a classic zone file. If you have come from Cloudflare or a registrar like Namecheap, the mental model is similar but the controls and the quirks are not. This guide walks through adding the TXT and CNAME records for DMARC, SPF and DKIM in Azure DNS specifically, using both the Azure portal and the az network dns CLI, and it calls out the Azure-specific traps that cause "the record is there but the checker says it is missing".

It assumes your domain's nameservers actually point at the four ns-.azure-dns.com/net/org/info servers that Azure assigns to your zone. Creating a DNS zone in Azure does not make it authoritative; your registrar has to delegate to those nameservers first. We cover how to confirm that below, because it is the single most common reason Azure DNS edits appear to do nothing.

This is not a generic "what is DMARC" explainer. If you need that grounding first, read What is DMARC, What is SPF and What is DKIM, then come back here to publish the records.

First, confirm Azure DNS is actually authoritative for your zone

Owning a DNS zone object in Azure and having the internet ask Azure for your records are two separate things. When you create a zone in Azure DNS, the portal shows you four assigned nameservers near the top of the zone overview, for example:

ns1-08.azure-dns.com.
ns2-08.azure-dns.net.
ns3-08.azure-dns.org.
ns4-08.azure-dns.info.

Those four must be set as your domain's nameservers at your registrar (GoDaddy, Namecheap, 123-Reg, wherever you bought the domain), or Azure DNS is just a draft nobody reads. The four hostnames are unique to your zone, so copy them from your own zone overview rather than from any guide.

To confirm delegation from the outside, run your domain through the DMARC checker or the SPF checker. Both resolve records the way the rest of the internet sees them, so they reflect whichever nameservers are truly authoritative, regardless of what the Azure portal shows. If a record you added in Azure never appears in those tools after the TTL window, delegation is almost certainly the problem: your registrar is still pointing somewhere else.

There is also a subtler Azure-specific trap. If you have both an Azure public DNS zone and an Azure private DNS zone with the same name, or a leftover zone in a different resource group, you can spend an afternoon editing the wrong one. Check the Resource group and the zone Type (Public) at the top of the blade before you start. DMARC, SPF and DKIM are public records and must live in a Public DNS zone.

How Azure DNS names records: record sets and @

Azure DNS does not store individual records the way a spreadsheet would. It stores record sets: a name plus a type (for example _dmarc of type TXT), and inside that set one or more values sharing a single TTL. This matters constantly for email authentication, so it is worth understanding before you add anything.

  • The apex (root) of the zone is @. For example.com, a TXT record set with name @ is published at example.com itself. This is where SPF lives.
  • Subdomain labels are written relative to the zone, never as the full name. To publish DMARC at _dmarc.example.com, the record set name is _dmarc, not _dmarc.example.com. Azure appends the zone name for you. If you type the full name, Azure stores it as _dmarc.example.com.example.com, which is invisible to mail receivers.
  • A record set holds multiple values under one TTL. When you add a TXT record set in the portal, each line in the value editor is a separate TXT record entry. This is exactly how you must NOT publish two SPF records (more on that below), and it is also how Azure represents a single long TXT value split into chunks.

The TTL field in Azure is raw seconds, with a default of 3600 (one hour). Lower it to 300 (five minutes) while you are actively changing records so fixes propagate faster, then set it back to 3600 once the record is stable. TTL never affects whether a record is correct; it only affects how long resolvers cache the old answer.

Adding the SPF record (TXT at the apex)

SPF (Sender Policy Framework) lists which servers are allowed to send mail using your domain in the envelope sender. It is a single TXT record at the root of the zone.

In the Azure portal, open your DNS zone, click + Record set, and fill in:

  • Name: @
  • Type: TXT
  • TTL / TTL unit: 3600 / Seconds (or 300 while testing)
  • Value: your SPF policy on a single line, for example:
v=spf1 include:spf.protection.outlook.com include:_spf.google.com ~all

The exact include: mechanisms depend on who actually sends your mail. The example above authorises Microsoft 365 and Google Workspace. A domain sending through Microsoft 365 plus a marketing tool and a transactional provider might instead read:

v=spf1 include:spf.protection.outlook.com include:servers.mcsv.net include:sendgrid.net ~all

The equivalent on the CLI is:

az network dns record-set txt add-record \
  --resource-group my-rg \
  --zone-name example.com \
  --record-set-name "@" \
  --value "v=spf1 include:spf.protection.outlook.com include:_spf.google.com ~all"

Three SPF rules that matter on Azure DNS and everywhere else:

  1. One SPF record per domain, full stop. You must not end up with two separate v=spf1 values in the apex TXT record set. Azure makes this easy to get wrong, because the value editor invites you to add a second line, and the CLI add-record command appends rather than replaces. Two v=spf1 strings produce a permerror and SPF fails for every sender. When you add a new platform, edit the existing SPF value and append another include:; do not add a second TXT entry. Our per-sender guides, such as email authentication for Mailchimp and email authentication for SendGrid, show the correct include for each platform.
  2. Stay under ten DNS lookups. Every include, a, mx, ptr and exists mechanism costs a lookup, and nested includes count too. Cross ten and SPF returns permerror regardless of how correct each include is. The SPF checker counts your lookups and flags which include is expensive. Background reading: SPF PermError: too many DNS lookups and Fix SPF PermError.
  3. Prefer ~all (softfail) while validating, then move to -all (hardfail) only once DMARC reporting confirms every legitimate source passes alignment. Do not jump to -all before you have proven your sending sources.

Azure DNS does not flatten SPF or count lookups for you; nothing in the portal warns you when you cross the ceiling. If your SPF is already near ten lookups, flattening it into a single static record buys headroom, at the cost of needing re-checks when providers change IP ranges. Our hosted SPF product keeps a single flattened record under the limit and updates it automatically. The mechanics are explained in How SPF flattening works.

Adding the DMARC record (TXT at _dmarc)

DMARC ties SPF and DKIM together with alignment and tells receivers what to do with mail that fails, plus where to send reports. It is a TXT record at _dmarc.yourdomain.

In the Azure portal, + Record set:

  • Name: _dmarc (label only, no domain appended)
  • Type: TXT
  • TTL: 3600
  • Value: start in monitoring mode:
v=DMARC1; p=none; rua=mailto:reports@yourdomain.com; fo=1

On the CLI:

az network dns record-set txt add-record \
  --resource-group my-rg \
  --zone-name example.com \
  --record-set-name "_dmarc" \
  --value "v=DMARC1; p=none; rua=mailto:reports@example.com; fo=1"

A few Azure-specific and DMARC-specific points:

  • The name is _dmarc, never _dmarc.example.com. The underscore label is fine in Azure DNS; it does not need quoting in the portal Name field.
  • Start at p=none. That is the whole point of the staged rollout: collect reports, see your real sending sources, fix what fails, then enforce. Do not publish p=reject on day one. The pacing is laid out in DMARC policy: none vs quarantine vs reject and the safe path is in Reach p=reject without breaking email.
  • Raw rua mailboxes drown you. A live domain can receive dozens of XML aggregate reports a day. Either parse them with the DMARC report analyzer, or point rua at a service that aggregates them. Reading your first DMARC report explains what you are looking at.
  • External RUA destinations need authorisation. If you send reports to a mailbox on a different domain, that domain must publish an _report._dmarc authorisation TXT record, or compliant receivers will not send to it. This is a frequent silent failure.

If you would rather not hand-build the tag string, the DMARC generator produces a valid record you can paste straight into the Azure value field.

Adding DKIM records (usually CNAME, sometimes TXT)

DKIM (DomainKeys Identified Mail) attaches a cryptographic signature to outbound mail so receivers can verify it was not altered and came from a server holding your private key. The public half lives in DNS under a selector, at selector._domainkey.yourdomain. Your sending platform gives you both the selector and the value. There are two formats, and Azure DNS handles them differently.

CNAME-style DKIM (the common case, including Microsoft 365)

Most modern platforms, including Microsoft 365, Google Workspace in some setups, Amazon SES, SendGrid, Mailchimp and Klaviyo, give you CNAME records that delegate the DKIM key back to their own DNS. This is the friendly case on Azure: no long key to paste, and the provider rotates the key for you.

Microsoft 365 is the most likely pairing with Azure DNS, and it issues two CNAMEs:

selector1._domainkey  ->  selector1-example-com._domainkey.<tenant>.onmicrosoft.com
selector2._domainkey  ->  selector2-example-com._domainkey.<tenant>.onmicrosoft.com

In the Azure portal, + Record set for each one:

  • Name: selector1._domainkey (label only)
  • Type: CNAME
  • TTL: 3600
  • Alias / value: selector1-example-com._domainkey.<tenant>.onmicrosoft.com

On the CLI:

az network dns record-set cname set-record \
  --resource-group my-rg \
  --zone-name example.com \
  --record-set-name "selector1._domainkey" \
  --cname "selector1-example-com._domainkey.contoso.onmicrosoft.com"

Azure-specific gotchas for CNAME DKIM:

  • A CNAME record set in Azure can hold exactly one value. If you try to add a second value to the same CNAME name, Azure rejects it. That is correct DNS behaviour (a name with a CNAME may not have other records), and it is why each selector is its own record set.
  • Add every selector your provider issues. Microsoft 365 needs both selector1 and selector2, or DKIM key rotation eventually breaks because Microsoft alternates between them. SendGrid and similar tools issue two or three CNAMEs plus a tracking CNAME; add each as its own record set. Skipping the second selector is a slow-motion failure: it works today and breaks weeks later.
  • Watch the trailing dot. Azure stores CNAME targets as relative-looking strings but treats them as fully qualified. Paste the hostname exactly as your provider gives it, without adding a trailing dot of your own and without accidentally appending your zone name.
  • There is no az ... add-record for CNAME, only set-record. Unlike TXT, where add-record appends, the CLI CNAME verb is set-record because the set can hold only one target. This trips people scripting bulk changes.

Verify each selector with the DKIM checker. You give it the domain and selector; it fetches the published key and confirms it resolves and parses. Selectors are explained in DKIM selectors explained.

TXT-style DKIM (raw public key, and the chunking question)

Some providers and most self-hosted mail servers hand you the public key directly as a TXT record. It is long, because a 2048-bit key is longer than a single DNS string can hold:

Name:  default._domainkey
Value: v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC7Vd6...long base64...QIDAQAB

In the Azure portal, + Record set, Type TXT, Name default._domainkey, and paste the whole v=DKIM1; ... string into the value box. This is where Azure's chunking behaviour matters, covered next.

The TXT chunking gotcha on Azure DNS

A single DNS TXT string can hold at most 255 characters. A 2048-bit DKIM key exceeds that, and occasionally a long SPF or DMARC value does too. The DNS standard handles this by letting one TXT record contain multiple quoted strings that resolvers concatenate. In raw zone-file syntax:

default._domainkey IN TXT ( "v=DKIM1; k=rsa; p=MIGfMA0GCS...first 255 chars..."
                            "...remaining base64...QIDAQAB" )

Azure DNS handles this in a way that surprises people coming from other hosts:

  • The portal splits long values automatically, by 255-character boundary. When you paste a single long string into the TXT value box and save, Azure stores it as the correct multi-string TXT record under the hood. You do not manually insert quote marks. Confirm the result by reading the record back: a long TXT will display as multiple value lines in the record set, which is correct and is the concatenated key.
  • Do not split the key yourself at an arbitrary point. If you hand-split the base64 and the two halves do not reassemble to the exact original (a dropped character, an extra space, a line break inside a chunk), the published key is malformed and DKIM fails silently. Paste the whole value as one line and let Azure chunk it.
  • On the CLI, each --value is a separate string in the set, not a chunk of one TXT record. This is the dangerous bit. Running txt add-record twice does not chunk a long value; it creates two separate TXT records in the same set, which for SPF means two v=spf1 records and a permerror, and for DKIM means a broken key. To enter a long DKIM key via CLI safely, pass the entire key as a single --value and let Azure split it on the 255-character boundary internally. If you must script the split for an extremely long value, the DNS record splitter produces the correctly quoted chunks.

After publishing any DKIM record, verify with the DKIM checker. A record that is present but malformed (missing p=, a stray space, a truncated key) fails silently, so checking after you publish is not optional.

CNAME flattening, the apex, and Azure Alias records

People moving from Cloudflare ask where Azure's CNAME flattening is. Azure DNS does not flatten CNAMEs in the Cloudflare sense, and for email authentication you do not need it to.

The DNS standard forbids a CNAME at the zone apex (the root example.com) when other records exist there, and SPF and DMARC both live in TXT records anyway, so flattening simply does not apply to them. Publish SPF as a plain TXT at @ and DMARC as a plain TXT at _dmarc. Do not try to "delegate" SPF by pointing the root at another provider with a CNAME; that is invalid at the apex and breaks the TXT records you need.

DKIM CNAMEs sit on a subdomain label like selector1._domainkey, never at the apex, so ordinary CNAME rules apply and there is no apex restriction to work around. You do not need a flattening feature for DKIM on Azure.

Azure does offer Alias record sets, which look superficially like flattening, but they only point at Azure resources (a public IP, a Traffic Manager profile, another Azure DNS record set). An Alias record cannot target an external hostname like selector1-example-com._domainkey.contoso.onmicrosoft.com. So for DKIM CNAMEs that point at a third party, you must use an ordinary CNAME record set, not an Alias. Reaching for Alias here is a common Azure-specific mistake that produces a record the portal accepts but that does not resolve to your provider's key.

A clean publish order on Azure DNS

Doing these in order avoids the most common self-inflicted failures:

  1. Confirm delegation. The four azure-dns nameservers from your zone overview are set at the registrar, and the zone is a Public zone in the right resource group.
  2. Lower TTL to 300 on the record sets you are about to touch, so mistakes are cheap to fix.
  3. Publish SPF as one TXT at @, one v=spf1 value only, under ten lookups, ending ~all.
  4. Publish DKIM CNAMEs (every selector) or the TXT key, as a single pasted value.
  5. Publish DMARC at _dmarc with p=none and a working rua.
  6. Verify by lookup, not by the portal. Run the DMARC checker, SPF checker and DKIM checker. The portal showing a record only proves you saved it in Azure; the checkers prove the internet can see it correctly.
  7. Restore TTL to 3600 once everything is stable.
  8. Collect reports for a couple of weeks, then move the policy from none to quarantine to reject, using pct and the subdomain policy sp as controls. The journey is in The enforcement journey.

The practical takeaway

Azure DNS removes some mechanics (it chunks long TXT values for you, TTL is one field) and adds its own traps: the record-set model that quietly lets the CLI append a second SPF or DKIM value, Alias records that cannot point at an external DKIM host, and delegation that has to be set at the registrar before any of this is authoritative. The discipline that makes Azure safe is the same on every host: right type, right name (label only, @ for the apex), exactly one SPF record, each DKIM selector as its own CNAME set, verify by lookup rather than trusting the portal, and start DMARC at p=none so you enforce only after the reports prove your legitimate mail passes.

When you are ready to move from "the records exist" to "the records are correct and enforced", the staged path to p=reject is the real work, and it is exactly what we automate. See hosted DMARC, hosted SPF and hosted DKIM for the done-for-you route, or start free: point the DMARC checker at your domain and turn on emailed monitoring so any future change to your Azure DNS records pages you the moment it happens.

Share

See where your domain stands today

Run a free DMARC scan, then let us take you to enforced p=reject with no email outage.