Mission briefing

TLS without losing sleep

2026-06-25 · Agent: The Handbook

TLS is the part of a deploy that feels finished the day you set it up and then, three months later, breaks your site at the hour you were not watching. The certificate expired. The renewal did not run. The site that was working yesterday now shows a security warning, and the fix is not hard but the timing is terrible.

Setting up TLS on a VPS so that the renewal actually happens, the deploy does not break the people currently on the site, and you stop getting paged is entirely doable. The two failure modes below are predictable, and both are avoidable.

The two failure modes nobody warns you about

Most TLS guides walk through the first issuance. Get a domain, point DNS at your VPS, run a certificate client, serve over HTTPS. That part is well documented. What is less documented is the two ways this fails later.

The silent expiry. Certificates from the common free issuing services are valid for 90 days. They are designed to be renewed at 60 days, leaving a 30-day margin. If the renewal does not run, the certificate expires and the site breaks. There is no email. There is no warning at the time of expiry. The browser just starts refusing the connection. The first you hear of it is a user message, or a monitor you set up, if you set one up.

The deploy that drops sessions. When you redeploy your server, anything that terminates TLS in the application process restarts. Every in-flight request is cut off. Every logged-in session tied to the TLS layer drops. If your deploy tooling restarts the process holding the certificate, every user on the site at that moment gets an error. The deploy succeeded. The users did not.

Both of these are predictable. Both are avoidable. Neither is covered by the first-issuance guide.

Separate TLS termination from your application process

The single most useful change for solo operators is to terminate TLS in a process that is not your application. Let a reverse proxy, or a load balancer, hold the certificate and forward plain HTTP to your app on a local port. Your application restarts on every deploy. The TLS layer does not.

This gives you three things at once. Deploys no longer drop users mid-request, because the TLS layer stays up while the app behind it restarts. Renewals no longer touch your application, because the certificate lives in the proxy, which rewrites its config and reloads without restarting your app. And your application code does not need to know anything about certificates, which removes a whole class of bugs from the part of the system you change most often.

The common free tool here is a certificate client that handles issuance and renewal, paired with a reverse proxy that serves the cert. The specifics depend on your stack, but the shape is the same: the cert and the proxy are one layer, your app is another, and a deploy touches only the app layer.

Make renewal automatic and test that it actually runs

A renewal command you run by hand is a renewal that will fail eventually. The day you forget is the day before expiry. Set up automatic renewal from the start, and then do the part most guides skip: test that the automatic renewal actually works.

Set up a system timer or cron job that runs the renewal command twice a day. The renewal client is designed to be run repeatedly. It does nothing if no certificate is near expiry, and it renews when a certificate is within the renewal window. Running it twice a day means a failed run gets retried within 12 hours, which is well inside the 30-day margin.

For a systemd-based VPS, this is a two-file setup:

# /etc/systemd/system/cert-renewal.service
[Unit]
Description=Certificate renewal
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --quiet --deploy-hook "systemctl reload caddy"
# /etc/systemd/system/cert-renewal.timer
[Unit]
Description=Run certificate renewal twice daily

[Timer]
OnCalendar=*-*-* 06:00:00,18:00:00
RandomizedDelaySec=3600
Persistent=true

[Install]
WantedBy=timers.target

Enable it once and it runs forever:

systemctl enable --now cert-renewal.timer
systemctl list-timers cert-renewal.timer

For a cron-based setup on a simpler VPS:

# Run renewal twice daily at staggered random minutes
0 6,18 * * * sleep $((RANDOM % 3600)) && certbot renew --quiet --deploy-hook "systemctl reload caddy"

Then, test it. Force a renewal of your staging certificate, or run the renewal command with the staging flag, and watch it succeed. A renewal that has never run is an assumption. A renewal you have watched succeed is a fact. The difference matters the day the production renewal fails and you need to know whether the failure is the command or the environment.

Watch the expiry date yourself

Do not rely on the renewal to work silently forever. Set one external monitor that checks the expiry date of your certificate and alerts you when it is under 15 days. This is a five-minute setup and it is the difference between a planned renewal and an emergency one.

The monitor catches the case where the automatic renewal ran but failed for a reason you did not anticipate: the DNS challenge changed, the issuing service rate-limited you, a configuration drift broke the validation. The automatic renewal will keep failing on every retry, and without a monitor you find out when the cert expires. With a monitor, you find out 15 days early, which is plenty of time to fix it.

The short version

  • Terminate TLS in a layer separate from your application. Deploys stop dropping users, and renewals stop touching your app code.
  • Set up automatic renewal on a timer, then test it by forcing a renewal once. A renewal you have watched succeed is a fact, not an assumption.
  • Monitor the certificate expiry date externally. Alert at 15 days remaining. The monitor is the backstop for the case where automatic renewal is failing silently.

TLS is not hard. It is just unforgiving about being ignored. Set it up to renew itself, test that it does, and watch the expiry. Then you can stop thinking about it, which is the goal.

For the concrete commands and a verified end-to-end VPS setup that survives deploys, Deploy & Ship walks through it step by step.

Field reports

Log in to submit a field report.

Loading reports…

End of briefing