SPF DNS Record Syntax Explained: The RFC 7208 Section 3 Encoding Rules (TXT Record Type, v=spf1 Version Tag, 255-Octet Character-String Limit, Single-Record Rule, No Subdomain Inheritance)
The focused DNS-record-level SYNTAX deep dive for SPF (RFC 7208 section 3). SPF is encoded as a single TXT record whose body begins with v=spf1 followed by space-separated mechanism and modifier tokens. This page enumerates every syntactic rule that constrains how that record is published at the DNS layer: the mandatory v=spf1 prefix, the TXT record type (no legacy type 99), the 255-octet character-string limit and multi-string concatenation, whitespace and case-sensitivity rules, the "exactly one SPF record per domain" rule, publication at the apex vs subdomain with no inheritance, wildcards, and comments (none). Includes six worked syntactic examples (minimum record, apex with on-prem + SaaS, multi-string chunking, per-subdomain records, IDN A-label encoding, redirect= delegation) and the seven most common syntax mistakes operators introduce. For operators answering "why does my SPF record need to be a TXT record", "what is the 255-character SPF limit", "can I have two SPF records", "is v=spf1 case sensitive", or "where do I publish my SPF record" queries.
This reference is provided in English. SPF is specified in an English-language RFC (RFC 7208) and the canonical syntactic vocabulary (TXT record, character-string, octet, v=spf1, A-label, U-label, Punycode, ABNF) does not translate. We keep the body in the source language so technical terms remain consistent with the specifications.
TL;DR
SPF is encoded at the DNS layer as a single
TXT record whose body begins with
v=spf1 followed by space-separated
mechanism and modifier tokens, terminating in a
qualified all token. The
DNS-record-level syntax is governed by
RFC 7208 section 3
and the underlying DNS specification in RFC 1035.
- TXT record only — the legacy SPF resource-record type 99 was removed by RFC 7208 section 14.1. Modern receivers query TXT exclusively.
- Mandatory
v=spf1prefix — the version tag is case-insensitive but conventionally lowercase. Records lacking the prefix are not SPF records. - 255-octet character-string limit — per CHARACTER-STRING, not per TXT record. A TXT record can contain multiple character-strings concatenated at lookup time (RFC 7208 section 3.3).
- Exactly one SPF record per domain
— multiple TXT records starting with
v=spf1producepermerror(RFC 7208 section 3.2). - No subdomain inheritance —
example.comSPF does not apply tomail.example.com. Publish a per-subdomain record or an explicitv=spf1 -allfor non-sending subdomains. - No comments — the
#character is a literal character in the SPF body, not a comment introducer. Comments live in the zone-file source, not in the record body.
The Domain Security
Checker surfaces every syntactic drift class
documented on this page: missing
v=spf1 prefix, multiple SPF records,
255-character overflow without chunking, and
subdomains without their own SPF record.
1. What "DNS record syntax" means for SPF (and where the semantics live)
SPF is a layered specification. The SYNTAX
LAYER defines how the policy is encoded at
the DNS: which record type to use, what character
set is valid, how tokens are separated, where to
publish the record, and how DNS-level constraints
(the 255-octet character-string limit, the response
size cap, the lookup count) bound the encoding. The
SEMANTIC LAYER defines what each
token means once it has been parsed: which IPs the
ip4 mechanism authorises, what the
include mechanism does, how the
qualifier prefixes shape the result code.
This page covers the SYNTAX LAYER exhaustively. The semantic layer is covered by the sibling reference pages:
- SPF protocol pillar — the whole-protocol survey (what SPF does and does not do, the seven result codes, alignment, the deployment workflow).
- SPF mechanisms explained — the COMPLETE 8-mechanism + 4-qualifier + 2-modifier semantic reference.
- SPF qualifiers explained — the focused per-qualifier semantic deep dive (Pass / Fail / SoftFail / Neutral).
- include vs ip4 vs a vs mx — the focused 4-way decision view for the IP-providing mechanisms.
Why the syntax layer matters
independently: a syntactically malformed
SPF record produces permerror (RFC 7208
section 4.5), which counts as a non-pass for DMARC
and may cause receiver-side rejection. The syntactic
failure modes documented below — multiple SPF
records, missing v=spf1 prefix,
255-character overflow without chunking, leading
whitespace — are operator-introduced and trivially
avoidable once the syntax is understood. Most "my
DMARC is failing" tickets where the SPF semantics
are correct trace back to a syntactic drift at the
DNS layer.
The canonical SPF DNS record: a
single TXT resource record at the publishing name
whose body is a single character-string (or
multiple character-strings concatenated by the
resolver) beginning with v=spf1,
followed by space-separated mechanism and modifier
tokens, terminated by a qualified
all token. Example minimum
well-formed record:
example.com. IN TXT "v=spf1 -all"
This record publishes "no sender is authorised for MAIL FROM @example.com" — appropriate for a null-sender domain. The seven sections below enumerate every syntactic rule that constrains how this record can be modified to authorise actual senders.
2. The mandatory
v=spf1 version tag (RFC 7208 §4.5)
Rule: Every SPF record MUST begin
with the literal token v=spf1. Records
that do not begin with v=spf1 are not
SPF records and are ignored by every SPF receiver.
This is the version discriminator that distinguishes
SPF from other TXT-based protocols (DKIM signature
records use v=DKIM1; DMARC records use
v=DMARC1; BIMI records use
v=BIMI1; SiteVerification records have
no version tag).
Syntax: The literal four-character
token v=spf1 followed by at least one
space, then the first mechanism or modifier. The
comparison is case-insensitive per RFC 7208 section
4.6.1, so V=SPF1 and
v=SPF1 are also valid — but convention
is strict lowercase v=spf1.
v=spf1 ip4:198.51.100.42 -all # valid V=SPF1 ip4:198.51.100.42 -all # valid (case-insensitive) spf1 ip4:198.51.100.42 -all # NOT a valid SPF record (no v= prefix) v=spf2 ip4:198.51.100.42 -all # NOT a valid SPF record (only v=spf1 is recognised)
Why: the version tag exists to
allow the DNS resolver to discriminate SPF records
from other TXT records at the same name. A modern
domain typically has multiple TXT records at the
apex (one for SPF, one for the
_dmarc subdomain, possibly one for
SiteVerification, one for DKIM at the selector
subdomain, one for MTA-STS, etc.). The
v=spf1 prefix lets the SPF processor
walk the TXT RRset and pick out the SPF record
deterministically.
How to check: Run
dig +short TXT example.com and look
for a quoted string starting with
"v=spf1. Or use the
Domain Security
Checker which surfaces the parsed version tag
explicitly.
When it breaks: the most common
drift class is publishing a record that starts with
a mechanism token directly (e.g.,
ip4:198.51.100.42 -all) — the
v=spf1 prefix was omitted during a
copy-paste. The record is technically a TXT record
but is not an SPF record, so every receiver treats
the domain as having NO SPF policy. The symptom:
DMARC fails because SPF returns "none" rather than
"pass" for legitimate senders.
Common pitfalls: (1) Omitting the
v=spf1 prefix after migrating between
DNS providers. (2) Writing v=SPF1
(valid but non-canonical) and triggering false-flag
warnings from naive SPF validators. (3) Writing
v=spf2 hoping for a "newer" SPF — only
v=spf1 is defined; there is no SPF 2.
(4) Misreading the prefix as v=dkim1
or v=dmarc1 when copying from a
template. (5) Leading whitespace before
v=spf1 — the ABNF in RFC 7208 section
12 forbids leading whitespace, and some strict
receivers reject the record.
3. The TXT record type and the deprecated SPF type 99 (RFC 7208 §3.1, §14.1)
Rule: SPF policies MUST be
published as TXT records (DNS RR type 16). The
experimental SPF resource-record type (RR type 99,
sometimes labelled SPF in older
DNS-management UIs) was deprecated and formally
removed by RFC 7208 section 14.1. Modern receivers
query TXT only and ignore type 99 entirely.
Syntax: the TXT record is published at the canonical DNS name (apex or subdomain) with a quoted body. Example zone-file syntax:
example.com. 3600 IN TXT "v=spf1 ip4:198.51.100.42 -all"
The TTL (3600 seconds shown here) is operator-controlled; lower TTLs (300-600 seconds) are recommended during deployment for fast rollback, higher TTLs (3600-86400 seconds) are appropriate after the record stabilises.
Why: the experimental SPF type 99 was defined in RFC 4408 (the prior SPF specification) as the "future-correct" record type. In practice it never gained universal resolver support, and many DNS providers refused to add UI support for it. RFC 7208 (the current SPF specification, April 2014) section 14.1 formally deprecated type 99 and mandates TXT-only publication. The benefit of using only TXT: every DNS provider supports TXT records, every resolver queries TXT for SPF, and there is no risk of divergence between two records.
How to check: Run
dig +short TXT example.com — the SPF
record appears in the TXT RRset. Also run
dig +short SPF example.com — modern
authoritative servers return NOERROR with no
records (type 99 is unused). If type 99 returns a
record, your DNS provider is still publishing the
legacy type — remove it and keep only the TXT
record.
When it breaks: if you publish the same SPF policy at both TXT and type 99, and later update only one (e.g., the TXT record adds a new SaaS sender, but type 99 still has the old list), the two records diverge. Modern receivers query only TXT and see the updated record; legacy middleboxes that still query type 99 see the stale record. The divergence is undetectable until a receiver behaves unexpectedly.
Common pitfalls: (1) Publishing at both TXT and type 99 from a DNS-management UI that offers both. (2) Forgetting to remove a stale type 99 record after migrating to TXT. (3) Publishing the SPF record at an A or CNAME record by mistake (the record body looks like an IP or hostname). (4) Publishing the SPF record as type ANY (which is not a publication type, only a query type). (5) Using a DNS provider that auto-converts TXT to SPF type 99 silently — switch providers or disable the conversion.
4. The 255-octet character-string limit and multi-string concatenation (RFC 1035 §3.3, RFC 7208 §3.3)
Rule: A single DNS character-string cannot exceed 255 octets (RFC 1035 section 3.3 — one length byte plus up to 255 content bytes). To publish an SPF record longer than 255 characters, split the body across MULTIPLE character-strings in a single TXT resource record; the resolver concatenates the character-strings into the logical record before returning it to the SPF processor (RFC 7208 section 3.3).
Syntax: in BIND zone-file syntax, multiple character-strings appear as multiple quoted strings separated by whitespace inside the TXT record body. Each quoted string is one character-string; the resolver concatenates them by joining the strings end-to-end with no additional whitespace.
example.com. 3600 IN TXT "v=spf1 ip4:198.51.100.0/24 ip4:203.0.113.0/24 "
"ip4:192.0.2.0/24 include:_spf.google.com "
"include:spf.protection.outlook.com -all"
The above publishes ONE TXT record (one resource record) containing THREE character-strings. The resolver returns the three strings; the SPF processor concatenates them into the logical record:
v=spf1 ip4:198.51.100.0/24 ip4:203.0.113.0/24 ip4:192.0.2.0/24 include:_spf.google.com include:spf.protection.outlook.com -all
Why: the 255-octet cap is a DNS protocol-level constraint, not an SPF-specific constraint. Every TXT record in DNS is subject to it. SPF inherits this constraint and adds the multi-string concatenation rule on top so that long SPF policies can still be published. The concatenation happens at the SPF processor (the receiver-side software), not at the resolver — so the resolver returns the multi-string TXT record verbatim, and the SPF processor reassembles the logical record.
How to check: Run
dig +short TXT example.com — if the
SPF record is multi-string, dig prints multiple
quoted strings separated by spaces. Or use
dig TXT example.com (without
+short) to see the full TXT RRset
with character-string boundaries. The Domain
Security Checker surfaces the chunked form
explicitly.
When it breaks: the typical failure mode is attempting to publish a single character-string longer than 255 octets — most DNS providers reject this at zone-file validation time with an error like "TXT string too long". The correct fix is to split into multiple character-strings. The second failure mode: some DNS provider UIs accept a long string from the user but silently truncate it to 255 octets at publish time — the published record is missing the tail mechanisms and produces unexpected permerror or false-negative SPF results.
Common pitfalls: (1) Publishing a single 300-character string without chunking and getting silent truncation. (2) Joining character-strings with extra whitespace ("v=spf1 ip4:..." " ip4:...") that creates a double-space in the concatenated record — the ABNF tolerates this but some strict receivers do not. (3) Splitting mid-token across character-strings ("v=spf1 i" + "p4:198.51.100.42 -all") which produces a malformed token after concatenation. (4) Using a UI that represents the record as a single field and cannot express the multi-string syntax. (5) Mistakenly publishing multiple TXT records starting with v=spf1 thinking that multi-string = multi-record (it does NOT; see §6 below).
5. Whitespace separators and case-sensitivity (RFC 7208 §4.6.1, §12 ABNF)
Rule: Mechanism and modifier tokens are separated by exactly one space character per the ABNF in RFC 7208 section 12. The ABNF tolerates multiple spaces between tokens but receivers may be strict. Tabs, newlines, and other whitespace characters are forbidden inside the record body. Mechanism names, modifier names, and qualifier prefixes are case-insensitive per RFC 7208 section 4.6.1, but convention is strict lowercase.
Syntax: single space between
tokens; no leading or trailing whitespace inside
the record body; all mechanism names
(a, mx, ptr,
ip4, ip6,
exists, include,
all) and modifier names
(redirect, exp) written
lowercase by convention.
v=spf1 ip4:198.51.100.42 include:_spf.google.com -all # canonical v=spf1 ip4:198.51.100.42 -all # tolerated (multiple spaces) v=spf1 ip4:198.51.100.42 -all # MALFORMED (tab between v=spf1 and first mechanism) v=spf1 IP4:198.51.100.42 INCLUDE:_spf.google.com -ALL # valid (case-insensitive) v=spf1 ip4:198.51.100.42 include:_spf.google.com -all # canonical lowercase
Why: the SPF grammar in the RFC
7208 section 12 ABNF defines mechanism tokens as
sequences separated by whitespace. The grammar is
intentionally simple so that the parser can be
written in a few hundred lines of code in any
language. Case-insensitivity is inherited from
the underlying DNS convention (DNS names are
case-insensitive) and applied uniformly to all SPF
keyword tokens. The case-insensitivity DOES NOT
extend to the domain-name arguments inside
mechanisms — include:_SPF.Google.com
works because DNS itself is case-insensitive at
the lookup layer, not because SPF normalises the
case.
How to check: Run
dig +short TXT example.com — the
output is the record body verbatim. Look for any
tab characters (typically rendered as
\t in escaped output), leading
whitespace inside the quoted string, or unusual
case on the mechanism names. The Domain Security
Checker flags non-canonical case as a "style"
warning, not a hard error.
When it breaks: the most common
whitespace drift is copy-pasting a multi-line SPF
record from a documentation source into a
DNS-management UI that does not strip leading
whitespace. The record is published as
v=spf1 ip4:... with leading
spaces; strict SPF processors return
permerror on the malformed prefix.
The second common drift: a tab character creeps in
between tokens (often from a markdown-style
"indented code block" being copy-pasted). The
third common drift: trailing whitespace inside the
quoted body — the receiver may interpret the
trailing space as a separator before a missing
final token.
Common pitfalls: (1) Leading whitespace from copy-paste of indented examples. (2) Tab characters from markdown code blocks. (3) Trailing whitespace producing implicit empty token. (4) Newlines inside the record body (forbidden; the body must be a single line at the SPF layer, even if the TXT record uses multiple character-strings for chunking). (5) Confusing case-insensitivity of keywords with case-insensitivity of domain arguments — both are case-insensitive but for different reasons, and SPF strictly normalises neither.
6. The "exactly one SPF record per domain" rule (RFC 7208 §3.2)
Rule: A domain MUST NOT have more
than one TXT record beginning with
v=spf1. If a receiver retrieves two or
more such records during a single SPF check, the
result is permerror (RFC 7208 section
4.5 explicitly mandates this) and the SPF check
fails. This counts as a non-pass for DMARC and may
cause receiver-side rejection.
Syntax: if a domain needs to authorise multiple sets of senders (the on-prem server, a SaaS sender, a marketing platform), combine them into a single TXT record with multiple mechanism tokens. Do NOT publish separate TXT records.
# WRONG — two SPF records cause permerror: example.com. IN TXT "v=spf1 ip4:198.51.100.42 -all" example.com. IN TXT "v=spf1 include:_spf.google.com -all" # RIGHT — one merged SPF record: example.com. IN TXT "v=spf1 ip4:198.51.100.42 include:_spf.google.com -all"
Why: the SPF processor needs a
deterministic single policy per domain. Multiple
records would create ambiguity: which policy applies
when the sender matches one record but not another?
The RFC removes the ambiguity by mandating
permerror on the multi-record case and
forcing operators to express the policy as a single
document. The constraint also makes SPF auditable:
one record per domain = one document to review =
one decision to make.
How to check: Run
dig +short TXT example.com | grep '^"v=spf1'
— the output should be exactly ONE line. Two or
more lines is the drift case. The Domain Security
Checker surfaces this drift class as "multiple SPF
records detected" with the full list of conflicting
bodies.
When it breaks: the canonical
cause is incremental sender additions where each
addition published a NEW TXT record rather than
EDITING the existing one. Common scenario: the
on-prem mail team publishes
v=spf1 ip4:198.51.100.42 -all; the
marketing team adds Mailchimp by publishing a
second record
v=spf1 include:servers.mcsv.net -all
"to authorise Mailchimp". The result is two
records, permerror, and broken delivery for both
senders. The fix is to merge into one record.
Common pitfalls: (1) Two teams publishing separate SPF records without coordination. (2) An old SPF record left in place after a vendor migration. (3) A CNAME at the apex (forbidden but occasionally present) resolving to a name with its own SPF record, producing apparent multi-record behaviour. (4) Confusing the single-record rule with the single-character-string rule (they are independent — one record can contain multiple character-strings; multiple records is the violation). (5) Believing that publishing a NEW record automatically supersedes an OLD record — DNS has no replacement semantics; the old record persists until explicitly removed.
7. Publication: apex, subdomain, wildcards, and the no-inheritance rule (RFC 7208 §3.4)
Rule: The SPF record applies ONLY
to the queried name. There is NO inheritance from
parent to child in the DNS hierarchy. A record at
example.com does not apply to
mail.example.com or any other
subdomain. The SPF receiver queries the EXACT name
extracted from the SMTP MAIL FROM domain (and
falls back to HELO/EHLO if MAIL FROM is empty per
RFC 7208 section 2.3); whatever it finds at that
name is the SPF record, and whatever is absent is
"no SPF record".
Syntax: publish one TXT record per
name that needs an SPF policy. For non-sending
subdomains, publish an explicit
v=spf1 -all to prevent spoofing.
example.com. IN TXT "v=spf1 ip4:198.51.100.42 -all" mail.example.com. IN TXT "v=spf1 include:_spf.google.com -all" api.example.com. IN TXT "v=spf1 -all" # explicit null news.example.com. IN TXT "v=spf1 include:servers.mcsv.net -all" *.example.com. IN TXT "v=spf1 -all" # wildcard backstop
Why: the no-inheritance rule
forces operators to make an explicit policy
decision for each subdomain. SPF cannot guess
whether mail.example.com is the
legitimate outbound server (requiring its own
sender authorisation) or a marketing subdomain
that sends nothing (requiring
-all). The explicit per-subdomain
record removes the ambiguity. The DNS protocol
itself supports inheritance via NS delegation
but TXT records (and SPF records by extension)
explicitly do not.
How to check: Enumerate every
subdomain that appears as a MAIL FROM in your
outbound traffic and confirm each has an SPF
record. Run dig +short TXT
mail.example.com for each candidate
subdomain — the record should exist if the
subdomain sends mail, or be an explicit
v=spf1 -all if it does not. The
Domain Security Checker surfaces per-subdomain
SPF coverage in its multi-subdomain audit mode.
When it breaks: the canonical
drift class is "spoofable subdomain" — an attacker
sends mail from
billing-confirmation@news.example.com
where news.example.com has no SPF
record. The receiver checks SPF, finds no record,
returns "none", and DMARC then evaluates against
the ORG-DOMAIN policy at
_dmarc.example.com. If DMARC is at
p=none the spoofed mail is delivered.
If DMARC is at p=quarantine or
p=reject the spoof is rejected — but
only because DMARC made up the gap; SPF alone
failed to block. The fix: publish explicit
v=spf1 -all on every non-sending
subdomain.
Wildcards: a wildcard TXT record
at *.example.com matches any subdomain
that has NO other RR set at that name. So
*.example.com IN TXT "v=spf1 -all"
applies to random.example.com only if
that name has no A, AAAA, MX, CNAME, or any other
RR — a constraint most subdomains do not satisfy.
Wildcards are useful as a belt-and-suspenders
backstop but never as the primary coverage
mechanism. The reliable posture is explicit
per-subdomain records for every name that ever
appears in MAIL FROM.
Common pitfalls: (1) Believing
that the apex SPF record covers subdomains (it
does not). (2) Publishing only the apex record and
leaving subdomains spoofable. (3) Relying on
wildcard TXT records that fail to match because
the subdomain has an A or CNAME record. (4)
Publishing
_spf.example.com IN TXT "v=spf1..."
thinking that prefix scopes the record to "SPF" —
in reality nothing queries
_spf.example.com for SPF; the record
must be at the actual name used in MAIL FROM. (5)
Publishing
example.com IN CNAME spf.example.net
at the apex (forbidden by DNS — apex CNAMEs are
invalid) and getting unpredictable behaviour from
the resolver.
8. Six worked syntactic examples
Each example shows a complete TXT record body
illustrating one syntactic feature. The body is
presented as it would appear in a BIND zone file
after the IN TXT directive; the
quoted-string boundaries are explicit.
8.1 Minimum well-formed record (null-sender domain)
The shortest valid SPF record. Appropriate for a domain that sends no mail at all (a parked domain, a domain used only for receiving mail on a separate name, an organisational placeholder).
example.com. IN TXT "v=spf1 -all"
Syntactic features: mandatory
v=spf1 prefix, single space separator,
terminal qualified -all, no
chunking needed (well under 255 octets).
8.2 Apex record authorising on-prem + SaaS senders
A typical mid-size deployment: one on-prem mail
server (ip4 mechanism), Microsoft 365
(include mechanism), and a marketing
SaaS (second include). All under 255
characters so no chunking is needed.
example.com. IN TXT "v=spf1 ip4:198.51.100.42 include:spf.protection.outlook.com include:servers.mcsv.net -all"
Syntactic features: single
character-string, three space-separated
mechanisms, strict lowercase, terminal
-all. Cumulative DNS-lookup count:
1 (M365 include) + 1 (Mailchimp include) = 2; well
under the 10-lookup ceiling.
8.3 Multi-string chunking for a long record
When the record exceeds 255 characters, split into multiple character-strings in a single TXT resource record. The resolver concatenates the strings end-to-end at lookup time.
example.com. IN TXT "v=spf1 ip4:198.51.100.0/24 ip4:203.0.113.0/24 ip4:192.0.2.0/24 "
"ip4:192.0.2.128/26 include:_spf.google.com "
"include:spf.protection.outlook.com include:servers.mcsv.net "
"include:_spf.salesforce.com -all"
Syntactic features: ONE TXT resource record (single RR), FOUR character-strings (each quoted independently), one logical SPF policy after concatenation. Note the trailing space inside the first three character-strings to ensure a space separator survives concatenation — omit the trailing space and the resulting body has no space between the last token of string N and the first token of string N+1, producing a malformed token.
8.4 Per-subdomain records with explicit null on non-sending names
Every subdomain that appears in MAIL FROM needs its
own SPF record. Non-sending subdomains get an
explicit v=spf1 -all to prevent
spoofing. Wildcard backstop for any subdomain not
explicitly enumerated.
example.com. IN TXT "v=spf1 ip4:198.51.100.42 -all" mail.example.com. IN TXT "v=spf1 include:_spf.google.com -all" news.example.com. IN TXT "v=spf1 include:servers.mcsv.net -all" api.example.com. IN TXT "v=spf1 -all" support.example.com. IN TXT "v=spf1 -all" *.example.com. IN TXT "v=spf1 -all"
Syntactic features: five explicit per-name records plus one wildcard. The wildcard fires only for names with no other RRs, which is rare in practice but valuable as a backstop for forgotten subdomains.
8.5 Internationalised domain names (IDN): ASCII vs U-label
Domain names inside SPF mechanisms must use the ASCII A-label form (Punycode), not the UTF-8 U-label form. RFC 7208 section 3.4 inherits this constraint from the DNS protocol itself: DNS labels are bytes, and SPF mechanism arguments are interpreted as DNS names.
# U-label form (FORBIDDEN in SPF mechanisms): example.com. IN TXT "v=spf1 include:_spf.möbel.example -all" # A-label form (correct, Punycode encoding): example.com. IN TXT "v=spf1 include:_spf.xn--mbel-5qa.example -all"
Syntactic features: all
mechanism-argument domain names in A-label
(Punycode xn-- prefix) form. The
SPF receiver will not perform UTF-8 to A-label
translation; the record body must contain
pre-encoded names.
8.6 The redirect=
modifier for cross-organisation policy delegation
The redirect= modifier (RFC 7208
section 6.1) instructs the receiver to abandon the
current record and re-evaluate against the SPF
record of the named target. Useful for centralised
SPF management across many domains owned by the
same organisation. Syntactically, redirect=
is a MODIFIER (key=value form), not a mechanism.
marketing.example.com. IN TXT "v=spf1 redirect=_spf.example.com" sales.example.com. IN TXT "v=spf1 redirect=_spf.example.com" support.example.com. IN TXT "v=spf1 redirect=_spf.example.com" _spf.example.com. IN TXT "v=spf1 ip4:198.51.100.0/24 include:_spf.google.com -all"
Syntactic features: the
redirect= modifier appears at the end
of the record (per RFC 7208 it is processed only
if no mechanism matched, so position matters
semantically but not syntactically). The target
record at _spf.example.com has its
own -all terminator that governs the
delegating records' enforcement. The
delegation reduces zone-file maintenance: update
one _spf.example.com record to update
SPF for all delegating subdomains.
9. The seven most common syntax mistakes
Documented from production drift across hundreds of audits: the syntactic failure modes operators introduce when authoring or editing SPF records. Each is trivially avoidable once the rule from the sections above is understood.
- Missing
v=spf1prefix — typically after migrating between DNS providers. The record looks like a plausible SPF body (ip4:198.51.100.42 -all) but is not an SPF record at all. Every receiver treats the domain as having no SPF policy. Detection:dig +short TXT example.comreturns a string that does not start with"v=spf1. Fix: prependv=spf1+ space to the existing body. - Multiple TXT records starting with
v=spf1— the most common permerror cause. Two teams publish separate records, or an old record is left in place after a vendor migration. Detection:dig +short TXT example.com | grep -c '^"v=spf1'returns 2 or more. Fix: merge into one record with all required mechanisms; remove the extras. - Single character-string longer than
255 octets — the DNS provider may
reject the record at publish time or silently
truncate it. Detection: count characters in
the published record body; if greater than
255 and no multi-string chunking appears in
dig TXT example.com, the record is broken. Fix: split into multiple character-strings (see §4 above), or reduce record length by flattening. - Tab character or leading whitespace
inside the record body — typically
from markdown-style code blocks being
copy-pasted. The strict SPF processor returns
permerror. Detection:
dig TXT example.comshows the verbatim body with escaped whitespace (\t). Fix: re-author the record with single-space separators and no whitespace beforev=spf1. - UTF-8 / U-label domain names inside
mechanisms — non-ASCII characters in
include / a / mx mechanism arguments. The SPF
receiver does not translate U-labels to
A-labels. Detection: any non-ASCII byte in
the record body. Fix: pre-encode every
internationalised domain in Punycode A-label
form (
xn--...). - Apex CNAME pointing to a name with its
own SPF record — DNS forbids apex
CNAMEs but some operators publish them
anyway via DNS-provider-specific "ALIAS" or
"ANAME" records. The result is unpredictable
SPF behaviour because the resolver may return
the CNAME target instead of querying TXT at
the apex. Detection:
dig example.comshows a CNAME at the apex. Fix: remove the apex CNAME; publish A/AAAA records at the apex and the SPF TXT directly onexample.com. - Publishing the SPF record at the
wrong name — typically at
_spf.example.com(which is not a canonical SPF location) instead ofexample.com. The receiver queries the MAIL FROM domain directly; whatever appears at_spf.example.comis never consulted unless explicitly referenced by aredirect=_spf.example.comin another record. Detection:dig +short TXT example.comreturns no SPF record. Fix: publish the SPF TXT at the actual name used in MAIL FROM.
10. FAQ
- Why does my SPF record need to be published as a TXT record and not a SPF record?
- The legacy SPF resource-record type (RR type 99) was experimentally defined in RFC 4408 but never gained universal resolver support. RFC 7208 section 14.1 formally deprecated and removed it: modern SPF deployments publish the policy as a TXT record only. The TXT record type (DNS RR type 16) is universally supported by every authoritative server and resolver, and every SPF receiver queries TXT only. Publishing the same policy at both record types is unnecessary, doubles DNS-query cost on every lookup, and risks divergence between the two records if one is updated and the other is forgotten. The canonical action is: publish as TXT, never as type 99.
- What is the 255-character SPF record limit and how do I work around it?
- The 255-octet limit is a property of a single DNS character-string (RFC 1035 section 3.3), not the SPF record as a whole. A TXT record can contain MULTIPLE character-strings, each up to 255 octets, which the resolver concatenates at lookup time (RFC 7208 section 3.3). So to publish an SPF record longer than 255 characters, split it across multiple quoted strings in your DNS zone-file. The practical ceiling: a well-engineered SPF record at the 10-DNS-lookup limit (RFC 7208 section 4.6.4) is typically under 1000 characters, so the working ceiling is dictated by mechanism count, not byte count. See SPF 10 DNS lookup limit and SPF flattening for the canonical remediation playbook.
- Can I have two SPF records on the same domain?
- No. RFC 7208 section 3.2 explicitly forbids multiple TXT records starting with v=spf1 on the same name. If a receiver retrieves two TXT records starting with v=spf1, the SPF check produces permerror (RFC 7208 section 4.5), which counts as a non-pass for DMARC and may cause receiver-side rejection. The most common cause is accidentally publishing one SPF record for the on-prem server and a second for a SaaS provider. The fix: merge into a single TXT record with multiple include / ip4 / ip6 mechanisms. The Domain Security Checker surfaces this drift class as "multiple SPF records detected".
- Is the SPF v=spf1 prefix case sensitive?
- No. RFC 7208 section 4.5 specifies v=spf1 as the canonical prefix, but the comparison is case-insensitive per RFC 7208 section 4.6.1. So V=SPF1, v=SPF1, and V=spf1 are all valid and produce the same behaviour. Convention is strict lowercase v=spf1 for consistency with the RFC examples. The mechanism names (a, mx, ptr, ip4, ip6, exists, include, all) are similarly case-insensitive but conventionally written lowercase. Best practice: write everything lowercase for consistency with every published authoritative source.
- Where should I publish my SPF record — at the apex or per subdomain?
- The SPF record applies ONLY to the queried name with no inheritance from parent to child (RFC 7208 section 3.4). So an SPF record at example.com governs mail with MAIL FROM example.com but NOT mail with MAIL FROM mail.example.com. The canonical posture is: (1) Publish a complete SPF record at the apex covering every sender that uses the apex as MAIL FROM. (2) For every subdomain that sends mail, publish an SPF record at that subdomain. (3) For every subdomain that does NOT send mail, publish an explicit v=spf1 -all to prevent spoofing. The lack of inheritance is the most common deployment mistake.
- Are there comments in SPF records?
- No. SPF has no comment syntax. The # character that introduces comments in many DNS zone-file editors is a comment in the ZONE FILE (consumed by the DNS server software), but the resulting TXT record body — which is what the SPF receiver sees — contains no comments at all. The ABNF grammar in RFC 7208 section 12 has no comment production. So you cannot write v=spf1 ip4:198.51.100.42 # office IP -all inside the record body. The workaround: keep comments in the zone-file source (in the line that publishes the TXT record but BEFORE the record-body quoting), in a separate documentation repository, or in a config-management commit message.
- Does the SPF record have a maximum size?
- There is no explicit byte-size cap in RFC 7208 — but there are three effective caps that constrain how large an SPF record can practically be. (1) The 255-octet DNS character-string limit. (2) The DNS response size limit (UDP traditionally 512 bytes, EDNS0 extends to 4096 bytes or more). (3) The 10-DNS-lookup ceiling of RFC 7208 section 4.6.4. In practice the lookup ceiling is reached long before the byte ceiling, so the working maximum is dictated by mechanism count rather than byte count. A well-engineered SPF record at the 10-lookup ceiling is typically under 1000 characters.
- What is the difference between a TXT character-string and a DNS resource record?
- A DNS resource record (RR) is the unit of DNS storage — a single TXT RR at a name. A TXT RR contains one OR MORE character-strings (RFC 1035 section 3.3), each a length-prefixed sequence of up to 255 octets. The resolver concatenates the character-strings into the logical TXT record when answering a query. So a "long" SPF record is technically one TXT resource record containing multiple character-strings, not multiple TXT records. Multiple TXT records starting with v=spf1 is a violation of RFC 7208 section 3.2 and produces permerror; a single TXT record with multiple character-strings is valid and is the canonical way to publish an SPF record longer than 255 characters.
- Can SPF use wildcard DNS records?
- Yes but with caveats. A wildcard TXT record at *.example.com matches any subdomain that has no explicit RR set at that name. So v=spf1 -all at *.example.com applies to mail.example.com and api.example.com only IF those names have no other RRs. DNS wildcards stop matching as soon as ANY RR exists at the name, so a subdomain with its own MX record will NOT inherit the *.example.com SPF record. The practical implication: wildcards are unreliable for SPF coverage of "all my non-mail-sending subdomains" because the wildcard only fires when the subdomain has no other RRs, and most subdomains do have at least A or CNAME. The reliable posture is: publish an explicit v=spf1 -all on every subdomain that does not send mail.
12. Audit your SPF record syntax now
The fastest way to confirm your SPF record is syntactically well-formed at the DNS layer:
- Confirm the TXT record exists at the
right name. Run
dig +short TXT example.com— the SPF record should appear as a quoted string starting with"v=spf1. If no such string appears, the record is missing. - Verify exactly one SPF record.
Run
dig +short TXT example.com | grep -c '^"v=spf1'— the count should be exactly 1. Any value greater than 1 is a §6 violation producing permerror. - Check character-string chunking for
long records. Run
dig TXT example.com(without+short) — if any character-string is longer than 255 octets, the publish failed or was silently truncated. Re-author with explicit chunking. - Audit per-subdomain coverage. For every subdomain that appears in your outbound MAIL FROM traffic, confirm an SPF record exists. Use the Domain Security Checker's multi-subdomain audit mode for the full enumeration.
- Inspect a real outbound message
with the
Email Header
Analyzer to confirm the
Authentication-Results header reports the
expected
spf=passfor legitimate senders — confirming both the SPF semantic layer (the right IP authorised) and the SPF syntactic layer (the record parsed cleanly).