aethercert
Documentation

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

  1. 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.
  2. Back up the current files and binding.
  3. 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).
  4. Test the product's configuration, if it has a test command; stop on a non-zero result.
  5. 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.
  6. Verify: compare the fingerprint of the certificate the service actually serves with the new certificate.
  7. On failure after step 3: restore the backup from step 2, activate again, and exit with a non-zero code. Exit with 0 only 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.

On this page