TLS operations

TLS Renewal Hooks Without Surprise Restarts

A renewed file is not the finish line. Validate the candidate, reload the consumer narrowly, and compare the certificate served at the public endpoint.

Find the process that consumes the files

ACME clients obtain certificates; application daemons decide when to read them. Some servers manage ACME and reload internally. Others read paths supplied by a separate client. Do not add a deployment hook until you know which process owns the active TLS socket and which files it opens.

sudo ss -H -lntp 'sport = :443'
systemctl cat example.service
sudo lsof -p "$(systemctl show -p MainPID --value example.service)" \
  | grep -E '\.(pem|crt|key)$'

Symlinked certificate directories can change targets during renewal. Check the final resolved paths, ownership, and mode rather than only the top-level link.

Validate the certificate and key as a pair

Inspect names, issuer, validity, and public-key identity before changing the service. Hashing the public keys is a simple format-independent match test.

openssl x509 -in fullchain.pem -noout \
  -subject -issuer -dates -ext subjectAltName
openssl x509 -in fullchain.pem -noout -pubkey | sha256sum
openssl pkey -in privkey.pem -pubout | sha256sum

The two public-key hashes must match. The private key should be readable by the service through the narrowest practical group or credential mechanism, and never be world-readable.

namei -l /etc/example/tls/privkey.pem
stat -c '%U %G %a %n' /etc/example/tls/privkey.pem

Make the deployment hook narrow

A deployment hook runs only after a successful renewal. It should validate application configuration, reload one service, and fail loudly. Avoid a restart when the daemon supports a reliable reload.

example-daemon validate --config /etc/example/config
systemctl reload example.service
systemctl is-active --quiet example.service
journalctl -u example.service --since '-5 minutes' -p warning --no-pager

A reload request returning zero only confirms that the manager delivered it. Review logs and the live socket. If the daemon keeps the old certificate after reload, document the required restart and schedule it rather than hiding downtime inside an automatic hook.

Inspect the certificate actually served

Read the public endpoint with the correct SNI. Compare its serial number and dates with the candidate on disk.

openssl s_client \
  -connect service.example:443 \
  -servername service.example </dev/null 2>/dev/null \
  | openssl x509 -noout -serial -subject -issuer -dates

Then make a normal HTTPS request from outside the host. This catches routing, hostname, chain, and trust problems that a local file inspection cannot.

curl --fail --show-error --max-time 15 \
  https://service.example/

Test the renewal client with its supported dry-run mode, but remember that staging issuance is not proof that the production hook, permissions, and public endpoint all work together.

Renewal checklist

  • Identify the active TLS process and its resolved file paths.
  • Check SANs, issuer, dates, and the certificate-key pair.
  • Keep private-key permissions narrow.
  • Validate application configuration before reload.
  • Review service logs and the live socket afterward.
  • Compare the served serial and dates through correct SNI.
  • Run a real external HTTPS request and monitor days to expiry.