SPF PermError — Too Many DNS Lookups
Share

How to Fix an SPF PermError from Too Many DNS Lookups

A company that had done everything the advice columns told them to do started losing mail, one message at a time. Their SPF record had gone over the limit, so they flattened it: every include: statement replaced by the raw IP ranges sitting behind it. Lookup count, zero. Problem solved. And then, for months, nothing happened at all, which is exactly what a solved problem looks like.

Then roughly one message in twenty started failing SPF. Not all of it. One in twenty.

"one of their email service providers added a new IP address to their pool. Once every 20 messages or so their load balancer would use this IP address, which would fail SPF verification"

Do not flatten your SPF record, Mailhardener

Their record was compliant. Their record was also wrong, and it had been quietly wrong from the moment a vendor added one address to a load balancer pool. Nobody sent them a bounce that said so. The fix was the failure.

Keep that story in your pocket. It is the reason this article opens with diagnosis rather than with the SPF too many DNS lookups fix you probably came here for. Almost every other page on this error jumps straight to the surgery. If you are reading this with a bounce message open in another tab, five minutes of diagnosis will save you a weekend.

This is the troubleshooting companion to our guide to SPF records and the 10-lookup limit. If you need the fundamentals of what an SPF record is, start there and come back.

First, prove the lookup count is actually your problem

"PermError" is a single word covering at least five different failures. RFC 7208 defines it plainly: "A "permerror" result means the domain's published records could not be correctly interpreted," and it "signals an error condition that definitely requires DNS operator intervention to be resolved" (RFC 7208 Section 8.7). Which intervention it needs depends entirely on which of these you have.

  • Two SPF records on the same name. Section 4.5: "If the resultant record set includes more than one record, check_host() produces the "permerror" result." Microsoft says the same thing in its own words, noting the receiving system simply cannot tell which record to evaluate.
  • A syntax error anywhere in the record. Section 4.6: the syntax is validated first, and any error returns permerror "immediately … without further interpretation or evaluation." A stray space in a CIDR block does this. Your lookup count never even gets counted.
  • An include: pointing at a name with no SPF record. Per Section 5.2's recursion table, a nested result of none makes the parent return permerror. Microsoft's guidance is blunt: "If an include: domain doesn't resolve or has no SPF record, SPF validation returns permerror." Decommissioned vendors do this to people constantly.
  • More than two void lookups. A separate cap in the same section as the famous one, and the subject of its own section below.
  • More than 10 lookup-causing terms. The one everybody writes about.

One near miss worth knowing: no SPF record at all is not permerror. Section 4.5 says an empty record set produces "none", which is a different diagnosis with a different fix.

So how do you tell which one you have? Start with the evidence the receiver already handed you. RFC 7208 asks receivers to write the result into the message: the Received-SPF header is defined as a trace field that "SHOULD be prepended to the existing header," and Authentication-Results carries the same information. If the mail bounced outright, Microsoft documents the strings to look for in the non-delivery report: "The message exceeded the hop count" or "The message required too many lookups."

And here is the part that should slow you down. In 2023, Czybik, Horlboge and Rieck scanned 12,823,598 domains for an ACM Internet Measurement Conference study titled Lazy Gatekeepers. They found 7,251,736 domains (56.5%) publishing SPF, of which 211,018 (2.9%) contained errors. Too many DNS lookups accounted for 49,421 of them. But the same error table counts 90,697 domains where the record simply wasn't found, 38,296 with syntax errors, and 19,356 with include loops. Add those up and the lookup limit is not even the leading cause of PermError on the internet. It is just the one with the best SEO.

Count Your Lookups Before You Change Anything

The DNSai SPF Analyzer walks your full include, redirect, a, mx and exists tree live, maps each host to a known vendor, and reports the lookup count against the limit of 10. Nothing is cached or stored. Every query runs against DNS when you press the button.

Analyze Your SPF Record →

What RFC 7208 actually says about the limit

Everything you have read about the 10-lookup rule traces back to five paragraphs in Section 4.6.4 of RFC 7208. It is worth reading in the original, because the vendor summaries lose things.

"The following terms cause DNS queries: the "include", "a", "mx", "ptr", and "exists" mechanisms, and the "redirect" modifier. SPF implementations MUST limit the total number of those terms to 10 during SPF evaluation, to avoid unreasonable load on the DNS. If this limit is exceeded, the implementation MUST return "permerror"."

RFC 7208 Section 4.6.4, IETF / RFC Editor, April 2014

Six terms. Two MUSTs. Note who the MUST is aimed at: the SPF implementation doing the checking, not you. The exemptions matter just as much, because that is where your budget comes from:

"The other terms -- the "all", "ip4", and "ip6" mechanisms, and the "exp" modifier -- do not cause DNS queries at the time of SPF evaluation (the "exp" modifier only causes a lookup at a later time), and their use is not subject to this limit."

RFC 7208 Section 4.6.4, IETF / RFC Editor, April 2014

You can list a thousand ip4: ranges and spend nothing. That single sentence is the entire mechanical basis of SPF flattening, and we will come back to what it costs.

Two clarifications the specification does not spell out but Microsoft does. First, its SPF documentation points out that "the number of DNS lookups (which can be different than the number of DNS queries)" is what gets counted. The limit counts lookup-causing terms, not packets on the wire. Second, and this is the one that trips up spreadsheets:

"Each include: statement requires at least one DNS lookup, and more lookups might be required if the include: value points to nested resources. In other words, having fewer than 10 include: statements doesn't guarantee fewer than 10 DNS lookups."

Set up SPF to identify valid email sources for your Microsoft 365 domain, Microsoft Learn, 3 July 2026

Which is why Google's own admin help saying "An SPF record can have up to 10 include: tags" is a useful simplification and not the rule. Microsoft's list of what counts, for its part, omits ptr entirely. Both are excellent documents for configuring those two products. Neither is the specification.

The three sub-limits people get backwards

Section 4.6.4 carries three more constraints, and mixing them up sends you fixing the wrong thing.

  • Each mx evaluation gets 10 address records. "the evaluation of each "MX" record MUST NOT result in querying more than 10 address records -- either "A" or "AAAA" resource records." Breach it and "the "mx" mechanism MUST produce a "permerror" result." This is on top of the mx term costing one against your 10.
  • The ptr sub-limit does the opposite. Same 10-address cap, completely different consequence: "If this limit is exceeded, all records other than the first 10 MUST be ignored." No permerror. The RFC even explains the asymmetry: "the set of and contents of the MX record are under control of the publishing ADMD, while the set of and contents of PTR records are under control of the owner of the IP address actually making the connection." You cannot be punished for a record a stranger controls.
  • A slow evaluation is a temperror, not a permerror. The RFC says processors "SHOULD impose a limit on the maximum amount of elapsed time to evaluate check_host()" and that if it is exceeded, "the result of authorization SHOULD be "temperror"." Temperror means try again later. Permerror means the record is broken. If you are seeing temperror, your problem is DNS latency, not your lookup count. (Microsoft's related advice: a minimum TTL of 3600 seconds on SPF TXT records, to avoid lookup timeouts.)
SPF record analysis and DNS lookup counting

The limit is counted during evaluation, over the whole include tree, not over the text of your record.

A record with six include: statements can easily be a twelve-lookup record.

The ip4: and ip6: mechanisms cost nothing against the limit of 10.

Which is the entire reason flattening works, and the entire reason it goes stale.

The second limit almost nobody mentions: void lookups

Read to the end of Section 4.6.4 and there is a second cap sitting there, with its own permerror attached to it. It has been in the specification since April 2014. It appears in roughly none of the articles competing for this search.

"SPF implementations SHOULD limit "void lookups" to two. An implementation MAY choose to make such a limit configurable. In this case, a default of two is RECOMMENDED. Exceeding the limit produces a "permerror" result."

RFC 7208 Section 4.6.4, IETF / RFC Editor, April 2014

A void lookup is a query that comes back empty. The RFC describes the cases it means as terms "for which DNS queries return either a positive answer (RCODE 0) with an answer count of 0, or a "Name Error" (RCODE 3) answer." NXDOMAIN counts. So does a perfectly successful response containing nothing.

Here is one you can check yourself. On 2026-08-01, a TXT query for spf.intuit.com returned RCODE 3, NXDOMAIN, with only an SOA in the authority section. A textbook void lookup. Any record still carrying include:spf.intuit.com burns one of its two, and separately gets a permerror from Section 5.2 for including a name with no SPF record.

; a void lookup looks like a successful query with nothing in it ; TXT spf.intuit.com -> status: NXDOMAIN (RCODE 3), ANSWER: 0 ; measured 2026-08-01 against a single recursive resolver

Two things make this limit genuinely different from the limit of 10. It is a SHOULD, not a MUST, and it MAY be configurable, which means two receivers can lawfully disagree about whether your record permerrors and both be correct. Do not treat it with the same certainty you treat the 10. But do not treat it as theoretical either: the Lazy Gatekeepers scan found 5,308 domains failing purely on too many void lookups, a separate row in the table from the 49,421 failing on the lookup count.

Void lookups come from the same place old furniture comes from. A vendor you stopped using in 2021. A typo in a hostname nobody re-read. Every dead include: in your record is a void lookup waiting for the third one to arrive.

PermError does not mean your mail gets rejected

This is where most articles on this subject, including one of ours, have been sloppy. The common claim is that PermError causes receiving servers to reject your mail. The specification says something considerably more careful.

"As such, there is no comprehensive normative requirement for message handling in response to any particular result."

RFC 7208 Section 8, Result Handling, IETF / RFC Editor, April 2014

None. For any result. Section 8 describes two legitimate receiver behaviors: reject or defer inside the SMTP session, or permit the message and add "an additional header field that indicates the result returned by check_host()." The RFC notes that the concern is "typically handled by merely recording the result in the header and allowing the message to pass on for additional processing." The famous rejection code shows up only under a conditional: "If the message is rejected during the SMTP transaction for this reason, the software SHOULD use an SMTP reply code of 550 and, if supported, the 5.5.2 enhanced status code." If. Microsoft hedges the same way, saying messages "might be rejected."

Then there is DMARC, which is what most people actually mean when they say their mail is broken. RFC 9989 Section 5.3.5 is explicit: "If one or more of the Authenticated Identifiers align with the Author Domain, the message is considered to pass the DMARC mechanism check." One or more. A domain with correct, aligned DKIM signing can sit on a completely broken SPF record and keep passing DMARC indefinitely, which is precisely how organizations run over the limit for years without noticing.

So the honest version is narrower and more useful than the folklore: a permerror removes SPF as a source of authentication. It does not fail DMARC by itself, and it does not mandate a rejection. What happens to a given message is the receiver's local policy plus whatever DKIM is doing for you. That is still bad. You have lost one of your two authentication paths and you are relying on the other one being correct. But it explains why your mail is not uniformly bouncing, and it should change your triage order. If your sending sources are hard to inventory, Microsoft's advice is to get DKIM signing and DMARC in monitoring mode working first, then use the DMARC reports to find out who is actually sending as you.

One more mechanic explains the strangest symptom of all, the "our Google mail is fine but our Zendesk mail bounces" complaint:

"destination email systems evaluate the sources in the SPF TXT record from left to right. Evaluation stops when the message source is validated, and no more sources are checked. Therefore, an SPF TXT record might contain enough information to cause more than 10 DNS lookups, but the validation of some mail sources by some destinations doesn't go deep enough in the record to result in an error."

Microsoft Learn, 3 July 2026

An over-budget record is not uniformly broken. It is broken for whoever you listed last. Analyzers, ours included, report the worst-case count for the whole tree, which is the right number to design against, though it is not literally what every receiver experiences for every message.

Your vendor lookup table is out of date. So was ours.

Now the part that changed how I think about this whole category of advice.

There is a table that circulates across dozens of SPF articles listing what each vendor costs you. include:_spf.google.com is 3–4 lookups. Microsoft is 2–3. And so on. It gets copied, republished, and used to plan record budgets. We published a version of it ourselves: our April 2026 post told readers that a single include:_spf.google.com consumes 3–4 lookups because of Google's nested includes. We corrected that page on 2026-08-01, while researching this one.

On 2026-08-01, that is not what Google publishes. A live TXT query for _spf.google.com returns this:

v=spf1 ip4:74.125.0.0/16 ip4:209.85.128.0/17 ip6:2001:4860:4864::/56 ip6:2404:6800:4864::/56 ip6:2607:f8b0:4864::/56 ip6:2800:3f0:4864::/56 ip6:2a00:1450:4864::/56 ip6:2c0f:fb50:4864::/56 ~all

Count the includes. There are none. Google flattened its own record; the _netblocks.google.com sub-include that made it expensive still resolves on its own, but _spf.google.com no longer points at it. As measured that day, include:_spf.google.com costs one lookup. Not four. We were wrong in April, along with most of the field, and the correction to that older post is being made separately.

The same single-resolver snapshot, taken 2026-08-01. DNSai's SPF Analyzer walks the same tree and reports the same figure for google.com — one lookup, none of them nested:

Include targetLookups observed 2026-08-01Why
_spf.google.com1Flat, ip4/ip6 only
spf.protection.outlook.com1Flat, ip4/ip6 only, -all
servers.mcsv.net (Mailchimp)1Flat, ip4 only
mail.zendesk.com1Flat, ip4 only
_spf.hubspotemail.net1Flat, 19 ip4 ranges
amazonses.com1Flat, ip4 only
spf.constantcontact.com1Flat; also publishes a legacy spf2.0/pra record, which is not an SPF record and is not a duplicate
sendgrid.net2Nests include:ab.sendgrid.net, which is flat
_spf.atlassian.net2Chains to include:amazonses.com
_spf.salesforce.com2The include plus one exists: query

Every number in that table has a date attached for a reason. It is one resolver, on one day, and any vendor can change its record tomorrow without telling anyone. Google just did. Treat the table as a demonstration of method, not as a budget. The only lookup count that means anything is the one you measure against your own record today.

Salesforce deserves a footnote because it shows a road most vendors have not taken. Its record contains no IP list at all:

v=spf1 exists:%{i}._spf.mta.salesforce.com -all

The exists mechanism macro-expands a name and does "a DNS A RR lookup (even when the connection type is IPv6)," per Section 5.7. One lookup, authorizing an arbitrarily large and dynamically managed set of addresses. It is elegant. It is also not a trick available to you: exists: is published by the sending vendor, not by the domain owner. You cannot rewrite someone else's include as an exists. What you can do is prefer vendors whose records are cheap, and notice that the ones doing it well tend to do it this way.

How to measure your own record

You can do this by hand. Query your domain's TXT record, then query the TXT record of every include: target it names, then repeat for anything nested inside those. Count every include, a, mx, ptr, exists and redirect you pass through. Note anything that returns NXDOMAIN or an empty answer. Those are your void lookups. (If TXT record mechanics are unfamiliar, our TXT record guide covers the query syntax.)

Windows

1. Open PowerShell from the Start menu.

2. Query your record, then each include target in turn:

Resolve-DnsName -Type TXT example.com
Resolve-DnsName -Type TXT _spf.google.com

macOS

1. Open Terminal from Applications > Utilities.

2. Use dig and watch the status line for NXDOMAIN:

dig TXT example.com +short
dig TXT _spf.google.com

Linux

1. Open a terminal (usually Ctrl+Alt+T).

2. Same commands; install dig first if you need to:

dig TXT example.com +short
sudo apt install dnsutils

For anything with real nesting this gets tedious fast, which is why the DNSai SPF Analyzer exists: it walks the whole include, redirect, a, mx and exists tree, expands macros, maps each host it finds to a known vendor, flags records that are already flattened, and gives you the lookup count against the limit of 10. Every query is live at the moment you run it.

The SPF too many DNS lookups fix, in order of what it costs you

Once you have a real count and know which branch is expensive, work down this list. The order matters, because the options get progressively more expensive to own.

1. Delete what you are not using

Free, and usually enough. Most over-budget records contain at least one vendor the company stopped paying two years ago. dmarcian makes the security argument for this better than the deliverability one:

"unnecessary entries in SPF records create attack surfaces. If an adversary is able to break into (or simply rent) any piece of infrastructure that is listed in an SPF record, that adversary is able to send DMARC-compliant email."

Concluding the Experiment: SPF Flattening, dmarcian, 2 March 2023

Deleting a dead include buys you a lookup and possibly clears a void lookup at the same time. Nothing else on this list does two jobs at once.

2. Move senders onto subdomains

The limit is per evaluation, and each evaluation starts at a name. So each name gets its own budget. Microsoft says it directly: "Move non-Microsoft email services to subdomains. For example, use marketing.contoso.com for marketing email with its own SPF record. Each subdomain has its own 10 lookup budget." The catch, also from Microsoft: "the SPF TXT record for contoso.com doesn't cover marketing.contoso.com; marketing.contoso.com needs its own SPF TXT record." This costs you a conversation with whoever configures the sending vendor, and nothing else. Microsoft's second argument for it is reputational — keeping mail you do not control off your primary domain.

3. Consolidate senders

Four marketing tools that each send mail is four includes. That is a procurement decision more than a DNS one, and nobody wants to hear it. It is also the only fix on this list that makes the problem smaller instead of moving it somewhere else.

4. Flatten, last, deliberately, with a name on it

Flattening replaces an include: with the ip4: and ip6: mechanisms it resolves to. It works because those mechanisms are exempt from the limit. It is also the reason that company at the top of this article lost one message in twenty.

Flattening does not fix your record. It converts a correctness problem you can see into a staleness problem you cannot. The record stops describing your senders and starts describing a photograph of your senders taken on the day you ran the script. Microsoft is explicit that some includes must never be flattened at all: "Microsoft's sending infrastructure uses dynamic IP addresses that change frequently," and it names include:spf.protection.outlook.com as the case in point. For anything you do flatten, Microsoft's instructions read like an operations runbook rather than a fix: replace only vendors with "a stable, documented set of sending IP addresses," "document which include: entries you replaced and when," and "review and update the flattened entries at least quarterly."

dmarcian went further and discontinued its flattening service in 2023, on the grounds that "SPF Flattening attempts to work-around the 'Too Many DNS Lookups' error without addressing the underlying issues that caused the error in the first place." Mailhardener's assessment of the trade is that flattening "merely reduces the network traffic of the receiver with a few hundred bytes at best, while sacrificing reliability."

Three more costs worth knowing before you commit:

  • Over-authorization. Expanding includes into raw CIDR blocks makes it much harder to see what you have authorized. The Lazy Gatekeepers study found 34.7% of domains already allow mail from over 100,000 IP addresses.
  • Record size. The lookup limit and the size guidance are independent constraints, and flattening can move you from breaking one to breaking the other. Section 3.4: the record "SHOULD remain small enough that the results of a query for it will fit within 512 octets." And because DNS caps each TXT character-string at 255 octets, long records get split into several quoted strings, which Section 3.3 requires be concatenated "without adding spaces". A mis-split there silently corrupts a CIDR block.
  • Hosted flattening moves the dependency, it does not remove it. Your SPF record now resolves through a third party at every delivery. That may well be the right trade for you. It is still a trade.

If you flatten, put a review date and an owner on it. A flattened record with no owner is a scheduled outage.

Back to the one in twenty

The company in the opening did not have an SPF problem after they flattened. Their lookup count was fine. Their syntax was fine. Every automated checker they ran would have shown a green tick, right up until the morning a load balancer picked an address that had not existed when they took their photograph.

That is the shape of this whole subject. The limit of 10 is less a limit on your DNS traffic than a limit on how much of your mail infrastructure you are allowed to describe by reference instead of by value. Every fix on the list above is a decision about where you keep that description — in your record, in your vendor's record, or in a subdomain's record. Flattening is the one that moves it into your record and makes you responsible for keeping it true.

So diagnose before you fix, and measure rather than consult a table. Treat any lookup count you read online as a number with an expiry date, this page included three months from now. If you want the wider context on how SPF, DKIM and DMARC fit together once your record is healthy, our email authentication guide covers all three.

Sources

  1. RFC 7208 — Sender Policy Framework (SPF) for Authorizing Use of Domains in Email, Version 1 (Sections 3.3, 3.4, 4.5, 4.6, 4.6.4, 5.2, 5.7, 8, 8.7, 9.1) — IETF / RFC Editor, April 2014. https://datatracker.ietf.org/doc/html/rfc7208#section-4.6.4
  2. RFC 9989 — Domain-based Message Authentication, Reporting, and Conformance (DMARC) (Sections 5.3.5 and 5.3.6) — IETF / RFC Editor, May 2026. Obsoletes RFC 7489. https://www.rfc-editor.org/rfc/rfc9989.html#section-5.3.5
  3. Set up SPF to identify valid email sources for your Microsoft 365 domain — Microsoft Learn (Microsoft Defender for Office 365), 3 July 2026. https://learn.microsoft.com/en-us/defender-office-365/email-authentication-spf-configure
  4. Set up SPF — Google Workspace Admin Help, Google. https://knowledge.workspace.google.com/admin/security/set-up-spf
  5. Lazy Gatekeepers: A Large-Scale Study on SPF Configuration in the Wild — Czybik, Horlboge & Rieck, ACM Internet Measurement Conference 2023 (arXiv preprint, 12 February 2025). https://arxiv.org/abs/2502.08240
  6. Lazy Gatekeepers — Table 2: SPF errors before and after our notification — Czybik, Horlboge & Rieck, arXiv full text, 12 February 2025. https://arxiv.org/html/2502.08240v1
  7. Concluding the Experiment: SPF Flattening — dmarcian, 2 March 2023. https://dmarcian.com/spf-flattening/
  8. Do not flatten your SPF record — Mailhardener. https://www.mailhardener.com/blog/do-not-flatten-spf
  9. Live TXT record measurements, 2026-08-01 — resolved with dig against a single recursive resolver and cross-checked with DNSai's SPF Analyzer: _spf.google.com, _netblocks.google.com, spf.protection.outlook.com, sendgrid.net, ab.sendgrid.net, servers.mcsv.net, mail.zendesk.com, _spf.hubspotemail.net, amazonses.com, spf.constantcontact.com, _spf.atlassian.net, _spf.salesforce.com, spf.intuit.com.

Share this article