- Credits to: Torstein Eide
- First created: 2026-01-12
- License: MIT
A small, opinionated wrapper around the step CLI for renewing mTLS certificates in a way that plays nicely with systemd.
It consist of 3 parts, pre-check, Cert Renewal and post scripts.
This script is designed to be used from systemd-timers, cron, or service hooks where exit codes actually matter.
It solves a common annoyance:
step ca renewexits with code1when a certificate does not need renewal.systemdinterprets that as a failure.
This wrapper normalizes that behavior so:
- “Nothing to renew” is treated as
systemdsuccess - Real errors still fail hard
- Optional post-renew hooks are executed only when relevant
- Pre-checks whether renewal is needed
- Runs
step ca renewonly when required - Normalizes exit codes for
systemdcompatibility - Optional post-renew hook support
- Verbose logging mode for debugging
- Sets up
systemd-timersandsystemd-units.
- Installing of
Stepon server. - Installing of
Stepcertificate authority. - Creating the certificate in the first place.
- Coping the certificate to the server.
Do to the way systemd units are setup if the certificate does not need renewal this script will exit early with code 0. Instead of code 1 from the pre-check. This is to avoid systemd treating the non-renewal as an error.
The script checks if the certificate needs renewal using
step certificate needs-renewal command.
If the certificate does not need renewal, the script exits early with code 0.
If the certificate needs renewal, the script renews it using
step ca renew --force command.
Any executable scripts in ${BIN_POST_PATH} will be run after a successful renewal
to perform additional tasks.
Typical use cases:
- Reloading or Restarting services that use the certificate
- Converting certificate formats (e.g., PEM to PKCS12)
- Importing renewed certificates into application-specific keystores
- Rebuilding bundles
- Notifying monitoring systems
It assumes that there only one certificate per machine. if multiple certificates exist per machine,then post-renewal needs be split accordingly. For example by using creating specific post-renewal scripts for each certificate.
This needs to be done since this Step certificate renewal wrapper script try to not exit
with error if no renewal is needed. If ExecStartPost or OnSuccess is used in the systemd
unit file, it will be executed even if no renewal is needed, which not be desired behavior.
if ExcecStartPre is used, the script will exit with code 1 if no renewal is needed, which will
be treated as an error by systemd, which not be desired behavior.
That results in alert fatigue and unnecessary noice.
For example systemd unit file
In the cert-renewer@.service systemd unit file, you can set the BIN_POST_PATH environment variable like this:
[Service]
...
Environment=BIN_POST_PATH=/etc/step/bin-post-%i| Code | Meaning |
|---|---|
| 0 | Success (including “no renewal”) |
| >=1 | Real error (propagated) |
This script is more chatty when run from command line than when run from systemd. And can be more chatty if VERBOSE_LOG env-variable is set to 1.
several environment variables are set by systemd. These include:
| Variable | Default | Description |
|---|---|---|
| CERT_LOCATION | /etc/step/certs/%i.crt |
Path to the certificate to renew, default by systemd |
| KEY_LOCATION | /etc/step/certs/%i.key |
Path to the corresponding private key, default by systemd |
| STEPPATH | /usr/bin/step) |
Path to the step installation |
Additional environment variables can be set. These include:
| Variable | Default | Description |
|---|---|---|
| VERBOSE_LOG | 0 |
If set to 1, enables verbose logging |
| SKIP_CHECK | 0 |
If set to 1, skips the pre-renewal check |
| BIN_POST_PATH | /etc/step/bin-post |
Path to the post-renewal scripts |
-
git clone the repository to your server:
git clone https://github.com/yourusername/step-cert-renew.git
-
Copy the
step-renewscripts to your desired location, e.g.,/etc/step/bin/step-renew:scp -r install.sh bin/ bin-post/ systemd/ ${REMOTE_SERVER}:/etc/step -
Configure the systemd timer unit as needed, e.g., adjust the schedule in
cert-renewer@.timer.sudo nano /etc/step/systemd/cert-renewer@.timer
-
Run the installation script to install the systemd service and timer units:
sudo /etc/step/install.sh
-
Verify that the service and timer units are installed correctly:
systemctl list-units --type=service 'cert-renewer@*' --no-pager systemctl list-timers 'cert-renewer@*' --no-pager
-
Use
systemctlto edit and set environment variables for the service unit as needed:sudo systemctl edit cert-renewer@foo.bar.service
Example of environment variables to set:
### Editing /etc/systemd/system/cert-renewer@foo.bar.service.d/override.conf ### Anything between here and the comment below will become the contents of the drop-in file [Service] Environment=BIN_POST_PATH=/etc/step/bin-post-foo.bar Environment=HEALTHCHECKSIO_PING_URL=https://hc-ping.com/Super_secret_prodject/Server1-Cert-Renewed ### Edits below this comment will be discarded
This script is intended to be run by systemd as part of a timer unit. It can also be run manually for testing or debugging purposes.
CERT_LOCATION="/etc/step/certs/mycert.crt" \
KEY_LOCATION="/etc/step/certs/mycert.key" \
STEPPATH="/etc/step/bin/step-renew" \
VERBOSE_LOG=1 \
SKIP_CHECK=0 \
/etc/step/bin/step-renew