---
title: "TLSA Mismatch: Debug a DANE 3 1 1 Failure with OpenSSL"
description: "A DANE TLSA 3 1 1 mismatch means the published hash no longer matches the served key. Compute the hash with OpenSSL, isolate the root cause, and fix it fast."
publishedAt: 2026-08-20
tags: ["dane", "tlsa", "dnssec", "smtp", "openssl", "tls-rpt", "troubleshooting"]
faq:
  - question: "How do I compare my live certificate against my published TLSA record?"
    answer: "Run two commands side by side: dig +short TLSA _25._tcp.<mx> shows what DNS publishes, and openssl s_client -starttls smtp piped through the SPKI pipeline hashes what the server actually serves. If the hex differs, the record has drifted. A DANE checker performs the same comparison in one pass."
  - question: "Why does OpenSSL print (stdin)= before my TLSA hash?"
    answer: "openssl dgst labels piped input — older releases print (stdin)=, OpenSSL 3.x prints SHA2-256(stdin)=. Pasting that label into a zone file corrupts the record. Add -binary and pipe through hexdump -ve '/1 \"%02x\"' to emit the bare 64-character hex digest with no prefix or trailing newline."
  - question: "What does Microsoft 365 error 4.7.323 tlsa-invalid mean?"
    answer: "Per Microsoft Learn, 4.7.323 can only be generated after a DNSSEC-authentic TLSA record was returned — so DNS and DNSSEC are fine, but the destination certificate does not match the authentic record. Recompute the SPKI hash from the live certificate and republish the TLSA record."
  - question: "Can I fix a TLSA mismatch without waiting for the TTL?"
    answer: "Yes. Add a second TLSA record whose hash matches the certificate the server currently serves, and keep the old record in place. Adding a matching record validates as soon as resolvers fetch the RRset, because DANE passes when any published record matches. Replacing forces you to wait out cached copies."
  - question: "Is my problem the TLSA record or DNSSEC?"
    answer: "Run dig +dnssec on the TLSA name against a validating resolver. No ad flag means DNSSEC is broken and senders discard the record unauthenticated (RFC 7672 Section 2.2). In TLS-RPT reports, tlsa-invalid points at the certificate; dnssec-invalid points at the DNS chain."
---
# TLSA Mismatch Troubleshooting: Compute the 3 1 1 Hash and Find the Drift

Mail flowed for weeks. Then, right after a certificate renewal, DANE-validating
senders started deferring with lines like `Server certificate not trusted` or
`Verification failed. The certificate differs.` That timing is the signature of
a DANE TLSA `3 1 1` mismatch: the SHA-256 hash published in DNS no longer
matches the public key your MX actually serves.

This guide is the debugging session. You will decode the sender's error
message, compute the served hash with OpenSSL, compare it against the published
record, identify which of five root causes you have, and roll over without
breaking validation again. If you are setting up a record from scratch instead,
start with our [TLSA record setup guide](/blog/tlsa-record-setup/) — this post
assumes the record already exists and has stopped matching.

## What does a TLSA 3 1 1 mismatch mean — and what's the fastest check?

The fastest TLSA record check is two commands, side by side. First, what DNS
publishes:

```
dig +short TLSA _25._tcp.mx.example.com
```

A healthy answer looks like `3 1 1` followed by a single 64-character hex
string — for example `3 1 1 0d2280aa14c3…6bbc3fdf` — with no `(stdin)=`
prefix, no whitespace inside the hash, and no truncation. Anything else in the
rdata is already a finding. Mail actively bouncing right now? Jump straight to
the [emergency add-don't-replace first aid](#emergency-first-aid) and come back
to diagnose afterward.

Second, what the MX actually serves — the live SPKI pipeline shown in the next
section. If the two hex strings differ, you have a key or selector problem. If
they match but senders still fail, the problem is DNSSEC or the certificate
chain, not the hash.

What do those three digits assert? A `3 1 1` mismatch means the SHA-256 digest
of your server's SubjectPublicKeyInfo (SPKI) no longer equals the
certificate-association data published at `_25._tcp.<mx>`. The fields come
from RFC 6698: certificate usage `3` is DANE-EE — pin this end-entity
certificate (§2.1.1); selector `1` hashes the public key rather than the whole
certificate (§2.1.2); matching type `1` is SHA-256 (§2.1.3). RFC 7672 §3.1
recommends exactly this combination for SMTP.

Confirm DNSSEC before touching the record. Run `dig +dnssec` against a
validating resolver and look for the `ad` (authenticated data) flag. RFC 7672
§2.2 is blunt about why: a TLSA lookup that is not DNSSEC-authenticated is
unusable, so a perfect hash behind broken DNSSEC still fails. A 2022
measurement study of DANE SMTP servers (Lee et al., USENIX Security 2022) found
that missing DS records at the parent zone caused roughly 99% of the DNSSEC
failures it observed — check the DS delegation first.

Prefer one pass over five terminals? Paste your MX into our
[DANE/TLSA checker](/tools/dane-checker/) — it resolves the record, validates
DNSSEC, and compares the live certificate in a single check.

## How do you compute the TLSA hash with OpenSSL?

To compute the TLSA hash with OpenSSL, extract the SPKI, encode it as DER, and
hash it with SHA-256. This is the canonical pipeline Viktor Dukhovni published
on the IETF dane list in 2014, and the Postfix
[TLS_README](https://www.postfix.org/TLS_README.html) endorses the resulting
`3 1 1` association as best practice:

<CodeBlock
  lang="bash"
  filename="Hash the on-disk certificate's SPKI (3 1 1 value)"
  code={spkiPipeline}
/>

Each stage does one job: `x509 -noout -pubkey` extracts the
SubjectPublicKeyInfo from the certificate, `pkey -pubin -outform DER` converts
it to binary DER, and `dgst -sha256` hashes those bytes. The generic `pkey`
subcommand works for both RSA and ECDSA keys — Dukhovni noted on the same
thread that the pipeline "is not RSA-specific and works equally well for ECDSA
keys" — so prefer it over the algorithm-specific `rsa`/`ec` variants, which
produce byte-identical output.

That hashes the certificate on disk. A mismatch investigation needs the
certificate your MX serves on port 25, which may not be the same file. The
drift check swaps the input for a live `s_client` session:

<CodeBlock
  lang="bash"
  filename="Hash the SPKI the MX serves live on port 25"
  code={liveDriftCheck}
/>

One honest caveat: many ISPs and cloud hosts block outbound port 25, so this
command hangs rather than fails — run it from the mail server itself, or let
the [DANE checker](/tools/dane-checker/) make the connection for you.

We verified this pipeline against a public DANE domain: the
served SPKI hashed to `0d2280aa14c34b9f…6bbc3fdf`, exactly matching one of the
three published `3 1 1` records. When your output does not match any published
record, you have found the drift.

Watch the output format. `openssl dgst` labels piped input — `(stdin)= ` on
older releases, `SHA2-256(stdin)= ` on OpenSSL 3.x — and appends a newline.
Paste that verbatim into a zone file and you have manufactured a malformed
record. The `-binary | hexdump -ve '/1 "%02x"'` form in the snippet emits the
bare 64-character hex digest, nothing else.

OpenSSL can also render the verdict itself. The verify one-liner hands
`s_client` the published rdata and asks it to authenticate the connection the
way a DANE-validating MTA would:

<CodeBlock
  lang="bash"
  filename="Verify the connection against your published TLSA rdata"
  code={daneVerify}
/>

A match prints `DANE TLSA 3 1 1 …<hash> matched the EE certificate at depth 0`
followed by `Verify return code: 0 (ok)` (observed with OpenSSL 3.6.1).
You can pass `-dane_tlsa_rrdata` multiple times to test the current and next
records together during a rollover.

Cross-check with a second tool before you edit DNS: Postfix's
`posttls-finger -c -Lsummary mx.example.com` reports which published record
matched the live chain, and GnuTLS's `danetool --check` performs an independent
fetch-and-verify. Once you have the correct hash, the
[TLSA record generator](/tools/dane-generator/) assembles the full record — the
setup guide covers publishing it, so we won't repeat that here.

## Why doesn't my computed hash match the published record?

Five root causes account for the mismatches that reach production. The
evidence column is the differentiator — each cause leaves a distinct
fingerprint in the hashes you just computed.

<DataTable caption="The five TLSA mismatch root causes, their evidence, and their fixes">

| Root cause                                            | Symptom / sender error                                    | Evidence to confirm                                                          | Fix                                                                                       |
| ----------------------------------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| **1. Renewal minted a new key**                       | Mail defers right after a certificate renewal             | On-disk and live SPKI hashes agree with each other but not with DNS          | `certbot --reuse-key`, or the current + next rollover (next section)                      |
| **2. Wrong selector or matching type**                | Record never matched, even on day one                     | The full-certificate digest equals the DNS value while the SPKI hash doesn't | Recompute with the SPKI pipeline and republish as `3 1 1`                                 |
| **3. Leaf vs intermediate confusion**                 | Postfix logs `no matching DANE TLSA records`              | Your hash matches an intermediate block from `s_client`, not the leaf        | Hash the first certificate in the chain; for DANE-TA(2), serve the pinned CA in the chain |
| **4. Stray artifact in the record**                   | Validators report malformed or wrong-length data          | Record contains `(stdin)=`, whitespace, or fewer than 64 hex characters      | Regenerate with `-binary \| hexdump` and republish                                        |
| **5. Not a hash problem: DNSSEC or resolver failure** | `TLSA lookup error` (Postfix), `tlsa lookup DEFER` (Exim) | No `ad` flag on `dig +dnssec`; the hashes actually agree                     | Fix the DS record, RRSIG expiry, or resolver — leave TLSA alone                           |

</DataTable>

<Figure
  src="/images/blog/tlsa-mismatch-troubleshooting/tlsa-mismatch-troubleshooting_root-cause-decision_flowchart.svg"
  alt="Decision flowchart mapping TLSA hash-comparison evidence — which computed hash equals which published value — to the five mismatch root causes: renewal drift, wrong selector, leaf versus intermediate confusion, stray artifact, and DNSSEC failure"
  caption="Follow the evidence: which hash equals which decides the root cause before you touch DNS."
/>

Cause 1 dominates production incidents. certbot generates a fresh key on
renewal by default, and Let's Encrypt certificates live 90 days — so the record
breaks at the first renewal, long after anyone associates the change with DNS.
Certificate rotation is precisely the failure mode selector `1` was designed to
survive, but only when the key is reused.

Cause 2 is the classic authoring error. To confirm it, hash the full
certificate — `openssl x509 -outform DER | openssl dgst -sha256` — and compare
that digest against the DNS value. The sys4 DANE
[common mistakes page](https://dane.sys4.de/common_mistakes) documents it
verbatim: "Some domains publish TLSA records with a selector of SPKI(1), which
indicates a digest of a public key, but the digest in the TLSA record is that
of the containing certificate." Hashing the PEM text instead of binary DER is
the sibling mistake on the same page. The 2022 USENIX study found selector-0
records mismatched 34% of the time in one of its datasets — far more than
selector 1 — because a full-certificate digest changes on every reissue even
with key reuse.

Cause 3 bit DANE-TA(2) publishers hard when Let's Encrypt stopped providing its
cross-sign by default in February 2024: records pinning the ISRG root stopped
validating because the root no longer appeared in the served chain.

The sender's error message narrows things down before you type a single
command:

- **Postfix**: `Server certificate not trusted` and
  `no matching DANE TLSA records` mean DANE authentication ran and failed — a
  genuine mismatch (causes 1–3). `TLSA lookup error` means the DNS query itself
  failed — cause 5.
- **Exim**: `Verification failed. The certificate differs.` is the
  post-renewal mismatch; `DANE error: tlsa lookup DEFER` is a resolver problem.
- **Microsoft 365**: `4.7.323 tlsa-invalid` — per
  [Microsoft Learn](https://learn.microsoft.com/en-us/purview/how-smtp-dane-works),
  this code "can only be generated after a DNSSEC-authentic TLSA record has
  been returned," so DNSSEC is intact and the certificate is the problem.

Rule out the causes in order: rotation drift first, then the wrong hashed
object, then artifacts, then DNSSEC.

## How do you roll over a TLSA record without breaking DANE?

RFC 7671 §8 updates the publisher rules: every usage/selector/matching-type
combination in your RRset must include at least one record matching the current
chain, at all times. §8.1 spells out the safe sequence — publish the record for
the future key alongside the current one at least two TTLs before deploying the
new chain, let caches age out, deploy the new certificate, verify, then remove
the obsolete record. The Postfix TLS_README goes one step more conservative:
wait until the DNSSEC signature on the previous TLSA RRset expires. We walk
through the publish-before-swap zone file in the setup guide, so here we stay
on the operational choice.

Two models keep renewals from ever reopening this incident:

- **Key reuse.** `certbot --reuse-key` keeps the SPKI stable across renewals,
  so the `3 1 1` record never needs to change. Confirm `reuse_key = True`
  actually landed in the lineage's renewal config — a Let's Encrypt community
  thread documents operators who believed it was set while certbot kept minting
  fresh keys.
- **Current + next.** Pre-generate the next key pair and publish its `3 1 1`
  record alongside the current one, so the RRset always contains both. Dukhovni
  presented this pattern at ICANN61 (2018); on renewal you switch to the
  already-trusted next key, then publish a fresh "next."

Two caveats scale with your fleet. If your server offers both RSA and ECDSA
certificates, the negotiated ciphersuite decides which one is served — the
RRset needs a matching `3 1 1` for each key. And RFC 7672 §2.1 binds TLSA
records to each MX hostname, so every MX host needs its own correct set.

<span id="emergency-first-aid" />

<Callout type="tip" title="Emergency first aid while mail is deferring">
  **Add** a record matching the currently served SPKI — do not replace the old
  one. DANE passes when any published record matches, so an added record
  validates as soon as resolvers fetch the RRset, while a replacement makes you
  wait out every cached copy.
</Callout>

One forward-looking date worth planning around: Let's Encrypt announced on
2 December 2025 that certificate lifetimes shrink to 45 days by February 2028.
That halves the drift window — manual TLSA maintenance stops being viable.

## How do you catch TLSA drift before mail bounces?

Your own monitoring sees your DNS; only senders see your DNS, DNSSEC, and
certificate the way validation does. TLS-RPT (RFC 8460) gives you that
sender's-eye view: publish a `_smtp._tls` TXT record and participating senders
mail you daily JSON reports. Three
[RFC 8460](https://datatracker.ietf.org/doc/html/rfc8460) result types
separate the failure classes this guide covered: `tlsa-invalid` means the
record was DNSSEC-authentic but the certificate didn't match (causes 1–4);
`dnssec-invalid` means the resolver couldn't validate the record (cause 5);
and `dane-required` means the sender demanded DANE and found no usable record
at all. New to the protocol? Start with our [TLS-RPT guide](/learn/tls-rpt/),
and drop a report into the
[TLS report analyzer](/tools/tls-report-analyzer/) to translate result types
into the failing MX and cause.

Scheduled checks close the loop between renewals. Dukhovni's
[danecheck](https://github.com/vdukhovni/danecheck) walks every MX host,
returns a non-zero exit code on any mismatch, and can verify chains at a
future time offset to catch imminent breakage — cron-friendly by design.
sys4's `smtp-dane-verify` ships a Prometheus exporter for the same check. And
the `-dane_tlsa_rrdata` one-liner from earlier belongs inside your ACME deploy
hook, blocking the deployment when verification returns anything but 0.

If you run MTA-STS alongside DANE, note that a missing policy file fails in
the same silent way a stale TLSA does — we cover that failure mode in
[fixing a missing MTA-STS policy](/blog/mta-sts-policy-missing-fix/).

DMARCguard monitors DANE and TLS-RPT among its 9 protocols: it performs the
live-vs-published comparison continuously and tells you which record to fix,
instead of leaving the diff to your next incident.

## FAQ

### How do I compare my live certificate against my published TLSA record?

Run two commands side by side: `dig +short TLSA _25._tcp.<mx>` shows what DNS
publishes, and `openssl s_client -starttls smtp` piped through the SPKI
pipeline hashes what the server actually serves. If the hex differs, the
record has drifted. A DANE checker performs the same comparison in one pass.

### Why does OpenSSL print `(stdin)=` before my TLSA hash?

`openssl dgst` labels piped input — older releases print `(stdin)=`, OpenSSL
3.x prints `SHA2-256(stdin)=`. Pasting that label into a zone file corrupts
the record. Add `-binary` and pipe through `hexdump -ve '/1 "%02x"'` to emit
the bare 64-character hex digest with no prefix or trailing newline.

### What does Microsoft 365 error 4.7.323 tlsa-invalid mean?

Per Microsoft Learn, 4.7.323 can only be generated after a DNSSEC-authentic
TLSA record was returned — so DNS and DNSSEC are fine, but the destination
certificate does not match the authentic record. Recompute the SPKI hash from
the live certificate and republish the TLSA record.

### Can I fix a TLSA mismatch without waiting for the TTL?

Yes. Add a second TLSA record whose hash matches the certificate the server
currently serves, and keep the old record in place. Adding a matching record
validates as soon as resolvers fetch the RRset, because DANE passes when any
published record matches. Replacing forces you to wait out cached copies.

### Is my problem the TLSA record or DNSSEC?

Run `dig +dnssec` on the TLSA name against a validating resolver. No `ad` flag
means DNSSEC is broken and senders discard the record unauthenticated
(RFC 7672 §2.2). In TLS-RPT reports, `tlsa-invalid` points at the certificate;
`dnssec-invalid` points at the DNS chain.

## Conclusion

A DANE TLSA `3 1 1` mismatch is a diff, and diffs are debuggable: compute the
served SPKI hash with OpenSSL, compare it against the published record, and
let the evidence pick one of the five root causes. Fix it by adding — not
replacing — a matching record, then make the fix permanent with key reuse or a
current + next rollover per RFC 7671, and put TLS-RPT plus a scheduled DANE
check in place so the next renewal is a non-event.

<CTA
  title="See whether your TLSA record matches the live certificate"
  description="Run your MX through the free DANE checker — it resolves the record, validates the DNSSEC chain, and compares the served certificate in one pass. No signup."
  label="Open the DANE checker"
  href="/tools/dane-checker/"
/>

And when you want the comparison running every day instead of every incident:
start monitoring your DMARC reports — free plan, no credit card.