Skip to content
AdminKit

Fixing SPF "too many DNS lookups" (PermError)

SPF has a hard limit of 10 DNS lookups per check, set by RFC 7208, and going even one over turns the entire check into a PermError — not a soft warning — which means mail can fail authentication even though every listed sender is legitimate. Here's what counts, how to count your own record, and how to fix it.

By AdminKit team, Netbase JSC · Updated October 22, 2026

What the 10-lookup limit actually is

RFC 7208 §4.6.4 caps SPF evaluation at 10 DNS lookups triggered by the mechanisms include, a, mx, ptr, exists, and the redirect modifier. The limit exists so a receiving server never has to chase an unbounded chain of DNS queries just to check one sender’s authorization — and the RFC is explicit that exceeding it is a PermError, a permanent failure of the whole check, not a warning or a partial result. A domain can have every real sending service correctly listed and still fail SPF entirely, purely because the record asks for an 11th lookup.

ip4, ip6, and the trailing all mechanism never cost a lookup — they’re evaluated directly against the connecting IP without touching DNS, so you can add as many of those as needed without affecting the count.

Why this creeps up without anyone noticing

Nobody writes an 11-lookup SPF record on purpose. It accumulates one legitimate addition at a time:

  • Each new sending tool (a CRM, a help desk, an invoicing platform, a newsletter service) typically asks you to add its own include: line.
  • Many of those include: targets are not flat IP lists themselves — they nest further includes, a, or mx mechanisms inside the provider’s own SPF record, and every one of those counts against your 10-lookup budget, not just the provider’s.
  • a and mx mechanisms can be worse than they look: mx triggers one lookup for the MX lookup itself, plus one more for each MX host returned, so a domain with four mail servers listed can spend 5 lookups on a single mx mechanism.

Step 1: count your current lookups

Start with AdminKit’s SPF check, which reads your domain’s SPF record and flags when it’s at or over the limit. To verify manually, use DNS lookup to pull the TXT record, then walk through it mechanism by mechanism:

v=spf1 include:_spf.google.com include:sendgrid.net include:mailgun.org a mx -all

Here, each include is one lookup against your own record (3 so far), a is one more (4), and mx is one plus one per MX host returned. If _spf.google.com itself nests two or three more includes internally — which it does — this record can already be past 10 before you’ve added a single extra service.

Step 2: decide what to cut, not just what to flatten

Before flattening anything, check whether every include: is still needed. A tool you stopped using six months ago that never had its include: removed is the cheapest fix available — it costs nothing in ongoing maintenance, unlike flattening.

For services you do still need, two options remain:

  1. Flatten the record. Resolve the provider’s current include: target to its underlying ip4/ip6 ranges and publish those directly instead of the include:. This removes the lookup entirely, but the provider can change its sending IP ranges without notifying you, so a flattened record needs a recurring re-check — it isn’t a one-time fix.
  2. Consolidate across multiple subdomains or a dedicated SPF-flattening record referenced by a single include:, if you control several domains that share the same sending services — this centralizes the lookup cost in one place instead of repeating it per domain.

Step 3: re-check before publishing

After editing the record, re-run SPF check to confirm the lookup count is now under 10 and that -all (or your intended policy) is still the last mechanism. A changed record only takes effect once DNS has propagated it, which depends on the record’s TTL and intermediate resolver caching, so don’t assume a fix is live the instant you publish it — re-check after waiting out the old TTL.

What happens if you ignore it

A PermError isn’t cosmetic. Many receiving mail servers treat an SPF PermError the same as a hard fail for scoring purposes, even though the actual problem is a record-authoring mistake, not a spoofing attempt. That means legitimate mail from every one of your correctly-listed senders can start landing in spam, or get rejected outright, until the lookup count is back under the limit — with no warning beyond whatever your own SPF or DMARC tooling shows you.

If you’re setting up SPF, DKIM, and DMARC together for the first time, our SPF, DKIM and DMARC explained guide covers how the three records work together before you hit this specific failure mode.

This guide is general information, not a security audit of your specific network — see our disclaimer for what AdminKit's guidance does and does not commit to.

Frequently asked questions

Why does going over the limit cause a total failure instead of just ignoring the extra entries?

RFC 7208 specifies that exceeding 10 DNS-querying mechanisms during evaluation must produce a PermError, treated as a permanent, unrecoverable failure of the whole check — not a partial pass. The limit exists so a malicious or misconfigured record can't force a receiver into unbounded DNS lookups.

Which SPF mechanisms actually count toward the 10-lookup limit?

`include`, `a`, `mx`, `ptr`, `exists`, and `redirect` each count as one lookup (or more, since `mx` and `ptr` can trigger additional lookups per returned record). `ip4`, `ip6`, and `all` never require a DNS lookup, so they're free to add without affecting the count.

Does nesting make the limit worse?

Yes. Each `include:` pulls in another domain's SPF record, and every lookup mechanism inside that nested record also counts against your original 10-lookup budget. A record with three `include`s that each nest two more `include`s can quietly burn through the limit without your own top-level record looking long.

Can I just switch to a `~all` or remove mechanisms to dodge the limit?

Removing mechanisms changes which senders are authorized, so only remove ones for services you no longer use. Switching `-all` to `~all` doesn't address the lookup count at all — PermError happens during evaluation, before the `all` mechanism is even reached, so it doesn't help.

What's "flattening" and is it always the right fix?

Flattening replaces `include:` mechanisms with the static `ip4`/`ip6` ranges they currently resolve to, cutting lookups to zero for that entry. It works, but those IP ranges can change without notice from the provider's side, so a flattened record needs to be re-checked periodically — it trades a lookup-count problem for a maintenance one.

Want AdminKit to watch this for you?

Free checks tell you the state today. Protective DNS and monitoring tell you when it changes.