Skip to content

The SPF 10-lookup limit: diagnosis and flattening

In-depth guide. Updated .

Why SPF fails with permerror after ten DNS lookups, how to count them including nested includes, and safer alternatives to flattening your record.

What the rule says

SPF (RFC 7208) lets a domain list the servers allowed to send mail using it as the envelope sender. Evaluating a record can require DNS queries, and a record could chain includes endlessly, so §4.6.4 caps the work. Terms that cause DNS lookups are limited to 10 per evaluation, and exceeding the limit produces a permerror result.

Which terms count toward the limit
TermCounts?Notes
include:YesPlus every counting term inside the included record, recursively
a, mxYesmx also looks up the addresses of each MX host, at most 10 of them
ptrYesDeprecated by RFC 7208 §5.5; avoid it
exists:YesOften used with macros
redirect=YesReplaces the rest of the evaluation with another record
ip4:, ip6:, allNoNo DNS query needed
exp=NoOnly fetched to explain a failure, after evaluation

The same section also limits void lookups: DNS queries that return no records or a name that does not exist. More than two of those in one evaluation is also a permerror, which catches includes that point at providers you no longer use.

The limit is per evaluation, not per record

A root record with four includes can use 14 lookups if the included records contain their own includes. Only a recursive count tells you the real number.

What happens when you exceed it

A receiver that hits the limit stops evaluating and returns permerror. RFC 7208 treats that as a permanent problem with your record, and receivers generally treat it like a failure. SPF then fails for every message, including mail from servers that are correctly listed.

With a DMARC policy in place the damage depends on DKIM. Messages that carry an aligned DKIM signature still pass DMARC; messages that relied on SPF alignment fail and are quarantined or rejected under an enforcing policy. That is why a record that worked for years can break the day someone adds one more marketing tool.

How nested includes add upA root record with four includes reaches eleven DNS lookups because two included records contain further includes, so the receiver returns permerror.example.com: 4 includes + mx = 5 lookupsinclude:_spf.mail.example.netinclude:esp.example.netinclude:crm.example.netinclude:helpdesk.example.net mx_spf.mail.example.net: +3 lookupsThree nested includes for regional serverrangesesp.example.net: +2 lookupsinclude for a partner network plus an amechanismcrm.example.net andhelpdesk.example.net: +1One nested include; the other record is ip4ranges onlyTotal: 11 lookups → permerrorSPF fails for all mail from example.com untilthe record is reduced
A root record with four includes reaches eleven DNS lookups because two included records contain further includes, so the receiver returns permerror.

Diagnosing your record

The Email Security resolves the record and every include and redirect from the XGM server. It shows the tree, counts lookups the way receivers do, flags void lookups and loops, and reports records that are close to the limit as a warning at eight or more. Use it before and after every change to the record.

You can also walk the tree by hand with dig. Query the root record, then query each include target and count again. It is tedious for large records, but it shows exactly where the lookups come from.

Walking an SPF tree with dig
dig +short TXT example.com
"v=spf1 include:_spf.mail.example.net include:esp.example.net mx -all"

dig +short TXT _spf.mail.example.net
"v=spf1 include:_spf1.mail.example.net include:_spf2.mail.example.net ~all"
Reading the SPF Checker result
FindingWhat to do
n of 10 DNS lookups used (passed)Nothing; keep room for future senders.
n of 10 DNS lookups used (warning, 8 or more)Plan a clean-up before the next vendor is added.
n DNS lookups (limit is 10) (critical)SPF is failing now; reduce lookups today.
includes without an SPF record (void)Remove includes for providers you no longer use.
Loop in the treeAn include points back to a record already evaluated; remove it.

Auditing a record step by step

Take a record that has grown over the years for example.com. The SPF Checker reports 12 lookups and permerror, so every message relying on SPF currently fails. The table below lists each term with the lookups it costs, including nested ones, and what the audit found.

Audit of an over-limit SPF record
TermLookupsFindingDecision
include:_spf.mail.example.net4Mailbox provider, sends most mailKeep
include:esp.example.net2Newsletter tool, DKIM already signs with example.comMove to news.example.com
include:oldcrm.example.net2CRM cancelled two years ago, no reports sinceRemove
include:helpdesk.example.net1Helpdesk, sends from its own bounce domainRemove; DKIM covers DMARC
mx1Inbound servers do not send outbound mailRemove
a1Web server sends contact form mailReplace with ip4:192.0.2.80
ptr1DeprecatedRemove

After the changes the root record uses four lookups, all from the mailbox provider, and the newsletter subdomain has its own record with two. The contact form keeps working through a fixed ip4 term. Nothing was flattened, so provider changes still flow through automatically.

Result of the audit
example.com.       IN TXT "v=spf1 include:_spf.mail.example.net ip4:192.0.2.80 -all"
news.example.com.  IN TXT "v=spf1 include:esp.example.net -all"

Before removing an include, confirm in your DMARC aggregate reports that the service either no longer sends or passes through aligned DKIM. The aggregate report guide shows how to read those rows.

SPF for HELO names and non-mail hosts

RFC 7208 also defines SPF checks for the HELO or EHLO name a server uses when it connects. Receivers use this identity when the envelope sender is empty, as in bounce messages. Publishing a small record for each mail server host name costs you nothing and keeps those checks passing.

SPF for a server host name
mail.example.com. IN TXT "v=spf1 a -all"

Host names that never send mail can publish v=spf1 -all. That includes web servers and other subdomains that appear in DNS but have no reason to be used as a sender. These records live on their own names, so they do not use any of the root domain's lookup budget.

Fixes that do not need flattening

1. Remove what you no longer use

Old records collect includes for tools that were cancelled years ago. Compare every include with your sender inventory and the sources in your DMARC aggregate reports. An include that never appears as a passing source for months is a candidate for removal.

2. Drop mx and a where they are not needed

mx authorises your inbound mail servers to send, which is only right if they actually send outbound mail. Many hosted mailbox providers deliver outbound mail from different servers covered by their own include. Removing an unnecessary mx or a saves a lookup each.

3. Let vendors use their own bounce domain

SPF is checked against the envelope sender (the return-path), not the visible From address. If a vendor sends with a bounce address on its own domain or on a subdomain like bounces.example.com, SPF is evaluated for that domain and your root record does not need the vendor's include at all. DMARC then passes through DKIM signed with your domain.

4. Split sending across subdomains

Each domain has its own SPF record and its own 10-lookup budget. Moving newsletters to news.example.com and receipts to billing.example.com moves their includes out of the root record. Each subdomain then needs its own DKIM setup and inherits the organizational DMARC policy.

Before and after splitting by subdomain
; before: 11 lookups at the root
example.com.       IN TXT "v=spf1 include:_spf.mail.example.net include:esp.example.net include:crm.example.net include:helpdesk.example.net mx -all"

; after: the mailbox provider stays at the root, other streams move
example.com.       IN TXT "v=spf1 include:_spf.mail.example.net -all"
news.example.com.  IN TXT "v=spf1 include:esp.example.net -all"
help.example.com.  IN TXT "v=spf1 include:helpdesk.example.net -all"

Flattening: how it works and why it is risky

Flattening replaces include mechanisms with the ip4 and ip6 ranges they currently resolve to. Address mechanisms do not count toward the limit, so a flattened record can list many providers with zero or one lookup. It solves the count, but it copies someone else's configuration into your DNS as a snapshot.

Providers change their sending ranges, sometimes without notice. A flattened record keeps the old ranges, and mail from the new servers fails SPF until someone updates the record by hand. The failure is silent: nothing in DNS tells you the provider's include changed.

Flattening trade-offs
AspectincludeFlattened ip4/ip6
Lookups1 or more per providerNone for the flattened part
Follows provider changesAutomaticallyOnly when you update the record
Record sizeShortLong; may need several TXT strings
Failure modepermerror when over the limitSilent failures for new provider IPs
MaintenanceLowNeeds automation and monitoring

If you flatten, automate it. A scheduled job should resolve the original includes, compare the ranges with the published record and alert or update when they differ. Keep includes for providers that change often, and flatten only stable ranges you control, such as your own servers.

Record size

A single TXT string is limited to 255 characters, so long records are split into several quoted strings that receivers join. RFC 7208 §3.4 also advises keeping the SPF answer small enough to fit a 512-byte DNS response over UDP. The SPF Checker notes records over 450 characters.
A long record split into TXT strings
example.com. IN TXT "v=spf1 ip4:192.0.2.0/24 ip4:198.51.100.0/24 ip4:203.0.113.0/24 " "ip6:2001:db8:10::/48 ip6:2001:db8:20::/48 include:_spf.mail.example.net -all"

Macros and SPF hosting services

Some services avoid the limit with the exists mechanism and SPF macros. The record contains a term such as exists:%{i}._spf.service.example.net, and the service answers per sending IP whether it is allowed. The evaluation costs one lookup regardless of how many providers sit behind it.

This moves the list of your authorised servers into a third party's DNS. It can be a sound choice for large organisations, but understand the dependency: if the service is down or misconfigured, SPF fails for all your mail. Macros also make the record harder to read and audit, including for tools like the SPF Checker, which count the lookup but cannot see the list behind it.

Watching for changes you did not make

Includes are live references. When a provider reorganises its own SPF record, adding a regional include or a new partner range, your count changes the next time a receiver evaluates your record. You get no notification, and the first sign may be a DMARC report full of SPF failures.

A simple safeguard is a scheduled check of the lookup count. Run the SPF evaluation daily from a script or monitoring system and alert when the count reaches eight or when the result is not pass for a known sending IP. The XGM API returns the count as JSON, which makes it easy to wire into existing monitoring.

Daily lookup-count check with the XGM API
count=$(curl -s -H 'Accept: application/json' https://xgm.ro/api/v1/spf/example.com | python3 -c 'import sys, json; print(json.load(sys.stdin)["lookups"])')
if [ "$count" -ge 8 ]; then echo "SPF for example.com uses $count of 10 lookups"; fi

Keep a copy of the full include tree from each check. When the count jumps, comparing two trees shows immediately which provider changed, and whether the change is permanent or a mistake on their side worth reporting.

Keeping it under the limit

  • Add an SPF review to vendor onboarding: which include, how many lookups, and whether DKIM with your domain makes the include unnecessary.
  • Run the Email Security after every DNS change and keep at least two lookups spare.
  • Review DMARC aggregate reports quarterly and remove includes that no longer send.
  • Prefer DKIM alignment for DMARC; SPF is a second layer, not the foundation.
  • Document why each include exists, in a comment in your DNS repository or change log.

SPF also has a second guard that is easy to forget: only one SPF record is allowed per name. Two TXT records starting with v=spf1 produce permerror just like too many lookups, and they often appear when two teams add providers independently. The SPF guide covers the other common syntax errors.

FAQ

Does ip4 or ip6 count toward the 10 lookups?

No. Address mechanisms and all do not query DNS. Only include, a, mx, ptr, exists and the redirect modifier count, including those inside included records.

Is the limit 10 includes or 10 lookups?

Ten DNS-querying terms in total across the whole evaluation. One include whose record contains three more includes uses four.

Why did my record break without any change on my side?

A provider you include added nested includes to its own record, raising your total above ten. Includes follow provider changes automatically, which is usually good but can push you over the limit.

Should I use ~all or -all?

Both work with DMARC, which is what receivers enforce. -all states clearly that unlisted servers are not allowed; ~all is a softer signal often used while the list is incomplete.

Does exceeding the limit affect DKIM?

No, DKIM is evaluated independently. Messages with an aligned DKIM signature still pass DMARC even while SPF returns permerror.

Is flattening against the rules?

No, publishing ip4 and ip6 ranges is normal SPF. The risk is operational: the ranges go stale when providers change them, unless you update the record automatically.

Sources