Hardening my domains' email with MTA-STS (for free, on GitHub Pages)
I receive email on more than one domain, and for a long time I assumed that mail sent to me was encrypted in transit. Mostly, it is. But “mostly” turned out to be doing a lot of work in that sentence, so I set up MTA-STS on all of them. This post explains what it is, why it matters, and how I host it for free on GitHub Pages.
The problem: STARTTLS is optional
When one mail server delivers a message to another, it usually upgrades the connection to TLS with STARTTLS. The catch is that this upgrade is opportunistic: if the receiving server doesn’t seem to offer TLS, the sender shrugs and delivers in plain text.
That means an attacker sitting in the middle can strip the STARTTLS offer from the conversation (a downgrade attack), or spoof DNS so mail goes to a server they control. The sender has no way to know that your server should have supported TLS.
What MTA-STS does
MTA-STS (RFC 8461) lets a domain publish a simple promise:
Mail for this domain must be delivered over TLS with a valid certificate, and only to these MX hosts.
Sending servers that support MTA-STS (Gmail, Outlook, and other large providers do) fetch that policy, cache it, and refuse to deliver if the connection can’t meet it.
There is an alternative called DANE, but it requires DNSSEC on your domain. MTA-STS only needs DNS and an HTTPS website, which makes it much easier to deploy.
The three pieces
- A DNS
TXTrecord at_mta-sts.example.comannouncing that a policy exists. - An HTTPS site at
mta-sts.example.comwith a valid certificate. - A policy file served at
https://mta-sts.example.com/.well-known/mta-sts.txt.
The second and third parts are just static hosting, which is where GitHub Pages comes in.
Hosting the policy on GitHub Pages
I used jpawlowski’s MTA-STS template. The steps are:
- Create a new repository from the template (use Use this template, not a fork or clone).
- Edit
.well-known/mta-sts.txt. - Put your hostname in the
CNAMEfile, for examplemta-sts.lew.to. - Add a
CNAMEDNS record pointingmta-sts.example.comto<username>.github.io, then enable GitHub Pages with HTTPS.
One detail worth knowing: GitHub Pages runs Jekyll, which ignores directories starting with a dot. The template’s _config.yml fixes that with a single line:
include: [".well-known"]
Without it, the policy file would simply return a 404.
Here is my actual policy. My mail is hosted by Fastmail, so the MX entries point at their servers:
version: STSv1
mode: enforce
mx: eu1-smtp.messagingengine.com
mx: eu2-smtp.messagingengine.com
max_age: 1209600
Finally, add the TXT record that switches it on:
_mta-sts.example.com. TXT "v=STSv1; id=20260101000000"
The id can be any string. What matters is that you change it every time you change the policy, because that’s how senders know to fetch a new copy.
Choosing the settings
mode:testingmeans senders report problems but still deliver;enforcemeans they refuse to deliver if TLS fails;nonewithdraws the policy. Starting intestingfor a little while is the safe approach, then switching toenforce.max_age: how long (in seconds) senders cache the policy.1209600is two weeks. Longer is more secure, but it also means a mistake lingers longer.- Changing providers: if you ever change MX hosts, update the policy (and its
id) to include the new hosts before you change the MX records, then remove the old ones later. Otherwise senders with a cached policy will refuse to deliver.
Managing several domains
I have one repository per domain: lew.to, lew.bz, lew.lu, lewis.ax, and so on. Each one is a copy of the same template with a different CNAME. It’s a little repetitive, but it keeps every domain independent, and the policy files almost never change.
Getting reports with TLS-RPT
MTA-STS pairs well with TLS reporting (RFC 8460). One more TXT record asks senders to email you a daily summary of successful and failed TLS connections:
_smtp._tls.example.com. TXT "v=TLSRPTv1; rua=mailto:tls-reports@example.com"
This is how you find out if something is broken before mail starts bouncing.
Checking that it works
A quick check from the command line:
curl https://mta-sts.example.com/.well-known/mta-sts.txt
dig +short TXT _mta-sts.example.com
If the first command returns your policy without certificate errors and the second shows your TXT record, you’re done. Online checkers such as Hardenize or MXToolbox will also validate the whole setup.
Wrapping up
The whole thing costs nothing, takes a few minutes per domain, and closes a real gap in how email is delivered. If you own a domain that receives mail, it’s one of the cheapest security improvements you can make.