MTA-STS policy fetch failures

Diagnose and fix sts-policy-fetch-error, the most common MTA-STS failure. Covers hosting mistakes, HTTP redirects, Cloudflare WAF blocking, GitHub Pages 404, and certificate problems on the mta-sts subdomain.

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 of https:// (senders will not fetch over plain HTTP)
  • Wrong filename like mta_sts.txt or MTA-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 the mta-sts subdomain too if it shares the same server block.
  • Trailing slash redirect. Some servers redirect /.well-known/mta-sts.txt to /.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:

  1. Go to your Cloudflare dashboard, select your domain.
  2. Navigate to Security > WAF > Custom rules.
  3. Create a new rule with this expression:
    (http.host eq "mta-sts.yourdomain.com")
  4. Set the action to Skip and select all security features (Browser Integrity Check, Bot Fight Mode, etc.).
  5. 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-sts subdomain 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:

  • notAfter in 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.

Was this page helpful? Send us feedback

Last updated: March 2026