Write a Script to Replace TLS Certificates Safely
How to script a TLS certificate swap on Windows or Linux that is safe to rerun: validate before install, keep a backup, activate, verify the served certificate, roll back on failure.
A script is the right tool when a product has no ready-made procedure. The script then is the procedure: it validates the new material, installs it safely, activates it, checks that it is really served, and can be run again without causing damage.
Before you begin
- Decide how the script gets its input:
- Certificate and private key as files in a protected location (readable only by the account running the script). Do not pass key material as command-line arguments - other processes can see them - and avoid environment variables for private keys, which child processes inherit and diagnostic tools can show.
- Settings such as the hostname, port or service name as arguments or environment variables.
- Store the script where only administrators can change it.
- Know what to restore on failure: the previous files, and the previous binding - the setting that tells the product which certificate to use, such as a thumbprint, an object name or a path.
What the script should do, in order
- Validate the input and stop with an error on any problem: missing files, wrong names in the SAN, wrong dates, a key that does not match the certificate, an incomplete chain.
- Back up the current files and binding.
- Install the new material in a protected, versioned location. Switch to it with an atomic step where possible - a rename or symlink switch on the same file system. Keep the permissions (Unix owner, mode and SELinux label; Windows key ACLs).
- Test the product's configuration, if it has a test command; stop on a non-zero result.
- Activate with the narrowest step that works - a reload rather than a restart - and wait until the service is healthy again, with a time limit.
- Verify: compare the fingerprint of the certificate the service actually serves with the new certificate.
- On failure after step 3: restore the backup from step 2, activate again, and exit
with a non-zero code. Exit with
0only if the new certificate is verifiably in use.
Make the script idempotent: running it twice with the same certificate must end in
the same state - no duplicate certificate objects, no permissions that change with each
run. Automated renewals retry, and a rerun must be harmless. If two renewals could run
at the same time against the same service, serialize them, for example with a lock file
(flock on Linux).
Skeleton (Linux)
#!/usr/bin/env bash
set -euo pipefail
CERT=$1 KEY=$2 HOST=$3 # file paths and the hostname to verify
DIR=/etc/myservice/tls
# 1. Validate
openssl x509 -in "$CERT" -noout -checkend 0
[ "$(openssl pkey -in "$KEY" -pubout | sha256sum)" = \
"$(openssl x509 -in "$CERT" -noout -pubkey | sha256sum)" ] || { echo "key mismatch" >&2; exit 1; }
# 2. Back up, 3. install
cp -p "$DIR/cert.pem" "$DIR/cert.pem.bak"; cp -p "$DIR/key.pem" "$DIR/key.pem.bak"
install -m 0644 "$CERT" "$DIR/cert.pem.new" && mv "$DIR/cert.pem.new" "$DIR/cert.pem"
install -m 0600 "$KEY" "$DIR/key.pem.new" && mv "$DIR/key.pem.new" "$DIR/key.pem"
rollback() {
mv "$DIR/cert.pem.bak" "$DIR/cert.pem"; mv "$DIR/key.pem.bak" "$DIR/key.pem"
systemctl reload myservice || true
echo "rolled back" >&2; exit 1
}
# 4. Test, 5. activate
myservice --check-config || rollback
systemctl reload myservice || rollback
# 6. Verify the served certificate
want=$(openssl x509 -in "$CERT" -noout -fingerprint -sha256)
got=$(openssl s_client -connect "$HOST:443" -servername "$HOST" </dev/null 2>/dev/null |
openssl x509 -noout -fingerprint -sha256)
[ "$want" = "$got" ] || rollback
echo "ok"Replace myservice --check-config, the paths and the port with your product's. On
Windows, the same structure applies in PowerShell: import with Import-PfxCertificate,
change the binding, test the served certificate, restore the old binding on failure -
and never put a PFX password into the script's source.
Rollback
The script's failure path (step 7) is the automatic rollback. Also keep a way to run it by hand - a rollback mode or a separate script - and test it before the first production run.
Common problems
- Key material appears in arguments, output or logs.
- The script copies the files but never activates them or checks the served certificate.
- Repeated runs create duplicate certificate objects.
- An error is caught and the script still exits with
0. - The rollback restores the files but not the binding.
Automating certificate replacement with aethercert
The aethercert custom-script targets run an administrator-provided script that already
exists in the agent's protected scripts directory and pass configured values as
AC_VAR_<NAME> environment variables. aethercert does not upload script content.
The script receives your variables and nothing else: aethercert writes no certificate,
chain or key for a custom-script target, and passes no paths to the script. If the hook
needs the material, give the certificate a second deploy target that writes the files,
and pass their paths to the script as variables.
See Deploy targets for the fields this preset takes, the permission gate a script has to pass, and what reaches its environment.