1. Knowledge base
  2. Email security

Cert Rotated, TLSA Forgotten: The Mail Outage Caused by Stale DANE-EE Records

A Let's Encrypt renewal runs at 03:00 and succeeds. Nginx, Dovecot and Postfix reload with the new certificate. Monitoring shows green, and the HTTPS check on the mail host passes. Over the next few hours, mail from Gmail-scale providers keeps flowing. Mail from Postfix, Exim and Microsoft Exchange Online senders that validate DANE starts piling up in remote queues. Nobody notices until a customer asks why a contract never arrived.

The cause is a single DNS record that still describes the previous key.

How DANE-EE pins a mail server

DANE (RFC 6698, applied to SMTP by RFC 7672) lets a domain publish, in DNSSEC-signed DNS, which certificate or key its mail server must present. The most common deployment is 3 1 1:

  • 3 is the certificate usage DANE-EE. The record pins the server's own end-entity key and ignores the CA chain.
  • 1 is the selector SPKI. It hashes the SubjectPublicKeyInfo, meaning the public key only.
  • 1 is the matching type SHA-256.
_25._tcp.mx1.example.com. 3600 IN TLSA 3 1 1 (
    8cb0fc6c527506a053f4f14c8464bebbd6dede2738d11468dd953d7d6a3021f1 )

A sending MTA looks up the MX, fetches the _25._tcp.<mx-host> TLSA records, and checks that the DNSSEC chain validates. It then connects with STARTTLS and compares the hash of the presented public key to the record. The hash covers the public key, so any renewal that generates a new private key changes it. Certbot does this by default.

Why the outage is total, not graceful

Opportunistic TLS on SMTP normally falls back to cleartext. DANE removes that fallback by design. Under RFC 7672, once validated TLSA records exist, the sender must not deliver unless a record matches. If none matches, the message is deferred rather than delivered insecurely.

A Postfix sender logs this:

postfix/smtp[2841]: certificate verification failed for mx1.example.com[203.0.113.25]:25:
  no matching TLSA records
postfix/smtp[2841]: 4F2A81C0B: to=<[email protected]>, relay=none, delay=312,
  status=deferred (Server certificate not trusted)

Deferral means the sender keeps retrying, typically for up to five days (Postfix's default maximal_queue_lifetime), and then generates a bounce. Senders see a delay warning after about four hours, and most users don't read it. At first the messages are not lost, but they are not arriving either, and once the queue lifetime expires they bounce.

What it costs

  • Inbound mail disappears silently, and it disappears from your most security-conscious senders. Government bodies, banks, and large European mail providers are the ones most likely to run DANE validation. The failures show up in their queues, not yours.
  • Your side shows no error. Your server sees a TLS handshake that sometimes ends abruptly, or nothing at all.
  • Detection takes a long time. Without monitoring, the first signal is often a human asking where a message went.
  • Compliance is exposed. Frameworks that reference secure mail transport (NIS2 mail hygiene guidance, Dutch and German government baselines, some ISO 27001 control mappings) treat a broken DANE deployment as worse than none. An auditor sees a published security assertion that the infrastructure fails to honour.

Is this exploitable?

The stale record isn't a data-exposure hole. Nobody can decrypt mail because the hash is wrong. The security value runs the other way: DANE refuses to deliver to an endpoint that doesn't match. The exposure comes from the operator's reaction. Under pressure, teams delete the TLSA record or disable DNSSEC to stop the bleeding. That downgrades the domain's own protection and re-opens the window for an on-path attacker to strip STARTTLS or substitute a certificate. An attacker who learns that a domain has had DANE failures can time an interception for that period.

How validation and monitoring catches it

A useful check compares three things, repeatedly:

  1. DNSSEC validity of the zone and of the _25._tcp.<mx> name (the resolver returns the AD flag).
  2. TLSA presence for every MX host, with a parseable usage selector matching-type.
  3. A live match. Connect to each MX on port 25, issue STARTTLS, hash the presented SPKI, and verify that at least one TLSA record equals it.

SetEnforce's DANE check does this for every MX. A failure of check 3 with checks 1 and 2 passing is the signature of this incident, and it is graded as critical because it implies active mail deferral. Run the check on a schedule shorter than the sender retry window, and alert on certificate expiry changes, since the hash only changes at renewal.

You can reproduce the live check by hand:

# What DNS says
dig +dnssec +multi TLSA _25._tcp.mx1.example.com

# What the server presents
openssl s_client -starttls smtp -connect mx1.example.com:25 </dev/null 2>/dev/null \
  | openssl x509 -noout -pubkey \
  | openssl pkey -pubin -outform DER \
  | openssl dgst -sha256 -binary | xxd -p -c 64

If the two hashes differ, senders are deferring right now.

Safe rotation procedure

The order is DNS first, certificate second, cleanup last. Both the old and new hashes must be valid for at least one full TTL before the server switches.

  1. Obtain the new key and certificate without deploying it, then generate the new hash from the staged certificate.

  2. Publish both TLSA records under the same name:

    _25._tcp.mx1.example.com. 300 IN TLSA 3 1 1 <hash-of-current-key>
    _25._tcp.mx1.example.com. 300 IN TLSA 3 1 1 <hash-of-new-key>
    
  3. Wait. Let the old record's TTL expire, plus the secondary-nameserver propagation delay and any DNSSEC re-signing interval. Verify with dig against several resolvers.

  4. Deploy the new certificate and reload Postfix.

  5. Run the live match check and confirm the new hash is served.

  6. Remove the old record only after a few days, once the queued mail of senders holding cached answers has drained.

Lowering the TTL to 300 seconds a week before a planned rotation shortens step 3.

The better fix: key-reuse renewals

Routine 60-to-90-day renewals shouldn't need DNS changes at all. If the private key is retained, the SPKI hash stays identical and 3 1 1 remains valid indefinitely.

certbot renew --reuse-key
# or persist it for a certificate:
certbot certonly --reuse-key -d mx1.example.com

In /etc/letsencrypt/renewal/mx1.example.com.conf, confirm reuse_key = True. Reserve full key rotation for a planned event, perhaps annually, and run it with the procedure above.

Two other defences are worth adding:

  • A deploy hook that recomputes the SPKI hash after every renewal and fails loudly if it differs from the published TLSA record.
  • 2 1 1 (DANE-TA), which pins the issuing CA's key. Alternatively, publish a 3 1 1 for a pre-generated standby key. The second option makes emergency rotations a certificate swap with no DNS wait.

Key takeaways

  • A 3 1 1 TLSA record pins the public key, so a renewal with a new key invalidates it.
  • DANE-validating senders do not fall back to cleartext. They defer, then bounce after the queue lifetime.
  • Never "fix" an outage by removing TLSA records or DNSSEC. That downgrades your own security.
  • Rotate in order: publish the new record, wait out the TTL, deploy the certificate, retire the old record.
  • Use certbot renew --reuse-key so routine renewals leave the hash unchanged.
  • Monitor the live key against DNS from outside, on a schedule shorter than sender retry windows.

Check whether your own domain gets this right. Scanning is free and needs no account.

Scan your domain

All articles