An “SPF record missing” finding means the scanner queried your domain’s DNS and found no TXT record beginning with v=spf1, so receiving mail servers cannot tell which hosts may send mail for it. Fix it by publishing one TXT record that lists every legitimate sender and ends in -all or ~all. Parked domains publish v=spf1 -all.
What the scanner is actually detecting
Sender Policy Framework (SPF, RFC 7208) is a DNS TXT record that names the IP addresses allowed to use your domain in the SMTP envelope sender (MAIL FROM). This finding usually arrives under one of these titles:
| Scanner | Finding title | What triggers it |
|---|---|---|
| SecurityScorecard | SPF Record Missing | No SPF record found for a domain attributed to your organization |
| Nuclei (spf-record-detect) | SPF Record – Detection | An info-level presence check: it queries TXT for the target FQDN and matches the word v=spf1. A “missing” result is the absence of a match. |
| Nuclei (spoofable-spf-records-ptr) | Spoofable SPF Records with PTR Mechanism | The TXT answer contains both v=spf1 and the literal string ” ptr ” (with spaces) |
| Nessus (plugin 31658) | DNS Sender Policy Framework (SPF) Enabled | Informational; reports that SPF is present, not that it is missing |
Two details matter. The Nuclei template checks the exact name you scanned, so a target of www.example.com tests www, not the apex. And the Nessus plugin is a positive signal: if a report builds “SPF missing” from its absence, check whether the DNS query simply failed.
Real-world risk
With no SPF record, receivers evaluating mail that claims your domain in MAIL FROM get the SPF result none (RFC 7208, section 4.5). They then fall back on DKIM, DMARC and reputation. Nothing on your servers is exposed; the risk is impersonation and deliverability:
- Spoofed envelope senders using your domain cannot be rejected on SPF grounds.
- DMARC needs an aligned SPF or DKIM pass. Without SPF, DMARC depends entirely on DKIM, which some of your senders may not sign with.
- Your legitimate mail loses an authentication signal and is more likely to be filtered as spam.
SPF alone does not protect the visible From header that users read; that is DMARC’s job. Treat this as a low-effort, worthwhile hygiene fix rather than an emergency.
For the ptr variant, RFC 7208 section 5.5 states the mechanism “SHOULD NOT be published” because it is slow, less reliable during DNS errors and loads the .arpa servers. It authorizes any IP whose forward-confirmed reverse DNS name falls inside your domain, including subdomains, which is often wider than the set of mail servers you meant to approve.
How to confirm it
On Linux or macOS, query the name the scanner reported:
# All TXT records at the apex
dig +short TXT example.com
# Only SPF records (more than one line here is a separate error)
dig +short TXT example.com | grep -i 'v=spf1'
# Ask the authoritative servers directly, bypassing caches
dig +short NS example.com
dig @ns1.example-dns.net TXT example.com +norecurse
On Windows:
Resolve-DnsName -Name example.com -Type TXT -DnsOnly |
Where-Object { ($_.Strings -join '') -like 'v=spf1*' } |
ForEach-Object { $_.Strings -join '' }
nslookup -type=txt example.com
If an SPF record exists, look for ptr or ptr:domain in it, count the SPF records (there must be exactly one), and check that it is a TXT record. RFC 7208 section 3.1 says SPF must be published as TXT only; the old SPF record type (99) is not used.
How to fix it
Step 1: inventory every sender
List everything that sends mail with your domain in the envelope sender: Microsoft 365 or Google Workspace, on-premises Exchange or Postfix relays, ticketing and CRM platforms, invoicing and marketing services, web servers sending form mail, and multifunction printers that relay directly. A DMARC reporting service helps find the ones nobody remembers. Microsoft recommends putting third-party bulk senders on a subdomain with its own record.
Step 2: choose the record
| Scenario | TXT value at the domain |
|---|---|
| Microsoft 365 only (commercial and GCC) | v=spf1 include:spf.protection.outlook.com -all |
| Microsoft 365 GCC High or DoD | v=spf1 include:spf.protection.office365.us -all |
| Google Workspace only | v=spf1 include:_spf.google.com ~all (Google’s documented value) |
| On-premises server plus Microsoft 365 | v=spf1 ip4:203.0.113.10 include:spf.protection.outlook.com -all |
| Domain never sends mail | v=spf1 -all |
Qualifiers: -all is fail, ~all is softfail, ?all is neutral. Microsoft recommends -all when DKIM and DMARC are also deployed; Google documents ~all. A common path is ~all while you confirm the sender list, then -all. Never publish +all, which authorizes everyone.
Step 3: publish it
BIND zone file (increment the SOA serial first):
@ 3600 IN TXT "v=spf1 ip4:203.0.113.10 include:spf.protection.outlook.com -all"
; a record longer than 255 characters is split into strings that are
; joined WITHOUT spaces, so keep the space inside a string
@ 3600 IN TXT ( "v=spf1 ip4:203.0.113.10 ip4:198.51.100.0/24 "
"include:spf.protection.outlook.com -all" )
named-checkzone example.com /path/to/db.example.com
rndc reload example.com
Windows DNS Server (run against the zone that answers the Internet, not only an internal split-horizon copy):
Get-DnsServerResourceRecord -ZoneName "example.com" -Name "@" -RRType Txt
Add-DnsServerResourceRecord -ZoneName "example.com" -Name "@" -Txt `
-DescriptiveText "v=spf1 ip4:203.0.113.10 include:spf.protection.outlook.com -all" `
-TimeToLive 01:00:00
Hosted DNS (registrar, Cloudflare, Route 53 and similar): add a TXT record with host @ or blank, depending on the provider. Microsoft 365 has no portal or cmdlet for SPF; it is always set at your DNS host. Microsoft suggests a TTL of at least 3600 seconds.
Step 4: fix structural problems
- Replace ptr. Identify the servers it was authorizing (your outbound relays) and list them explicitly with ip4: or ip6:, or use a or mx if those names really are your senders.
- One record per name. If a vendor told you to “add an SPF record”, merge its include: into your existing record. Two records produce permerror.
- Stay under 10 DNS lookups. include, a, mx, exists and redirect each count, nested includes count too, while ip4, ip6 and all do not. Exceeding the limit is also permerror.
- Cover subdomains that send. SPF is not inherited; mail.example.com or marketing.example.com need their own records. For parked domains, Microsoft suggests a wildcard TXT v=spf1 -all at *.example.net if your DNS host supports it (a wildcard only answers for names with no records of their own).
- Remove stale includes. An include: for a vendor you dropped keeps authorizing whoever controls that domain later, a risk covered in our guide to auditing a sprawling domain portfolio.
How to verify the fix and rescan
- Query the authoritative servers first, then a public resolver. Resolvers may cache the earlier “no record” answer for the zone’s negative TTL (from the SOA), so an older view can linger briefly.
- Confirm exactly one line comes back from dig +short TXT example.com | grep -i ‘v=spf1’ and that it contains no ptr.
- Send a test message from each sender to an external mailbox and read the Authentication-Results header. You want spf=pass.
- Rerun the scanner. For Nuclei: nuclei -u example.com -id spf-record-detect,spoofable-spf-records-ptr. The first should now match and the second should not. If Nessus scans the domain, plugin 31658 should now appear. External rating platforms refresh on their own schedule.
What can break and how to roll back
- A forgotten sender fails SPF. With -all, its mail may be rejected or junked. Add the sender, or relax to ~all while you investigate.
- permerror from duplicates or too many lookups. Receivers may treat this as a failure, which can be worse than having no record.
- Forwarded mail. A forwarding server’s IP is not in your record, so forwarded messages can fail SPF. DKIM signatures survive forwarding, which is one reason Microsoft pairs SPF with DKIM and DMARC.
Record the old value before any change. Rolling back means restoring the previous value, not deleting the record, since deletion returns you to none. On Windows DNS:
Remove-DnsServerResourceRecord -ZoneName "example.com" -RRType Txt -Name "@" `
-RecordData "v=spf1 ip4:203.0.113.10 include:spf.protection.outlook.com -all" -Force
Then add the previous value back with Add-DnsServerResourceRecord. On BIND, restore the zone file, increment the serial and run rndc reload. Old answers persist in caches for up to the record’s TTL.
Common false positive reasons
- Wrong name scanned. The scan targeted a web hostname rather than the mail domain. The finding is technically true for that name; publishing v=spf1 -all there is cheap if it never sends mail.
- Split-horizon DNS. An internal scanner resolved an internal zone that lacks the record the public zone has.
- Query failure. A timeout or SERVFAIL looks the same as “no match” to a presence check.
- Template matching limits. The ptr template matches the literal ” ptr ” with spaces on both sides, so it misses ptr:example.com and a trailing ptr. The match runs on the TXT answer, so text in another TXT record can contribute. Read the actual record before acting.
- Not your domain. A rating platform attributed a domain you do not own or no longer use. Dispute the attribution with the vendor.
While you are in the zone, check for records pointing at deprovisioned resources, described in our guide to dangling DNS and subdomain takeover.
FAQ
Does a domain that never sends email need an SPF record?
Yes. Publish v=spf1 -all so receivers know no host is authorized. Microsoft documents exactly this for parked domains.
Should I use -all or ~all?
Microsoft recommends -all alongside DKIM and DMARC; Google documents ~all. Start with ~all if your sender inventory is uncertain, then tighten.
Can I add a second SPF record for a new vendor?
No. Multiple SPF records at one name cause permerror. Merge the vendor’s include: into the existing record, or give the vendor a subdomain.
Why is the ptr mechanism deprecated?
RFC 7208 says it should not be published: it is slow, unreliable when DNS errors occur and burdens reverse DNS servers. Use explicit ip4, ip6 or include entries instead.
Tracking this finding across many domains
SPF findings tend to reappear across dozens of domains and subdomains, often reported by more than one tool. SITEY, a self-hosted vulnerability management platform, imports findings from 16 scanners (Nessus results come in by uploading an exported .nessus file) and merges duplicates per scanner, not across scanners, so the same domain flagged by two tools remains two items to close.
Sources
- RFC 7208: Sender Policy Framework (SPF) for Authorizing Use of Domains in Email, Version 1
- Microsoft Learn: Set up SPF to identify valid email sources for your Microsoft 365 domain
- Google Workspace Admin Help: Set up SPF
- Tenable: Nessus plugin 31658, DNS Sender Policy Framework (SPF) Enabled
- ProjectDiscovery nuclei-templates: spf-record-detect and spoofable-spf-records-ptr