A void lookup is an SPF term whose DNS query comes back with nothing: a host that doesn’t exist, a domain with no MX, an include whose vendor is gone. RFC 7208 allows two. A receiver that enforces the limit returns permerror at the third. Check your domain below to see every term your SPF record tree looks up, which ones come back void, and which include leads to each.
Last verified 1 October 2026.
You see it as spf=permerror in the Authentication-Results header of a received message, or as a void lookup warning in an SPF checker. The open-source pyspf library, for example, words it:
Void lookup limit of 2 exceeded
RFC 7208, the SPF standard, defines the limit in §4.6.4:
The reason is in §11.1: a record full of names that don’t exist can turn every receiver into a source of DNS queries against a victim, and “mitigation of this class of attack can be accomplished with minimal impact on the deployed base by having the verifier abort processing and return ‘permerror’ (Section 2.6.7) as soon as more than two ‘void lookups’ have been encountered (defined in Section 4.6.4).”
A permerror is not a pass, so DMARC then depends on an aligned DKIM signature. Note the wording: the void limit is a SHOULD, while the 10-lookup limit is a MUST. pyspf enforces it, and so does pypolicyd-spf, the Postfix SPF policy server built on it. Google’s, Microsoft’s and Yahoo’s SPF and sender guidelines don’t mention void lookups, so whether they apply the limit isn’t documented; treat it as enforced somewhere and fix the terms. It is also separate from the 10 DNS lookup limit: a record with four lookups can still fail on three voids.
Sources: RFC 7208 §4.6.4, §5, §5.2, §6.1 and §11.1; pyspf spf.py (MAX_VOID_LOOKUPS = 2, version 2.1.0); Google Workspace Admin Help, Set up SPF; Microsoft Learn, Set up SPF to identify valid email sources for your Microsoft 365 domain; Yahoo, Sender Best Practices (checked 1 October 2026).
Enter the domain in your envelope sender (Return-Path), or your From: domain if you don’t know it. The check runs in your browser against public DNS: it reads your SPF record, follows every include and redirect, and looks up every a, mx and exists name the way a receiver does.
This reads what your DNS publishes right now through Cloudflare’s public resolver, or Google’s when Cloudflare’s fails. A lookup that fails (server error or timeout) shows as “Couldn’t check” and is never counted as void. Names built from macros (%{i} and the like) and ptr depend on the sending server, so they are listed but not looked up. The count treats an a name as void only when it has neither A nor AAAA records; a receiver looks up only A for mail from an IPv4 server, so an AAAA-only name is flagged separately. For the full A–F grade with DMARC, DKIM and MTA-STS, use the DMARC checker.
a, a:host | An address lookup for the name (A for mail from an IPv4 server, AAAA for IPv6). Void when the name doesn’t exist or has no address of that type. |
|---|---|
mx, mx:domain | An MX lookup. Void when the domain has no MX records or doesn’t exist. The receiver then looks up each MX host’s address; pyspf counts an empty answer there as void too, so a retired mail server still listed in MX can add a void per host. |
exists:name | An A lookup. Void when there’s no A record. With a macro (exists:%{i}._spf.example.com) that is how the mechanism says “not this server”, so for unlisted servers it uses up void lookups by design. |
include:, redirect= | A TXT lookup for the target’s SPF record. If the target has no records, that is a void answer, but it is already a permerror on its own: an include whose target has no SPF record returns permerror (§5.2), and so does a redirect (§6.1: “if no SPF record is found, or if the <target-name> is malformed, the result is a ‘permerror’ rather than ‘none’”). |
ptr | Depends on the connecting IP. RFC 7208 §5.5 says it “SHOULD NOT be published”. |
ip4, ip6, all | No DNS query, so never void. |
| Server failure or timeout | Not void. RFC 7208 §5: “if the DNS server returns an error (RCODE other than 0 or 3) or the query times out, the mechanism stops and the topmost check_host() returns ‘temperror’.” |
Every evaluation counts: a dead name that two of your includes both use counts twice. Receivers evaluate terms left to right and stop at the first match, so mail from a server listed before the third void term can still pass. Mail from every server matched later, and every forged message, gets the permerror.
An old include: for an email or marketing service you no longer use, whose SPF record or whole domain is gone. The include target returns nothing: a permerror on its own. Delete the include.
Your own record is clean, but an included record uses a: or mx terms for servers its owner has retired. Those voids count against your domain. The checker shows the include path for each one; if it’s inside a provider’s record, ask the provider to fix it, or replace the include with the provider’s current one from its setup guide.
a or mxMany records still start with v=spf1 a mx, from a time when the website or MX host also sent mail. After a move to Google Workspace or Microsoft 365, the root domain may have no A record of its own, or the a:mail.example.com host was deleted. a mx on a domain with neither is two voids before anything else is counted.
a:include:spf.protection.outlok.com, a:mai.example.com, or a:192.0.2.10, which asks DNS for a host literally named “192.0.2.10” and gets NXDOMAIN. An IP address belongs in ip4:192.0.2.10, which costs no lookup at all.
exists term pointing nowhereAn exists: left over from an old SPF management service, on a name that no longer resolves for anyone.
a:: delete it, or replace it with ip4:/ip6: for servers that still send. a or mx on a domain that has no A or MX records: delete it; your mail provider’s include already covers its servers. IP address in a:: change it to ip4:. Typo: correct the spelling.For example, on a domain whose root has no A or MX record, this record has two void lookups (a and mx) plus an include of a newsletter vendor whose domain is gone, which is a third void answer and a permerror on its own:
v=spf1 a mx include:spf.old-newsletter.example include:_spf.google.com ~all
The fixed record keeps only the live sender:
v=spf1 include:_spf.google.com ~all
From a terminal, dig +short A mail.example.com, dig +short MX example.com and dig +short TXT spf.vendor.example show the same thing: an empty answer is a void lookup, and dig without +short shows status: NXDOMAIN for a name that doesn’t exist.
No. They are separate limits in RFC 7208 §4.6.4. The 10-lookup limit counts every include, a, mx, ptr and exists term and every redirect, whatever the answer. The void limit counts only the lookups that come back empty (NOERROR with no answers) or with NXDOMAIN, and it is two. A record can be well under 10 lookups and still fail on voids. See how to fix too many DNS lookups for the other limit.
Not necessarily. RFC 7208 says implementations SHOULD limit void lookups to two, which is a recommendation, not a MUST like the 10-lookup limit. The open-source pyspf library, used by the pypolicyd-spf policy server for Postfix, enforces it and returns permerror with “Void lookup limit of 2 exceeded”. Google’s, Microsoft’s and Yahoo’s SPF and sender guidelines don’t mention void lookups, so their behavior isn’t documented. Fix void terms even if your mail is delivered today.
No. A void lookup is a NOERROR answer with no records or an NXDOMAIN answer. A server failure or timeout is a DNS error, and RFC 7208 §5 makes that a temperror, not a void lookup. The checker on this page shows those terms as “Couldn’t check” and never counts them.
The included records count too. If a provider’s record uses a, mx or exists terms on names that no longer resolve, those void lookups are charged to every domain that includes it. The checker shows the include path that leads to each void term, so you can tell whether to fix your own record or ask the provider to fix theirs.
Not on its own: the limit is two, and the third void lookup is what makes SPF return permerror. But a void term does nothing useful, costs one of your 10 DNS lookups, and leaves no room if a provider’s record later adds one. Remove it.
If you manage domains for clients or brands, Email Security Signals checks a whole list and returns one row per domain with SPF, DKIM, DMARC and an A–F grade. Void lookups are in spf.voidLookups (with spf.voidLookupsIsMinimum when a lookup failed); one or two show up as a low-severity spf_void_lookups issue naming each void term, and more than two as a high-severity spf_permerror. $0.02 per graded domain ($20 per 1,000); invalid inputs, domains that don’t exist, DNS lookup failures and duplicates are free.
Related: fixing SPF “too many DNS lookups”, fixing “multiple SPF records” and the free DMARC, SPF and DKIM checker.