MTA-STS: A Complete Setup Guide (with TLS-RPT and DANE context)
Publish an HTTPS-backed SMTP TLS policy the way receiving MTAs expect to consume it.
Loading...
Publish an HTTPS-backed SMTP TLS policy the way receiving MTAs expect to consume it.
SMTP was designed before TLS. STARTTLS was bolted on to negotiate TLS mid-conversation, which is elegant but trivially defeatable: an attacker in the middle simply strips the server's 'STARTTLS' capability from the EHLO response, and both MTAs fall back to plaintext. You will never know - the servers don't complain.
MTA-STS (RFC 8461) solves this by letting receiving domains publish a policy that says 'TLS is mandatory, these are my MX hosts, don't accept anything else.' Senders fetch the policy over HTTPS (with Web PKI, not DNSSEC) and refuse to downgrade. Combined with TLS-RPT (RFC 8460), senders also send you a daily report on every TLS failure.
RFC 8461 requires the policy to live at exactly mta-sts.<domain>, over HTTPS, with a valid certificate the sender's TLS trust chain will accept. You need to stand up the subdomain and host one static file.
Pick the hosting pattern that fits your stack:
version: STSv1
mode: testing
mx: mx1.example.com
mx: mx2.example.com
max_age: 86400The mx lines must be exact matches for your MX record contents - not CNAME targets, not wildcards unless you genuinely run wildcard MX. max_age is how long senders cache the policy; start at 86400 (one day) so changes propagate quickly during the rollout.
server {
listen 443 ssl http2;
server_name mta-sts.example.com;
ssl_certificate /etc/letsencrypt/live/mta-sts.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mta-sts.example.com/privkey.pem;
location = /.well-known/mta-sts.txt {
default_type text/plain;
alias /var/www/mta-sts/mta-sts.txt;
add_header Cache-Control "max-age=300" always;
}
# 404 for everything else - the subdomain serves exactly one file.
location / { return 404; }
}1. Create a Pages project, add a file at public/.well-known/mta-sts.txt
2. Assign custom domain mta-sts.example.com
3. Cloudflare issues the edge certificate automatically
4. Verify: curl https://mta-sts.example.com/.well-known/mta-sts.txt_mta-sts.example.com. IN TXT "v=STSv1; id=20260417T000000Z"The id is an arbitrary string, but must change every time you change the policy body. Senders cache by id - a new policy body with the old id is ignored. A UTC timestamp (YYYYMMDDTHHMMSSZ) is a widely used convention that prevents collisions and makes change history obvious.
TLS-RPT is the feedback loop. Without it, you have no visibility into whether your policy is actually being honoured or whether TLS is silently failing for some senders.
_smtp._tls.example.com. IN TXT "v=TLSRPTv1; rua=mailto:[email protected]"Reports arrive as gzipped JSON attachments (one per sender per day). You can self-host a parser or use a processor (URIports, Postmark, Valimail, Red Sift).
After two weeks of testing mode with clean TLS-RPT reports, update the policy body to mode: enforce, bump the id, and re-publish.
version: STSv1
mode: enforce
mx: mx1.example.com
mx: mx2.example.com
max_age: 604800_mta-sts.example.com. IN TXT "v=STSv1; id=20260501T000000Z"Once in enforce, conformant senders (Gmail, Microsoft 365, iCloud and others) will refuse delivery if your TLS breaks or your MX changes without a corresponding policy update.
DANE (DNS-Based Authentication of Named Entities) pins the TLS certificate of your MX hosts in DNS - TLSA records. It's the older, more cryptographic alternative to MTA-STS, and it requires DNSSEC on your zone to be trustworthy.
No. RFC 8461 requires the exact URL https://mta-sts.<domain>/.well-known/mta-sts.txt. Apex hosting is not conformant and senders will reject the policy.
Senders that have cached an enforce-mode policy will refuse to deliver to the new MX because its hostname is not in the cached mx list. They will keep retrying until they re-fetch the policy (after max_age) or until their own retry budget is exhausted. The safe pattern is: update the policy (new mx list + new id), wait max_age, then cut over DNS.
RFC 8461 allows 86400 (1 day) to 31557600 (1 year). For most domains, 604800 (7 days) is a sensible default - long enough to reduce lookup load, short enough that policy changes propagate within a week.