16 April 2026 · 13 min read
What "MTA-STS policy not found" actually means
MTA-STS (SMTP MTA Strict Transport Security, defined in RFC 8461) is the mechanism that lets your domain tell sending mail servers: "always use TLS when delivering to my MX hosts, and only trust a certificate that matches these hostnames." Without it, opportunistic STARTTLS can be silently stripped by an attacker sitting between two mail servers, and the message is delivered in clear text. With it, a conforming sender refuses to deliver over an insecure connection.
For any of that to work, a remote sender has to be able to discover and fetch your policy. The discovery sequence is rigid, and every step has to succeed. When people say "the policy is not found", they almost always mean one of five distinct failures, not one. The remote MTA tried to:
- Look up the
_mta-sts.<yourdomain>TXT record in DNS, found nothing usable, and stopped. Or: - Found the TXT record, then tried to open
https://mta-sts.<yourdomain>/.well-known/mta-sts.txt, and the host did not resolve, the TLS handshake failed, the certificate did not match, the path returned a 404, or the file content was malformed.
A sender that cannot complete this discovery does not bounce your mail. It simply behaves as if you have no MTA-STS policy at all and falls back to opportunistic TLS. That is exactly why "policy not found" is so easy to miss: nothing breaks loudly. Your protection just is not there. The only way to catch it is to test the discovery the way a sender does, which is what the MTA-STS checker does.
This article walks the discovery chain in the same order a real sending MTA walks it, so you can find the exact link that is broken.
The discovery chain, in the order a sender follows it
A sending mail server that wants to deliver to example.com and supports MTA-STS does this:
- Query DNS for the TXT record at
_mta-sts.example.com. It must containv=STSv1and anid=tag. - If (and only if) that record exists and its
iddiffers from any cached policy, fetchhttps://mta-sts.example.com/.well-known/mta-sts.txtover HTTPS. Note the fixed hostname (mta-sts.prefix) and the fixed path (/.well-known/mta-sts.txt). Neither is configurable. - Validate the TLS certificate presented by
mta-sts.example.comusing the normal web PKI. A self-signed or mismatched certificate is a hard failure here, by design. - Parse the policy file. It must be a conformant RFC 8461 policy:
version: STSv1, amode, amax_age, and onemxline per MX hostname pattern. - Cache the policy for up to
max_ageseconds, keyed by theidfrom the DNS record. - On the next delivery, compare the live MX hostnames against the
mxpatterns in the cached policy and require TLS with a matching, valid certificate.
The id in DNS is what triggers a refetch. The actual policy lives in the file over HTTPS. This split is the single most misunderstood part of MTA-STS: changing the policy file alone does nothing unless you also bump the id in the TXT record, because senders key their cache on the id and will keep serving the old cached policy until it changes.
Run the MTA-STS checker against your domain now. It reports each of these steps as pass, warn or fail, so the rest of this article maps directly onto its output.
Failure 1: the DNS TXT record is missing or malformed
This is the most common "policy not found". If the _mta-sts TXT record is absent or wrong, the sender never even attempts the HTTPS fetch.
Look up the record directly:
dig +short TXT _mta-sts.example.com
A correct answer looks like this:
"v=STSv1; id=20260623T101500;"
Things that break it:
- No record at all. You published the policy file but forgot the DNS half, or the CNAME/TXT has not propagated. Wait for TTL, then recheck.
- Wrong host. The record must be at
_mta-sts.example.com, with the leading underscore. A record atmta-sts.example.com(no underscore) is the wrong name; that label is for the HTTPS host, not the TXT. - Wrong version token. It must be the exact string
v=STSv1.v=STS1,v=MTASTS1orversion=STSv1are all rejected. RFC 8461 §3.1 requires the record to begin withv=STSv1. - Missing
id. Theidtag is mandatory. Without it, conforming senders treat the record as invalid and skip MTA-STS entirely. Theidis an opaque, max 32-character alphanumeric string; a timestamp like20260623T101500is a common, sensible convention because it is unique and sortable. - TXT split across strings. DNS TXT records are sometimes chunked into multiple quoted strings. That is fine when concatenated, but make sure your provider concatenates them rather than treating them as separate records. Two separate
v=STSv1TXT records at the same name is undefined behaviour and some senders will reject it.
If you delegate hosting to DMARC Engine, the _mta-sts record is a CNAME to a served TXT we control, and we bump the id for you whenever the policy changes. A self-hosted setup means you own that bookkeeping. The verifier in our platform deliberately checks that the resolved record carries the exact expected id, not merely any v=STSv1 record, because a stale or self-hosted record with a different id is functionally "not found" from a sender's point of view.
Failure 2: the HTTPS host does not resolve or does not connect
Once the TXT record validates, the sender opens https://mta-sts.example.com/.well-known/mta-sts.txt. The hostname is fixed: it is your domain with mta-sts. prepended. If that hostname has no DNS record, or has a record pointing somewhere that does not answer on port 443, the fetch fails and the policy is "not found".
Check that the host resolves:
dig +short mta-sts.example.com
Common breakages:
- No A/AAAA/CNAME at
mta-sts.example.com. You set up the TXT record but never created the web host. There is nothing to connect to. - The host resolves but nothing listens on 443. A CNAME to a service that only serves the apex, or an A record to a box where HTTPS is firewalled, both fail. MTA-STS is HTTPS only: there is no HTTP fallback and no other port.
- It is behind a proxy that blocks unknown user agents or non-browser clients. Some WAFs challenge or block requests without browser headers. A sending MTA is not a browser. If your CDN serves a JavaScript challenge instead of the file, the sender gets HTML, not a policy, and parsing fails.
- Redirects. RFC 8461 §3.3 forbids following HTTPS redirects when fetching the policy. If
mta-sts.example.com/.well-known/mta-sts.txtreturns a 301 or 302 (for example, an apex-to-www redirect, or a forced trailing-slash redirect), conforming senders treat it as a failure. The file must be served directly with a200.
Test the actual fetch the way a sender would, with redirect following disabled, so a "successful" redirect does not mask the problem:
curl -sS -o - -w "\nHTTP %{http_code}\n" \
--max-redirs 0 \
https://mta-sts.example.com/.well-known/mta-sts.txt
You want HTTP 200 and the policy body. Anything else (a 30x, a 404, a 403, HTML, a connection refused, a timeout) is a "not found" from MTA-STS's perspective.
Failure 3: the TLS certificate is invalid or does not match
This is the failure people least expect, because the file might be sitting right there and openable in a browser that has clicked through a warning. MTA-STS senders do not click through warnings. The certificate served by mta-sts.example.com must be valid web PKI and must match the hostname mta-sts.example.com.
It fails when:
- The certificate is self-signed or issued by an untrusted CA.
- The certificate is expired.
- The certificate's subject/SAN covers
example.comorwww.example.combut notmta-sts.example.com. A wildcard*.example.comdoes covermta-sts.example.com; a bare apex certificate does not. - The host is served by a platform that does not provision a certificate for that exact subdomain. If you point
mta-sts.example.comat a static host, you must ensure that host issues a cert for that hostname.
Inspect the certificate the host actually presents:
echo | openssl s_client -servername mta-sts.example.com \
-connect mta-sts.example.com:443 2>/dev/null \
| openssl x509 -noout -subject -dates -ext subjectAltName
Confirm mta-sts.example.com appears in the subject alternative names and that notAfter is in the future. If the SAN list does not include the exact mta-sts. hostname, that is your bug: the policy is unreachable to any conforming sender, no matter how perfect the file content is.
Our hosted MTA-STS reports a domain as active only after the HTTPS policy file genuinely serves, which inherently requires the custom-hostname TLS certificate to be live. A matching DNS id alone is treated as "not serving yet", precisely because an unreachable HTTPS host is the most frequent silent failure.
Failure 4: the file path or filename is wrong
The path is not negotiable. It is exactly:
/.well-known/mta-sts.txt
That means:
- The directory is
.well-known(with the leading dot). On many systems dotfiles and dot-directories are hidden or excluded from static serving by default, so the file uploads but never serves. Check your web server explicitly allows serving/.well-known/. - The filename is
mta-sts.txt, lower case, with the.txtextension. Notmta_sts.txt, notmta-sts, notMTA-STS.txt. - It must be reachable at the apex of that path on
mta-sts.example.com, not under a subdirectory.
A 404 here is the giveaway. If dig shows the host resolves and openssl shows a valid certificate but curl returns HTTP 404, the file is in the wrong place or the dot-directory is being filtered. Also confirm the response Content-Type is plain text. RFC 8461 specifies text/plain; some senders are lenient, but serving the file as text/html (because a framework wrapped it) or as an octet stream invites trouble.
Failure 5: the file content is malformed
The file exists, serves over valid HTTPS, at the right path, with a 200, and senders still reject it. Now the problem is the body. The policy must be a conformant RFC 8461 §3.2 document. A correct one looks like this:
version: STSv1
mode: enforce
mx: mail.example.com
mx: *.example.com
max_age: 604800
The rules our checker enforces, mirroring what conforming senders require:
versionmust be exactlySTSv1. A missing or different version fails the whole policy.max_ageis mandatory and bounded. It is in seconds, must be a non-negative integer, and must not exceed31557600(one year). A typical production value is604800(one week). Ifmax_ageis missing or out of range, the policy is rejected. Senders cache for this long, which is why you start low when testing and raise it once stable.modemust beenforce,testingornone. Inenforcemode a sender refuses delivery on a TLS or certificate mismatch. Intestingmode it delivers anyway but reports failures via TLS-RPT.noneeffectively disables the policy.- If
modeisenforceortesting, you need at least onemxline. A policy with nomxentries in an enforcing mode is invalid. Eachmxis a hostname or a single-label wildcard such as*.example.com. - Line endings and structure. Each directive is
key: valueon its own line. Stray blank lines are tolerated, but a missing colon, smart-quote characters pasted from a document, or a UTF-8 byte order mark at the start of the file can all break parsing.
Notice there is one mx line per pattern. They are not comma-separated on a single line. Repeating the mx key is correct and expected.
The trap that breaks more setups than any single failure: MX coverage
Suppose every step above passes. The policy is found, valid, enforcing. Mail still fails to deliver, or worse, delivers but defeats the point. The cause is that the mx patterns in your policy do not cover the MX hosts your domain actually publishes.
A sender in enforce mode compares each live MX hostname against the policy's mx patterns. If a live MX host is not matched by any pattern, the sender treats that host as not authorised by your policy and will not deliver to it over MTA-STS. In practice that can stall mail to that host entirely while the policy is enforced.
Get your live MX:
dig +short MX example.com
Then make sure every host returned is covered by an mx line. Watch for:
- Google Workspace and Microsoft 365 migrations. Your policy lists the old provider's MX patterns; you switched providers and updated DNS but not the MTA-STS file. The new MX hosts are uncovered.
- Wildcard scope.
*.example.commatchesmail.example.combut does not matchmail.mailprovider.net. A single-label wildcard only matches one label deep and only within the exact suffix. If your MX is on a provider's own domain, list that provider's hostname or wildcard explicitly. - Mixed providers. If you have MX records on two different domains (primary and backup), every one of them needs a covering pattern.
The MTA-STS checker resolves your live MX hosts and tests each one against your published policy patterns, flagging any host that is "NOT covered". That single check catches the most damaging misconfiguration, the one that passes every discovery step yet still breaks delivery.
A clean, working example end to end
Here is a complete, consistent setup for example.com whose mail is on its own infrastructure.
DNS, the TXT record at the underscore host:
_mta-sts.example.com. IN TXT "v=STSv1; id=20260623T101500;"
DNS, the policy web host (a CNAME or A/AAAA to something that serves HTTPS with a cert for mta-sts.example.com):
mta-sts.example.com. IN CNAME policy-host.example-cdn.net.
The file served at https://mta-sts.example.com/.well-known/mta-sts.txt, Content-Type: text/plain, HTTP 200, no redirect:
version: STSv1
mode: testing
max_age: 86400
mx: mail.example.com
mx: *.example.com
Note mode: testing and a short max_age: 86400 (one day). That is the correct way to roll MTA-STS out: deploy in testing first, watch the TLS-RPT reports for any legitimate sender that would have been blocked, and only then switch to mode: enforce and raise max_age. When you change the file, change the id in DNS so senders refetch.
While you are deploying TLS reporting, publish a TLS-RPT record so you actually receive the failure data:
_smtp._tls.example.com. IN TXT "v=TLSRPTv1; rua=mailto:tls-reports@example.com"
Without TLS-RPT you are flying blind: you will not know that a sender hit a certificate mismatch or a stale policy until someone complains.
A repeatable diagnostic checklist
When you see "MTA-STS policy not found", run these in order and stop at the first failure:
dig +short TXT _mta-sts.example.comreturns onev=STSv1record with anid. If not, fix DNS.dig +short mta-sts.example.comresolves to something live on 443. If not, create or repair the web host.openssl s_client -servername mta-sts.example.com -connect mta-sts.example.com:443shows a valid, unexpired certificate whose SAN includesmta-sts.example.com. If not, fix the certificate.curl --max-redirs 0ofhttps://mta-sts.example.com/.well-known/mta-sts.txtreturnsHTTP 200, plain text, no redirect. If not, fix the path, the dot-directory serving, or the redirect.- The body has
version: STSv1, a validmax_age(0 to 31557600), amode, and onemxper pattern. If not, fix the file. - Every host in
dig +short MX example.comis covered by anmxpattern. If not, add the missing patterns. - Whenever you change the file, bump the
idin the TXT record so senders drop their cache.
Where this fits in your wider email authentication
MTA-STS protects the transport between mail servers. It does not authenticate the sender, that is SPF, DKIM and DMARC. A common, sensible posture is DMARC at p=reject for sender authentication, MTA-STS in enforce for transport encryption, and TLS-RPT collecting evidence on both. They are complementary, and "policy not found" on MTA-STS is one of the few authentication failures that breaks silently rather than visibly, which is exactly why it lingers.
If you would rather not maintain the underscore TXT, the mta-sts. web host, the certificate, the .well-known file and the id bookkeeping by hand, our hosted MTA-STS product serves all of it from delegated records: you publish two CNAMEs once, and the policy file, certificate and id rotation are handled, with verification that the HTTPS policy genuinely serves before anything is reported as active. You can keep your mail flow exactly as it is, monitor the result, and get an alert if the policy ever stops resolving.
The practical takeaway
"MTA-STS policy not found" is never one problem. It is a five-link chain (DNS TXT, HTTPS host, certificate, file path, file content) plus the MX-coverage trap, and the failure is silent because senders fall back rather than bounce. Walk the chain in order, fix the first broken link, and remember that the id in DNS, not the file, is what makes senders refetch. Run your domain through the MTA-STS checker to see exactly which link fails, and pair the fix with a TLS-RPT record so you keep getting evidence that delivery stays encrypted. If you would rather hand the whole apparatus off, the done-for-you hosted setup removes the bookkeeping that causes most of these failures in the first place.