MTA-STS Actually Working — Postfix, Not RSPAMD
MTA-STS Actually Working — Postfix, Not RSPAMD
There are a lot of MTA-STS guides floating around, and a surprising number of them include RSPAMD configuration steps that look authoritative but don’t actually enforce anything. I followed one of them. This post is what I learned cleaning it up and getting MTA-STS working correctly.
What MTA-STS Actually Does
Before getting into the how, it’s worth being precise about what MTA-STS is and isn’t.
MTA-STS (Mail Transfer Agent Strict Transport Security) is a mechanism that lets a domain declare that mail destined for it must be delivered over verified TLS — meaning the connecting server must present a valid, trusted certificate. Without it, SMTP will happily negotiate an opportunistic TLS connection with an untrusted or self-signed cert, which offers basically no protection against a man-in-the-middle attack.
It operates in two directions:
Inbound (your domain): You publish a policy and a DNS record. Remote senders that support MTA-STS will fetch your policy and enforce verified TLS when connecting to your MX hosts. Your server is passive here — you publish, others enforce.
Outbound (your server): Your server fetches remote domains’ MTA-STS policies and enforces verified TLS when delivering to them. This is the active part, and it requires actual software.
Where the Common Guides Go Wrong
The guide I originally followed included an RSPAMD configuration section that looked like this:
1
2
3
4
5
6
7
MTA_STS_DOMAINS {
type = "from";
map = "/etc/rspamd/mta_sts_domains.map";
description = "Domains with MTA-STS policies";
score = 0.0;
action = "no action";
}
This does nothing useful. It tags messages from domains that happen to be in a manually maintained flat file, but it doesn’t fetch policies, doesn’t validate certificates, and doesn’t enforce anything. RSPAMD operates at the content inspection layer — it never touches the raw SMTP TLS negotiation, so it architecturally cannot enforce MTA-STS. The section was cargo-culted from somewhere and added noise to logs without providing any security value.
The correct tool for outbound MTA-STS enforcement is postfix-mta-sts-resolver. It runs as a daemon, fetches and caches remote MTA-STS policies, and feeds policy decisions to Postfix via a socketmap interface. Postfix then enforces them at connection time.
What the Working Stack Looks Like
1
2
3
4
5
6
7
DNS (_mta-sts TXT record) + HTTPS policy file (Apache)
↓
postfix-mta-sts-resolver (127.0.0.1:8461)
↓
smtp_tls_policy_maps (Postfix socketmap)
↓
Postfix enforces verified TLS outbound
RSPAMD doesn’t appear in this diagram because it has no role here.
Implementation
Step 1: Policy Files
For each domain you control, you need a policy file served over HTTPS at a specific URL. The URL format is fixed by the spec:
1
https://mta-sts.yourdomain.com/.well-known/mta-sts.txt
Note that this requires a dedicated subdomain with its own TLS certificate — not a path on your main domain.
Before writing the policy file, check your actual MX records:
1
dig MX yourdomain.com +short
Every MX host returned must appear in the policy file. Missing even one will cause delivery failures once you’re in enforce mode, because compliant senders will refuse to use an MX that isn’t listed.
A policy file for a domain with two MX hosts:
1
2
3
4
5
version: STSv1
mode: enforce
mx: mail.yourdomain.com
mx: mail.secondarydomain.com
max_age: 86400
If you’re just starting out, use mode: testing first. Testing mode logs violations but doesn’t block delivery, giving you time to validate your setup before you risk deferring legitimate mail.
Step 2: Apache Configuration
Each mta-sts subdomain needs its own virtual host. The HTTP vhost just redirects to HTTPS:
1
2
3
4
5
6
<VirtualHost *:80>
ServerName mta-sts.yourdomain.com
DocumentRoot /var/www/mta-sts.yourdomain.com
RewriteEngine on
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>
The HTTPS vhost serves the policy file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<IfModule mod_ssl.c>
<VirtualHost *:443>
ServerName mta-sts.yourdomain.com
DocumentRoot /var/www/mta-sts.yourdomain.com
<Directory /var/www/mta-sts.yourdomain.com>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
<Directory "/var/www/mta-sts.yourdomain.com/.well-known">
Require all granted
</Directory>
SSLCertificateFile /etc/letsencrypt/live/mta-sts.yourdomain.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/mta-sts.yourdomain.com/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
</IfModule>
Get a certificate for each subdomain:
1
certbot certonly --apache -d mta-sts.yourdomain.com
Verify the policy file is actually being served correctly:
1
2
curl -sv https://mta-sts.yourdomain.com/.well-known/mta-sts.txt 2>&1 \
| grep -E "(< HTTP|version:|mode:|mx:|max_age:)"
You want HTTP/1.1 200 OK and the full policy content. Also verify the certificate chain:
1
2
3
echo | openssl s_client -connect mta-sts.yourdomain.com:443 \
-servername mta-sts.yourdomain.com 2>/dev/null \
| openssl x509 -noout -dates -issuer
Step 3: DNS Records
Add a TXT record for each domain:
1
_mta-sts.yourdomain.com. IN TXT "v=STSv1; id=20250101"
The id field is a cache-buster. Remote servers cache your policy for up to max_age seconds, and only re-fetch when they see a changed id. Update it whenever you change the policy file.
Formatting matters here. The value must be unescaped:
1
2
"v=STSv1; id=20250101" ← correct
"\"v=STSv1; id=20250101\"" ← breaks external discovery
Some DNS provider UIs add escape characters automatically. Verify what actually got published:
1
2
3
4
dig TXT _mta-sts.yourdomain.com +short
# Also test from external resolvers
dig @8.8.8.8 TXT _mta-sts.yourdomain.com +short
dig @1.1.1.1 TXT _mta-sts.yourdomain.com +short
If you want TLS failure reports delivered to your inbox, add a TLS-RPT record too:
1
_smtp._tls.yourdomain.com. IN TXT "v=TLSRPTv1; rua=mailto:tls-reports@yourdomain.com"
Step 4: Postfix TLS Baseline
MTA-STS outbound enforcement builds on top of existing Postfix TLS configuration. Your main.cf should already have something like this:
1
2
3
4
5
6
7
8
9
# Inbound
smtpd_tls_cert_file = /etc/letsencrypt/live/mail.yourdomain.com/fullchain.pem
smtpd_tls_key_file = /etc/letsencrypt/live/mail.yourdomain.com/privkey.pem
smtpd_tls_security_level = may
smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
# Outbound
smtp_tls_security_level = may
smtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtp_tls_security_level = may is correct here — opportunistic TLS for domains without an MTA-STS policy. The resolver will upgrade specific domains to secure as needed.
Step 5: Install and Verify postfix-mta-sts-resolver
On Debian 13 (Trixie), the package is in the main repo:
1
apt install postfix-mta-sts-resolver
The package enables and starts postfix-mta-sts-resolver.service automatically. Verify:
1
2
systemctl status postfix-mta-sts-resolver.service
ss -tlnp | grep 8461
The daemon should be active and listening on 127.0.0.1:8461. The default config at /etc/mta-sts-daemon.yml uses a SQLite cache and is fine for most deployments.
Before wiring this into Postfix, test it directly. Gmail is a good target since they publish a well-known MTA-STS policy:
1
postmap -q gmail.com socketmap:inet:127.0.0.1:8461:postfix
Expected output:
1
secure match=.gmail-smtp-in.l.google.com:gmail-smtp-in.l.google.com servername=hostname
secure means the resolver fetched the policy and will tell Postfix to enforce verified TLS for that domain. If you get no output, check the service status.
Step 6: Wire It Into Postfix
Two settings to add, then a reload:
1
2
3
postconf -e 'smtp_tls_policy_maps = socketmap:inet:127.0.0.1:8461:postfix'
postconf -e 'smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt'
postfix reload
The first tells Postfix to consult the resolver for every outbound delivery. The second is easy to miss and causes a frustrating failure mode: without a CA bundle, Postfix can’t verify any remote certificate and will defer all MTA-STS enforced mail with:
1
status=deferred (Server certificate not verified)
On Debian the CA bundle is at /etc/ssl/certs/ca-certificates.crt. Set it and the problem goes away.
Step 7: Verify End-to-End
Send a test email to an address at a domain you know publishes MTA-STS:
1
echo "MTA-STS test" | sendmail -v someone@gmail.com
Then check the logs:
1
journalctl -u postfix -n 30 | grep -E "(TLS|status=)"
What you want to see:
1
2
Verified TLS connection established to gmail-smtp-in.l.google.com[...]:25: TLSv1.3 ...
... status=sent (250 2.0.0 OK ...)
The word Verified is what matters. It means the certificate was checked against the CA bundle and passed. Untrusted TLS connection established means it didn’t — check your smtp_tls_CAfile setting.
Ongoing Maintenance
When you update a policy file: bump the DNS id value so remote servers invalidate their cache and re-fetch. Otherwise they’ll keep enforcing the old policy for up to max_age seconds.
Watch the mail queue: after enabling enforcement, check mailq over the following day. Any remote domain with a broken MTA-STS policy — mismatched MX, expired cert, unreachable policy URL — will cause your outbound mail to that domain to defer. It’s not your fault, but you’ll want to know it’s happening.
Monitor Apache logs for policy fetches: once your DNS propagates, external mail servers will start fetching your policy files. External IPs hitting /.well-known/mta-sts.txt in your Apache logs confirms remote servers are discovering and caching your policy.
1
tail -f /var/log/apache2/mta-sts.yourdomain.com_access.log
Certificate renewal: make sure your renewal process covers the mta-sts.* subdomains. A lapsed cert on the policy endpoint breaks inbound enforcement for anyone trying to fetch your policy.
Summary
MTA-STS is a Postfix and DNS problem. RSPAMD has no role in it. The working approach is:
- Publish policy files via Apache with valid TLS certificates
- Add
_mta-stsTXT records to DNS with correctly formatted values - Install
postfix-mta-sts-resolverand verify it responds to policy queries - Add
smtp_tls_policy_mapsandsmtp_tls_CAfileto Postfix - Reload and confirm
Verified TLSin the logs
If you already followed a guide that added RSPAMD configuration for MTA-STS, you can safely remove it — it was doing nothing. Clean up any MTA_STS_DOMAINS blocks in multimap.conf and delete the accompanying .map file, run rspamadm configtest, and restart RSPAMD. Nothing will change in behavior because nothing was being enforced.