# Fix SPF PermError: too many DNS lookups

Fix SPF lookup-limit errors by tracing nested includes, removing obsolete senders and reviewing flattening limits. Verify DNS changes and real sender results.

Canonical: https://tamperbox.dev/guides/fix-spf-too-many-dns-lookups

Updated: 2026-10-18

Reviewed: 2026-10-17

An SPF record can look short and still exceed the lookup budget because a provider include expands into more includes. Diagnose the branch responsible before changing authorization.

## Find the lookup that exceeds the budget

Open [SPF Checker](https://tamperbox.dev/spf-checker) with the sending domain. Expand its include tree and check whether inspection completed. The counter estimates lookup-causing terms; it is not a sender-IP evaluation. Macros and failed branches can leave only a lower bound.

The built-in harbor-supply.example fixture is synthetic and needs 11 lookups. Use it to practice reading the tree without changing production DNS. In a real record, note which top-level sender contributes the expensive branch.

## Reduce the work without dropping legitimate senders

Remove includes only after confirming that the service no longer sends for this domain. Separate an appropriate sender onto a subdomain with its own authentication configuration. Review a compatible flattening proposal only when you can maintain the resulting address snapshot as the provider changes its ranges.

Keep one SPF record at a name. Splitting a long record into TXT strings is different from publishing multiple SPF records and does not reduce lookup cost.

## Verify the change

Save the old record for rollback, publish the reviewed replacement through your DNS provider, then query it again after relevant caches expire. Send representative test messages from every retained service and inspect receiver results with [Email Header Analyzer](https://tamperbox.dev/email-header-analyzer). A lower static count does not by itself prove that each sender passes.

## Common mistakes

Do not remove a provider merely because its branch is expensive. Do not interpret an incomplete tree as a passing count. Avoid copying a sample record or stale flattened ranges into production.

## Worked example: 11 lookups becomes 9

Use the built-in `harbor-supply.example` fixture. Its record is:

```text
v=spf1 include:_spf.inboxcloud.example include:send.bulkpost.example include:mail.ticketdesk.example include:spf.officesuite.example ip4:198.51.100.24 ~all
```

The four include branches cost 4, 2, 3 and 2 lookups respectively, including their nested records. The `ip4` and `all` mechanisms cost zero. Total: **11**, one above the SPF limit of 10.

Suppose your sender inventory confirms the office-suite service is retired. Removing only its include gives:

```text
v=spf1 include:_spf.inboxcloud.example include:send.bulkpost.example include:mail.ticketdesk.example ip4:198.51.100.24 ~all
```

The remaining static count is **9**: 4 + 2 + 3. That leaves one lookup-causing term of headroom. The address ranges and domains here are synthetic and must not be published as a real policy. For a live change, verify that the removed service has no remaining senders and test all three retained paths. Keeping its include may be necessary even though it makes the count expensive.

## Reference

[SPF specification and lookup limits, RFC 7208](https://www.rfc-editor.org/rfc/rfc7208).
