Skip to content
notspoofed.comSPF · DKIM · DMARC

Publishing an MTA-STS policy safely

By · Published

MTA-STS tells senders that mail to your domain must be encrypted, must go to an MX host you name, and must present a valid certificate for that name. Without it, TLS between mail servers is opportunistic: a sender tries, and if anything goes wrong it delivers in plain text rather than not at all. An attacker who can strip the STARTTLS capability from the connection gets exactly that outcome.

The catch is that MTA-STS is a promise you cannot easily take back. Publish mode: enforce with a mistake in it and senders stop delivering to you, for as long as your max_age says to cache it. This page is about getting there without that happening.

The three pieces

MTA-STS is unusual in needing DNS, a web host and a certificate all at once.

  1. A DNS TXT record at _mta-sts.example.com, which only announces that a policy exists and gives it an id.
  2. A policy file served over HTTPS at https://mta-sts.example.com/.well-known/mta-sts.txt.
  3. A valid certificate on that mta-sts subdomain — a web certificate, entirely separate from the one on your mail server.

Three things in three places, maintained by potentially three different processes. That is the whole reason MTA-STS breaks as often as it does.

Start in testing mode. Always.

The DNS record first:

_mta-sts.example.com.  TXT  "v=STSv1; id=20260811120000"

The id is an opaque string you change whenever you change the policy — it is how senders know to re-fetch instead of using their cached copy. A timestamp is the conventional choice because it is guaranteed to increase.

Then the policy file, in testing mode:

version: STSv1
mode: testing
mx: mail.example.com
mx: mail2.example.com
max_age: 604800

mode: testing means senders evaluate the policy, report failures via TLS-RPT, and deliver anyway. It is the whole point of the mode: you learn what would have broken without breaking it. Leave it there for a couple of weeks, read the reports, and only then switch to enforce.

This is why TLS-RPT comes first. Testing mode with no reporting configured tells you nothing at all — you have published a policy that is deliberately not enforced and arranged to never hear about it.

The four things that go wrong

CRLF line endings

The policy file must use CRLF line endings. This is the single most common cause of a policy that fetches fine and does not work, and it is invisible: the file looks perfect in every editor and browser. A file saved by a Linux text editor has LF endings and will not parse.

Check it with file mta-sts.txt — you want “ASCII text, with CRLF line terminators”. Or convert with unix2dos. A policy that fails to parse is treated as no policy at all, so you get sts-policy-invalid and no protection.

The MX list drifting

Every hostname in your MX records must appear in the policy. Miss one and mail through that host only fails, which presents as intermittent breakage that is maddening to diagnose — some mail arrives, some does not, depending on which MX the sender picked.

A wildcard entry like mx: *.example.com is legal and covers a whole provider’s range. When you change mail providers, the policy has to change with the MX records, and ideally before them.

Redirects on the policy URL

The policy must be served directly at the well-known path, with Content-Type: text/plain. Senders do not follow redirects. An HTTP to HTTPS upgrade at your edge, or a canonical-host redirect from mta-sts.example.com to www.example.com, produces sts-policy-fetch-error and the policy is simply not applied.

Test it the way a sender does, without following anything:

curl -i https://mta-sts.example.com/.well-known/mta-sts.txt

You want a plain 200 on the first response, not a 301.

The certificate nobody monitors

The mta-sts subdomain is a web host that gets set up once and then falls outside whatever monitoring covers your main site. When its certificate expires — and it will — senders can no longer trust the policy, and your MTA-STS protection silently stops applying. That is sts-webpki-invalid, and it is the failure most likely to be running right now on a domain that set this up two years ago.

Add that hostname to certificate expiry monitoring the day you create it.

Moving to enforce

When the reports have been clean for a couple of weeks, change mode: testing to mode: enforce and update the id in DNS so senders re-fetch.

Consider a shorter max_age to begin with — a day rather than a week. It is the length of time a sender caches your policy, which is also the length of time you are stuck with a mistake. Once it has been stable for a month, raise it; a longer cache is more resistant to an attacker interfering with the DNS record.

After the switch, watch for certificate-host-mismatch specifically. Under testing mode it was reported and ignored; under enforce the same failure means mail is refused.

Checking it worked

The DNS record and the policy file must agree on the id, the MX list must match your actual MX records, and the file must parse. The SPF, DKIM and DMARC checker reports whether the record exists and whether the policy is reachable, and the TLS-RPT analyzer is where you see what senders actually experienced — which is the only measure that counts.

Related guides