security.txt Generator
An RFC 9116 security.txt with the Expires field it requires, which is the field most of the files on the web are missing or have let lapse.
# security.txt, as defined by RFC 9116.
#
# Upload this to BOTH of these paths, with the .well-known one as canonical:
# /.well-known/security.txt
# /security.txt
#
# It has to be served over https, as text/plain, and without a redirect to
# somewhere else. Expires is required: past that date the file is invalid.
Contact: mailto:[email protected]
Contact: https://example.com/security
Expires: 2026-12-31T23:59:59.000Z
Encryption: https://example.com/pgp-key.txt
Acknowledgments: https://example.com/hall-of-fame
Preferred-Languages: en, de
Canonical: https://example.com/.well-known/security.txt
Policy: https://example.com/security-policy
Output is valid and updates as you type.
Fix the highlighted fields to update the output.
A security.txt file tells somebody who has found a vulnerability in your site where to
send it. RFC 9116 defines it, and it has exactly two required fields: Contact and
Expires.
Expires is the one everybody forgets. Past that date the file is formally invalid, and a
large share of the security.txt files on the web are expired, which is worse than
absent: it says nobody has looked at this in over a year.
How to use
- Put in a contact address somebody actually reads.
- Set
Expiresabout a year out, and put a calendar reminder in for a month before. - Fill in the optional fields you have. Policy is the most useful of them.
- Upload to
/.well-known/security.txt, and put a copy at/security.txt.
Example
# security.txt, as defined by RFC 9116.
#
# Upload this to BOTH of these paths, with the .well-known one as canonical:
# /.well-known/security.txt
# /security.txt
#
# It has to be served over https, as text/plain, and without a redirect to
# somewhere else. Expires is required: past that date the file is invalid.
Contact: mailto:[email protected]
Contact: https://example.com/security
Expires: 2026-12-31T23:59:59.000Z
Encryption: https://example.com/pgp-key.txt
Acknowledgments: https://example.com/hall-of-fame
Preferred-Languages: en, de
Canonical: https://example.com/.well-known/security.txt
Policy: https://example.com/security-policy
Two Contact lines are allowed and the order is meaningful: it is the order you prefer to
be contacted in.
Pitfalls
A contact nobody reads is worse than nothing. The file is a promise that a report will reach a person. If it lands in a shared inbox with four hundred unread messages, the reporter’s next step is usually a public disclosure.
Expires is required and it is a real expiry. A year is the usual choice. Renewing it
is a one-line edit, and the reminder to do it is the actual work.
It has to be served as text/plain over https. A server that returns it as
text/html, or a redirect to a pretty page, fails the format. WordPress will happily
serve a static file from the web root; if a plugin is rewriting everything, check the
response headers.
Both paths, one canonical. /.well-known/security.txt is the location the RFC
defines. A copy at /security.txt is permitted for the people and scanners that look
there first, and the Canonical field says which one is official.
Acknowledgments, with no middle e. The field name is fixed by the RFC and a
misspelling is an unknown field. Same for Preferred-Languages, which appears once with a
comma-separated list rather than once per language.
Do not put the key in the file. Encryption takes a URL to a key, a dns: URI or an
openpgp4fpr: fingerprint. A PGP block pasted into the file is not what the field means.
A security.txt invites reports, including bad ones. Expect automated mails asking for
a bounty for a missing header. The Policy field is where you say what is in scope and
whether you pay, which cuts most of that down.
Compatibility
Everything runs in the browser: nothing is uploaded and nothing is stored.
The field order in the output is the conventional one, which does not matter to a parser:
RFC 9116 requires Contact and Expires to be present, and only Canonical and
Contact may appear more than once. Comments start with # and are ignored.
Expires is written as a full ISO 8601 timestamp with a Z, which is what the RFC asks
for. A bare date is a common mistake and a strict parser rejects it.
For WordPress, the simplest route is a static file in the web root:
wp-content is not involved, and you want /.well-known/security.txt to exist on disk.
If your host blocks dot-directories, a rewrite rule to a file elsewhere works, as long as
the response is a 200 with Content-Type: text/plain rather than a redirect.
Signing the file with PGP is allowed by the RFC and rarely done. If you do, the signed
block replaces the plain file and Canonical becomes load-bearing.
Frequently asked questions
Do I need one?
What should the Policy page say?
Will it attract attackers?
Policy field exists to filter.How do I check it is valid?
text/plain, and
there is no redirect. There are online validators for RFC 9116 as well, and the two fields
they check first are the two required ones.What if my site is on a subdomain?
security.txt on
example.com does not cover shop.example.com, and Canonical makes the intended scope
explicit.