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=spf1 prefix — 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=spf1 produce permerror (RFC 7208 section 3.2).
  • No subdomain inheritance — example.com SPF does not apply to mail.example.com. Publish a per-subdomain record or an explicit v=spf1 -all for 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:

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.

  1. Missing v=spf1 prefix — 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.com returns a string that does not start with "v=spf1. Fix: prepend v=spf1 + space to the existing body.
  2. 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.
  3. 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.
  4. 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.com shows the verbatim body with escaped whitespace (\t). Fix: re-author the record with single-space separators and no whitespace before v=spf1.
  5. 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--...).
  6. 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.com shows a CNAME at the apex. Fix: remove the apex CNAME; publish A/AAAA records at the apex and the SPF TXT directly on example.com.
  7. Publishing the SPF record at the wrong name — typically at _spf.example.com (which is not a canonical SPF location) instead of example.com. The receiver queries the MAIL FROM domain directly; whatever appears at _spf.example.com is never consulted unless explicitly referenced by a redirect=_spf.example.com in another record. Detection: dig +short TXT example.com returns 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:

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. Inspect a real outbound message with the Email Header Analyzer to confirm the Authentication-Results header reports the expected spf=pass for legitimate senders — confirming both the SPF semantic layer (the right IP authorised) and the SPF syntactic layer (the record parsed cleanly).