Your security.txt Has an Expiry Date — and It Is Probably Already Stale

You added a security.txt file the way you were supposed to. Dropped it at /.well-known/security.txt, listed a Contact: line so a researcher who finds a bug has somewhere to send it, maybe an Encryption: and a Policy:. Then you moved on, because a static text file is the definition of a thing that does not need attention.

Except this one does. RFC 9116 gave security.txt an expiry date, made it mandatory, and defined what happens when it passes — and the answer is that a lapsed file MUST be treated as stale. Most of the security.txt files published on the internet are already past their Expires date, and the tools people use to check these things will not tell you, because presence is the only thing they check.


What RFC 9116 Actually Requires

security.txt predates its RFC by years — it circulated as a draft convention long before it was standardized — which is why so much of the deployed base is subtly wrong. RFC 9116 (April 2022) is the actual standard, and it is stricter than the folklore:

  • The canonical location is /.well-known/security.txt. A file at the domain root (/security.txt) is the legacy draft location; clients may still fall back to it, but .well-known is where a compliant consumer looks first.
  • Contact: is required, and at least one must be present. It is the entire point of the file — a way to reach you that does not depend on a researcher guessing security@.
  • Expires: is required. This is the part that trips people. RFC 9116 §2.5.5 makes Expires a mandatory field with an ISO 8601 timestamp, and it says exactly one is allowed. More importantly, it defines the consequence: once that timestamp is in the past, the data in the file MUST be considered stale and should not be relied upon.

That last requirement is the one almost nobody operationalizes. Expires is not advisory metadata. It is a self-imposed deadline that says "if you are reading this after date X, assume the contact information may be wrong." A security researcher who finds something on your production system in month 14, reads a security.txt that expired in month 12, and follows the RFC will treat your disclosure contact as untrustworthy — which is the exact opposite of what the file exists to do.

Why Almost Every "security.txt Checker" Misses This

Run your domain through the usual suspects — a security-headers grader, a TLS scanner, an uptime monitor — and a security.txt either shows up as a green check or does not show up at all. The check is binary: is there a file? Presence is trivial to test and it is what everyone tests.

Presence is not the interesting question. These are:

  • Is the Expires date in the past? A present-but-expired file passes every presence check and fails the one thing RFC 9116 actually mandates about it. This is the security.txt equivalent of a certificate that renewed to disk but never reloaded — technically deployed, functionally dead.
  • Does the file even have a Contact field? A surprising number of "security.txt" URLs return HTTP 200 with a body that is your single-page-app's HTML shell or a soft-404 catch-all. It looks served. It contains no Contact and no Expires. A 200 with none of the RFC's fields is not a security.txt — it is an HTML page at a security.txt URL, and a presence check that stops at the status code will call it valid.

Neither of those is caught by "does a file exist there." Both are caught by parsing the file the way RFC 9116 describes: fields are Name: value, case-insensitive, # comments and blank lines ignored, and you honor the first valid Expires and compare it to now. It is not hard to do — it is just not what a presence check does.

"Not Found" and "Could Not Reach It" Are Different Facts

There is a second trap, and it is the one that makes a naive monitor lie to you over time.

If you build something to watch your own security.txt and it fails soft — treating any non-success as "no file" — then a DNS hiccup, a momentary 5xx, a WAF returning 403 to your checker, or a connection timeout all get recorded as "the file is gone." They are not. They are the absence of an answer, which is a completely different claim from the server telling you the file is absent.

The distinction that matters:

  • A 404 or 410 is the server saying "there is no file here." That is real information about the file.
  • A 403, a 429, a 5xx, a timeout, or a TLS failure is the server telling you about the server, not about the file. The file's status is unknown, not absent.

Collapsing "unknown" into "absent" is how a monitor drifts into crying wolf: every transient blip reads as a disappearance, you learn to ignore it, and then the real lapse — the Expires date quietly passing — arrives in the same undifferentiated noise you have trained yourself to dismiss. A check worth trusting only claims the file is gone when the server actually said so, and reports "I could not determine this" the rest of the time.

The Expires Field Is a Renewal Deadline, Same as a Certificate

Here is the mental model that makes this tractable: security.txt with a mandatory Expires is a certificate with worse tooling. It expires on a date you chose, its expiry is invisible until someone trips over it, and the failure mode is embarrassing — a real vulnerability report bouncing off a stale contact, or a researcher deciding you are not serious about disclosure because your own file says its data should not be trusted.

The reason people set a far-future Expires and forget it is precisely the reason the RFC discourages that: a distant expiry defeats the field's purpose, which is to force you to periodically confirm the contact is still real. A one-year Expires is a once-a-year renewal you will forget the same way certificate renewals get forgotten — right up until the clock runs out. And unlike a TLS certificate, nothing in your infrastructure reissues it. There is no ACME for security.txt. If you do not put a reminder on it, the only thing that will notice it lapsed is the researcher you wanted to hear from.

So the operational posture is the same one you already apply to TLS: do not trust that a file you deployed once is still valid — check the served artifact from outside, on a schedule, and watch the date. Parse the file a client actually receives, confirm it still has a Contact, and treat the Expires date as a deadline to monitor, not metadata to set and abandon.


Merlonix checks security.txt the way RFC 9116 says to read it, not just whether a file responds. On every check it fetches /.well-known/security.txt (with the legacy root fallback), confirms a real Contact field is present, parses the Expires timestamp, and flags a file whose date has passed as stale — the expired-but-present case that presence checks miss entirely. It distinguishes "the server said 404" from "I could not reach it," so a transient blip never reads as a disappearance. And because it sits on the same surface as your HTTP security headers, SPF/DKIM/DMARC posture, and DNSSEC, the disclosure file is monitored next to everything else a researcher or an attacker looks at first.

You can check any domain's live security.txt, headers, and TLS posture right now without signing up, and the free tools hub has the rest. A security.txt was the right thing to publish. Letting its Expires date pass unnoticed turns a signal of competence into a signal of neglect — and you will not find out from any tool that only asks whether the file exists.