Hi all,
I would like to share a small Bash script that creates and renews signed
What it does
For every domain in a list it writes two files to
Features
Requirements
Linux with Bash,
Quick start: Plesk server or any server as root
Monthly renewal as a Plesk scheduled task: Tools & Settings > Scheduled Tasks > Add Task, task type "Run a command", system user root, command:
or with cron:
On the first run the script creates
Optional settings go into
To add all hosted domains from Plesk at once, look at the dry run first:
Quick start: SSH without root
Configuration goes to
Quick start: FTP-only web space
Run it on your own computer (Linux, macOS, or WSL on Windows). On Linux and WSL force the profile once:
Open a new terminal, then:
Upload the folder
The script prints the expiry date and the date by which to renew. Put it in your calendar; to renew, run
Things to know
Documentation
The attachments contain the script and the full documentation in English and German (installation for all three profiles, permissions, configuration, scheduling, troubleshooting) plus a FAQ that explains every message of the script and what to do about it:
The script is released under the MIT License, so you can use, change and share it freely.
Feedback, bug reports and suggestions are welcome.
Regards
Tessa
I would like to share a small Bash script that creates and renews signed
security.txt files (RFC 9116) and the matching pgp-key.txt for any number of domains. I wrote it for my own Plesk server first and then made it universal, so it also works on a VPS without root and even on FTP-only web space.What it does
For every domain in a list it writes two files to
<webroot>/.well-known/:security.txt- Contact, Expires, Encryption, Canonical, Preferred-Languages, clear-signedpgp-key.txt- the public key, referenced by theEncryptionfield
security@<domain>) in an isolated GnuPG keyring, so your normal keyring is never touched. Every run sets Expires to "now + 365 days", so a monthly scheduled task keeps the files from ever expiring.Features
- Three profiles in one script, detected automatically or forced with -p:
server- root on a server such as Plesk: many domains, chown per domain, config in/etc/security-txt/ssh- normal SSH user (VPS, web space): everything below $HOME, nochownftp- runs on your own computer (Linux, macOS, WSL) and writes to an output folder that you upload; it reminds you of the renewal date
- GnuPG is optional: without
gpgthe script says so and writes an unsignedsecurity.txt(valid according to RFC 9116) - Simple domain list
domain|webroot|owner|group|contactand the commandsadd,remove,list,run add example.comis enough on Plesk: the webroot defaults to/var/www/vhosts/<domain>/httpdocs, owner and group are read from that folder, and the files are created at oncecheckfetches the files over HTTPS and tests reachability, content type, expiry date and signatureimport-pleskfinds Plesk domains that are not in the list yet (dry run by default,--applyadds them)- random passphrase generated on the first run and stored in the keyring folder (mode 600); an existing keyring and passphrase can be reused
- files are replaced atomically, parallel runs are blocked with a lock, temporary files are cleaned up
- configurable contact address,
Preferred-Languages, optionalwwwCanonical line, validity in days
Requirements
Linux with Bash,
awk and curl (for check); GnuPG 2.1.22 or newer is optional but recommended. I developed and tested it with Bash 5.2 and GnuPG 2.4. The macOS date fallback and the old Bash 3.2 are untested.Quick start: Plesk server or any server as root
Code:
install -m 0755 -o root -g root security-txt.sh /usr/local/sbin/security-txt.sh
install -d -m 0755 -o root -g root /etc/security-txt
Code:
security-txt.sh add example.com
security-txt.sh add shop.example.com /var/www/vhosts/example.com/shop.example.com
security-txt.sh list
security-txt.sh check example.com
Monthly renewal as a Plesk scheduled task: Tools & Settings > Scheduled Tasks > Add Task, task type "Run a command", system user root, command:
Code:
/usr/local/sbin/security-txt.sh run
or with cron:
Code:
cat > /etc/cron.d/security-txt <<'EOF'
0 0 1 * * root /usr/local/sbin/security-txt.sh run >/dev/null
EOF
chmod 0644 /etc/cron.d/security-txt
On the first run the script creates
/root/.security_txt_gpg (700) with a random passphrase in passphrase (600). Please back up this folder together with the passphrase file. /etc/security-txt/ must only be writable by root, because the script runs as root and uses the paths and owners from the list.Optional settings go into
/etc/security-txt/security-txt.conf:
Code:
LANGUAGES="en, de"
WWW_CANONICAL=1
To add all hosted domains from Plesk at once, look at the dry run first:
Code:
security-txt.sh import-plesk
security-txt.sh import-plesk --apply
security-txt.sh run
Quick start: SSH without root
Code:
mkdir -p ~/bin
cp security-txt.sh ~/bin/security-txt.sh
chmod 0700 ~/bin/security-txt.sh
Code:
~/bin/security-txt.sh add example.com ~/httpdocs
~/bin/security-txt.sh check example.com
Configuration goes to
~/.config/security-txt/, the keyring to ~/.security_txt_gpg (700). The public files are written with mode 644 so that the web server can read them. Schedule it with crontab -e:
Code:
0 0 1 * * $HOME/bin/security-txt.sh run >/dev/null
Quick start: FTP-only web space
Run it on your own computer (Linux, macOS, or WSL on Windows). On Linux and WSL force the profile once:
Code:
mkdir -p ~/bin
cp security-txt.sh ~/bin/security-txt.sh
chmod 0700 ~/bin/security-txt.sh
echo 'export SECURITY_TXT_PROFILE=ftp' >> ~/.bashrc
Open a new terminal, then:
Code:
~/bin/security-txt.sh add example.com
Upload the folder
~/security-txt-output/example.com/.well-known/ into the web root of the domain (your FTP client must show hidden files), then verify:
Code:
~/bin/security-txt.sh check example.com
The script prints the expiry date and the date by which to renew. Put it in your calendar; to renew, run
~/bin/security-txt.sh run and upload again.Things to know
- The mailbox
security@<domain>should exist, or set another address in thecontactfield of the list. - RFC 9116 recommends an
Expiresdate less than a year ahead. The default is 365 days. SetDAYS_VALID=350if a checker complains. - The signature check in
checkverifies against the key published on the same host. It proves integrity, not the identity of the key owner. - In the
serverprofile the script runs as root and writes into folders owned by the domain users. Use it where you trust the domain owners; otherwise let each user run thesshprofile. - Subdomains are separate hosts: add each one with its webroot (
add sub.example.com /path/to/webroot), keepWWW_CANONICAL=0if your list contains subdomains. Two entries cannot share a webroot:addrefuses it andrunwarns about duplicates in a hand-edited list. - If an old keyring exists without a passphrase file, the script stops and tells you how to store the passphrase once.
Documentation
The attachments contain the script and the full documentation in English and German (installation for all three profiles, permissions, configuration, scheduling, troubleshooting) plus a FAQ that explains every message of the script and what to do about it:
security-txt.shREADME.md(English) andREADME.de.md(Deutsch)FAQ.md(English) andFAQ.de.md(Deutsch)CHANGELOG.mdLICENSE(MIT)
security-txt.sh --version). The script uses Semantic Versioning; changes are listed in the changelog. To update, just replace the script file: your list, settings and keyring stay as they are.The script is released under the MIT License, so you can use, change and share it freely.
Feedback, bug reports and suggestions are welcome.
Regards
Tessa