How to Set Up SPF, DKIM and DMARC on Your Hosting Without Breaking Mail

To set up SPF, DKIM and DMARC, you publish three kinds of DNS records on your domain and you do it in that order: SPF first, DKIM second, DMARC last and in monitoring mode. Doing it in that order matters because DMARC is the policy layer that reads the other two, and if you publish a strict policy before SPF and DKIM are correct, you will reject or quarantine your own legitimate mail.

Everything below assumes you control the DNS zone for your domain and can send test mail from a real mailbox on it. The records are plain text in DNS, and the only tools you need are dig and a copy of the raw message headers.

Start with SPF: list the hosts allowed to send as you

SPF is a single TXT record on the domain apex. It lists the sending sources that are allowed to use your domain in the envelope sender, and it ends with a policy that tells receivers what to do with everything else. A typical record looks like this:

example.com.  300  IN  TXT  "v=spf1 mx ip4:203.0.113.10 include:_spf.mailprovider.example -all"

Read it left to right. v=spf1 marks the record. mx allows the hosts in your MX records. ip4: allows one literal address. include: pulls in another domain's SPF record, which is how you authorize a relay or a mail provider without copying their addresses by hand. The trailing -all is a hard fail for anything not matched.

Two mechanical rules trip people up. First, there must be exactly one SPF record per domain name. A second TXT record starting with v=spf1 is a permanent error and receivers will treat the domain as having no valid SPF at all. Second, SPF has a lookup limit: each include, a, mx, ptr and exists term costs a DNS lookup, and going past the limit is a permerror. Keep the chain shallow and check it before you go live:

$ dig +short TXT example.com
"v=spf1 mx ip4:203.0.113.10 include:_spf.mailprovider.example -all"
$ dig +short TXT _spf.mailprovider.example
"v=spf1 ip4:198.51.100.0/24 -all"

Resolve every include by hand like that and count the hops. If the provider's record itself has includes, they count too. When you are close to the limit, flattening the record by replacing an include with its literal ip4: ranges works, but you then own keeping those ranges current.

Before you tighten anything, send a message from each system that legitimately sends as your domain, including web applications and any transactional mail, and confirm the receiving side sees a pass. If a sender is missing from SPF, add it now rather than after DMARC is enforcing.

Add DKIM: sign the message, publish the key

DKIM is different in kind from SPF. SPF checks the envelope sender against a list of IPs. DKIM puts a cryptographic signature in the message headers, computed over selected headers and the body, and publishes the public key in DNS so receivers can verify it. Because it signs the content, DKIM survives forwarding in many cases where SPF does not.

You do not usually write DKIM keys by hand. Your mail software generates a key pair and gives you a selector name and a public key to publish. The record goes at selector._domainkey.example.com:

mail._domainkey.example.com.  300  IN  TXT
  "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A..."

The p= value is the base64 public key. Some DNS panels split long values into multiple quoted strings; that is fine, the receiver concatenates them. What is not fine is a truncated key or a stray space inside the base64, so copy the value exactly as your mail software prints it.

The selector is the part before ._domainkey, and it appears in every signed message as the s= tag of the DKIM-Signature header. That is how a receiver knows which DNS name to query. To confirm the key is live and matches what your signer uses, query it:

$ dig +short TXT mail._domainkey.example.com
"v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A..."

Then send a message to a mailbox you control, view the raw source, and look for DKIM-Signature with d=example.com and s=mail. The receiving server's authentication results header, usually Authentication-Results, will say whether the signature verified. If it says dkim=permerror, the key in DNS does not match the signer or the record is malformed. Common causes are an extra whitespace character in p=, a selector typo, or the signer rotating keys without you publishing the new one.

Use a different selector for each sending system, for example one for the main mail server and one for a web application, so you can rotate or revoke one without touching the other. Keep the private key off any host that does not need to send.

How to set up SPF, DKIM and DMARC so the policy comes last

DMARC lives at _dmarc.example.com as a TXT record. It tells receivers what to do when a message claiming to be from your domain fails both SPF and DKIM alignment, and where to send reports. The key word is alignment: SPF or DKIM must not only pass but pass for a domain that matches the From header, either exactly or on the organizational domain.

Begin with a policy of none. That publishes the record and starts report collection without changing delivery:

_dmarc.example.com.  300  IN  TXT
  "v=DMARC1; p=none; rua=mailto:[email protected]; adkim=r; aspf=r; pct=100"

The rua= address receives aggregate XML reports, one per reporting receiver per period, summarizing pass and fail counts by source IP and by alignment. adkim and aspf set alignment to relaxed (r) or strict (s); relaxed lets a subdomain of the same organizational domain align. Leave them relaxed until you know what your senders look like.

Then read the reports. Aggregate reports are compressed XML, and the fields that matter are the source IP, the authentication result for SPF and DKIM, and the alignment result. Any IP sending real mail that shows up as failing alignment is a sender you have not authorized yet. Fix SPF or DKIM for it first. This is the whole reason for the monitoring phase: you are discovering your own mail streams.

When the reports show only known senders passing, move to p=quarantine and later to p=reject. If you are nervous, use pct= to apply the policy to a fraction of mail while you watch, then raise it. A typical end state:

_dmarc.example.com.  300  IN  TXT
  "v=DMARC1; p=reject; rua=mailto:[email protected]; ruf=mailto:[email protected]; adkim=r; aspf=r"

Query the record to confirm what is actually published, since a duplicate or misspelled record silently disables the policy:

$ dig +short TXT _dmarc.example.com
"v=DMARC1; p=reject; rua=mailto:[email protected]; adkim=r; aspf=r"

One message can have several Authentication-Results headers as it crosses systems; read the one added by the final receiving server. And remember that DMARC only covers the From header domain. Spoofing of other headers, or of display names, is outside its scope.

What to do next

Publish SPF with a hard fail, publish a DKIM key per sending system, then publish DMARC at p=none with a reporting address you actually read. Give the reports time to accumulate, fix every legitimate sender that fails alignment, and only then step the policy to quarantine and reject. Recheck all three records with dig after every DNS change, because a typo in a TXT value is invisible in most control panels and only shows up as a silent authentication failure in the headers of mail you send yourself.

Related articles

Subscribe to our newsletter

Get the latest hosting tips, performance insights, and industry news.