What sts-policy-fetch-error means
The failure in your TLS-RPT report
When a sending mail server tries to deliver mail to your domain, it looks up your MTA-STS policy by fetching https://mta-sts.yourdomain.com/.well-known/mta-sts.txt. If that fetch fails for any reason, the sender records a sts-policy-fetch-error in its TLS-RPT (TLS Reporting) JSON report. This is the single most common MTA-STS failure type.
In a TLS-RPT report, the failure looks like this:
{
"policies": [
{
"policy": {
"policy-type": "sts",
"policy-string": [],
"policy-domain": "yourdomain.com"
},
"summary": {
"total-successful-session-count": 0,
"total-failure-session-count": 142
},
"failure-details": [
{
"result-type": "sts-policy-fetch-error",
"failed-session-count": 142,
"failure-reason-code": "Could not fetch MTA-STS policy"
}
]
}
]
}
The sender tried 142 times and failed every time. It could not retrieve your policy file, so it could not enforce TLS on the connection. Your MTA-STS deployment is effectively broken.
The TLS-RPT report does not tell you why the fetch failed. You need to diagnose that yourself. The sections below cover every common cause.
Policy file at the wrong path
The exact URL requirement
RFC 8461 Section 3.3 requires the policy file to be served at exactly this URL:
https://mta-sts.yourdomain.com/.well-known/mta-sts.txt
Every part of this URL matters. The subdomain must be mta-sts. The path must include the .well-known/ directory. The filename must be mta-sts.txt. HTTPS is mandatory, not optional.
Common mistakes
- File placed at
https://mta-sts.yourdomain.com/mta-sts.txt(missing.well-known/directory) - File placed at the root domain
https://yourdomain.com/.well-known/mta-sts.txt(wrong hostname) - Using
http://instead ofhttps://(senders will not fetch over plain HTTP) - Wrong filename like
mta_sts.txtorMTA-STS.txt
Diagnosis
Test the exact URL that sending servers will request:
curl -v https://mta-sts.yourdomain.com/.well-known/mta-sts.txt
You should see an HTTP 200 response with your policy content. If you get a 404, 403, or connection error, the path or hosting is wrong.
A valid response looks like this:
version: STSv1
mode: enforce
mx: mail.yourdomain.com
mx: mx2.yourdomain.com
max_age: 604800
HTTP redirects blocking the fetch
Why redirects break MTA-STS
RFC 8461 Section 3.3 explicitly states that senders must not follow HTTP redirects when fetching the policy file. This is a security measure. If redirects were allowed, an attacker who controls DNS could redirect the policy fetch to a server they control and serve a permissive policy.
This means any redirect, whether from HTTP to HTTPS, from www to non-www, or from one path to another, will cause the fetch to fail.
Diagnosis
Check for redirects with curl:
curl -I https://mta-sts.yourdomain.com/.well-known/mta-sts.txt
If you see a 301 or 302 status code with a Location header, that is the problem:
HTTP/1.1 301 Moved Permanently
Location: https://www.yourdomain.com/.well-known/mta-sts.txt
Common redirect causes
-
Certbot's auto HTTP-to-HTTPS redirect. If you run Certbot with
--redirect, it adds a blanket redirect rule for all HTTP traffic on the virtual host. This typically does not affect HTTPS requests, but if your nginx/Apache config has additional redirect rules, they compound. -
Catch-all www redirect. Many nginx configs redirect everything to
www.yourdomain.com. This catches themta-stssubdomain too if it shares the same server block. -
Trailing slash redirect. Some servers redirect
/.well-known/mta-sts.txtto/.well-known/mta-sts.txt/.
Fix for nginx
Create a dedicated server block for the mta-sts subdomain that serves the file directly without redirects:
server {
listen 443 ssl;
server_name mta-sts.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/mta-sts.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mta-sts.yourdomain.com/privkey.pem;
location = /.well-known/mta-sts.txt {
root /var/www/mta-sts;
}
}
Fix for Apache
Create a dedicated virtual host:
<VirtualHost *:443>
ServerName mta-sts.yourdomain.com
DocumentRoot /var/www/mta-sts
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/mta-sts.yourdomain.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/mta-sts.yourdomain.com/privkey.pem
<Directory /var/www/mta-sts>
Options None
AllowOverride None
Require all granted
</Directory>
</VirtualHost>
Make sure no global .htaccess or RewriteRule applies a redirect to this virtual host.
Cloudflare Browser Integrity Check blocking
The invisible failure
This one is especially frustrating because the policy file works perfectly in your browser. You visit the URL, you see the policy content, everything looks fine. But sending mail servers get blocked.
The cause: Cloudflare's Browser Integrity Check (BIC) and Bot Fight Mode inspect the User-Agent header. Browsers send something like Mozilla/5.0 .... Mail servers send things like libwww-perl/6.68, Go-http-client/2.0, or no User-Agent at all. Cloudflare blocks these as suspicious.
Diagnosis
Test with a non-browser User-Agent:
curl -A "libwww-perl/6.68" -v https://mta-sts.yourdomain.com/.well-known/mta-sts.txt
If you get a Cloudflare challenge page (an HTML response with a 403 status or a JavaScript challenge), BIC is blocking the fetch.
Solution: WAF custom rule
Create a Cloudflare WAF Custom Rule that skips security checks for the MTA-STS subdomain:
- Go to your Cloudflare dashboard, select your domain.
- Navigate to Security > WAF > Custom rules.
- Create a new rule with this expression:
(http.host eq "mta-sts.yourdomain.com") - Set the action to Skip and select all security features (Browser Integrity Check, Bot Fight Mode, etc.).
- Place this rule first in your rule list so it executes before any blocking rules.
After creating the rule, test again with the non-browser User-Agent to confirm the policy is now accessible.
GitHub Pages not serving .well-known/
Why Jekyll ignores your directory
GitHub Pages uses Jekyll by default to build your site. Jekyll ignores directories and files that start with a dot, underscore, or hash. This means your .well-known/ directory is silently excluded from the build. The file exists in your repository, but GitHub Pages returns a 404.
Solution
Add a file named .nojekyll to the root of your repository. This file can be empty. It tells GitHub Pages to skip Jekyll processing and serve all files as-is.
touch .nojekyll
git add .nojekyll
git commit -m "Disable Jekyll to serve .well-known directory"
git push
After pushing, wait a few minutes for GitHub Pages to rebuild, then verify:
curl -I https://mta-sts.yourdomain.com/.well-known/mta-sts.txt
You should now see an HTTP 200 response instead of 404.
Certificate problems on the mta-sts subdomain
Why certificates matter here
The policy fetch happens over HTTPS. If the TLS certificate for mta-sts.yourdomain.com is expired, self-signed, or missing intermediate certificates, the sender's HTTP client will refuse the connection. The fetch fails before the policy file is ever requested.
Common certificate problems
- Expired certificate. The certificate was valid when you set up MTA-STS but has since expired. Let's Encrypt certificates expire every 90 days.
-
MTA-STS subdomain not included in renewal. Your main domain certificate renews automatically, but the
mta-stssubdomain was added as a one-off and was not included in the renewal configuration. - Self-signed certificate. Not trusted by sending mail servers.
- Missing intermediate chain. The server sends the leaf certificate but not the intermediate certificates needed to build the trust chain.
Diagnosis
Check the certificate with openssl:
openssl s_client -connect mta-sts.yourdomain.com:443 -servername mta-sts.yourdomain.com /dev/null | openssl x509 -noout -dates -subject -issuer
This shows the certificate's subject, issuer, and validity dates. Look for:
-
notAfterin the past (expired) - Subject that does not match
mta-sts.yourdomain.com(wrong certificate) - Issuer that is the same as the subject (self-signed)
To check the full certificate chain:
openssl s_client -connect mta-sts.yourdomain.com:443 -servername mta-sts.yourdomain.com -showcerts /dev/null
If you see only one certificate (the leaf) and no intermediates, the chain is incomplete.
Fix for Let's Encrypt
Make sure the mta-sts subdomain is included in your certificate. If you use Certbot:
certbot certonly --nginx -d yourdomain.com -d www.yourdomain.com -d mta-sts.yourdomain.com
This creates a single certificate covering all three hostnames. Certbot's auto-renewal will renew all of them together.
Wrong Content-Type header
The requirement
RFC 8461 Section 3.3 specifies that the policy file must be served with a Content-Type of text/plain. Some sending servers are strict about this and will reject responses with other content types.
Diagnosis
Check the Content-Type header:
curl -I https://mta-sts.yourdomain.com/.well-known/mta-sts.txt
Look for the Content-Type line. If you see application/octet-stream, text/html, or anything other than text/plain, fix your server configuration.
Fix for nginx
Ensure the .txt extension maps to the correct MIME type. In most nginx installations this is the default, but you can force it:
location = /.well-known/mta-sts.txt {
root /var/www/mta-sts;
default_type text/plain;
}
Fix for Apache
Add the MIME type in your virtual host or .htaccess:
AddType text/plain .txt
Prevention
Validate with the free domain checker
The DMARCTrust domain checker validates MTA-STS policy accessibility as part of its comprehensive domain analysis. It checks the DNS record, fetches the policy file, verifies the certificate, and reports any issues. Run it after every change to your MTA-STS configuration.
Eliminate hosting problems entirely
Every failure in this article, the wrong path, the redirect, the Cloudflare block, the Jekyll 404, the expired certificate, comes from hosting the policy file yourself. DMARCTrust's Hosted MTA-STS (Receiver Shield, included in the Pro plan) eliminates all of them. You add a CNAME record, and DMARCTrust serves your policy file on a managed infrastructure with automatic certificate renewal. No web server, no path configuration, no certificate to manage.
Monitor with TLS-RPT
Always publish a TLS-RPT DNS record alongside your MTA-STS record. TLS-RPT reports tell you when senders encounter policy fetch failures, so you catch problems before they affect all inbound mail. Without TLS-RPT, you are flying blind.