Skip to content
use-cases / cert-renewal-no-ssh / hero
CRON · EXEC · NO SSH

Roll your TLS certificates without an SSH session

Hoody Cron carries the schedule. Hoody Exec carries the renewal logic. Sunday at 04:00 a curl fires, certbot runs, the new bundle is PATCHed into the proxy, and the reload is acknowledged. No shell session, no key in ~/.ssh, no jump host between you and the cert.

Read the cron docs
use-cases / cert-renewal-no-ssh / lifecycle

Three URLs, no shell

The whole renewal pipeline is two endpoints and a 5-field schedule. Cron triggers Exec. Exec calls ACME and PATCHes the proxy. The acknowledgement comes back as a 200.

RENEWAL LIFECYCLEPOSTED ONCE · RUNS WEEKLY
01 · SCHEDULE

POST one cron entry

POST /users/me/entries with schedule "0 4 * * 0" and command "curl /scripts/certs/renew". The cron service writes it into the user crontab and starts ticking. You did this once, from anywhere with HTTPS.

02 · RUN

Exec runs the script

Sunday 04:00, cron curls the exec URL. Exec spins up the script, talks to Let's Encrypt's ACME endpoint, validates the http-01 challenge, and writes the renewed bundle to /files. No node logging in, no session needed.

03 · RELOAD

PATCH the proxy bundle

The script PATCHes the new cert and key into the Hoody proxy's bundle endpoint. The proxy hot-reloads — no restart — and the next TLS handshake serves the new certificate. The whole cycle is under a minute.

Each step is an HTTP call you can replay from a terminal — `curl --dry-run` if you want to debug, `curl -i` if you want the headers. The pipeline doesn't depend on a logged-in session at any point.

use-cases / cert-renewal-no-ssh / mechanism

What the two endpoints look like

On the left, the cron entry that schedules the job. On the right, the exec script that does the work. Both are addressable via HTTP — both are auditable, both are replayable from any laptop or phone.

cron · register the schedule
POST · once
# register the weekly renewal
curl -X POST \
  https://cron.containers.hoody.com/users/me/entries \
  -H "Content-Type: application/json" \
  -d '[ "schedule":"0 4 * * 0", "command":"curl -fsS https://exec.containers.hoody.com/scripts/certs/renew", "comment":"weekly TLS renewal", "enabled":true ]'

# response
HTTP/1.1 201 Created
{ "id":"7a92", "schedule":"0 4 * * 0", "enabled":true }
exec · scripts/certs/renew.ts
GET · runs weekly
// scripts/certs/renew.ts
// @mode serverless
// @timeout 120000

const domains = ["your-app.com", "api.your-app.com", ...];

for (const d of domains) {
  const cert = await acme.order(d);
  await fetch("/api/v1/proxy/cert/bundle", {
    method: "PATCH",
    body: JSON.stringify({ d, cert })
  });
}

return { "renewed": domains.length };

Two URLs, one Sunday morning, zero SSH. The cron entry is data — POST it once and it's there until you DELETE it. The exec script is a file — change it via the API and the next run picks up the new logic. Nothing about this loop requires a person to be on a machine.

use-cases / cert-renewal-no-ssh / powers

Why this beats the SSH crontab

Same outcome — a renewed certificate every week — with three properties the legacy setup never had.

AUTH · URL TOKEN

No private key in ~/.ssh

Auth is a URL token, not an SSH key. Rotate it via the API and the old token stops working everywhere at once. No agent forwarding, no bastion, no key file you forgot you copied to a CI box three years ago.

AUDIT · HTTP TRACE

Every renewal is a request log

Each run is a row in the cron log and a request line in the exec log. Grep for who triggered it, when, what cert, what response code. The 200 from the proxy is the receipt that the reload landed.

DEBUG · DRY-RUN A CURL

You don't SSH to debug

Replay the renewal from your laptop with `curl /scripts/certs/renew?dry_run=true`. Watch the response, fix the script via the exec write API, retry. The whole loop happens over HTTPS — same pipe the production schedule uses.

use-cases / cert-renewal-no-ssh / capacity

What you skipped

Numbers from the deployment, not benchmarks. The shape is what matters: very few moving parts, all of them speak HTTP.

  1. SSH SESSIONS0

    Setup is a POST. Renewal is a GET on a schedule. Debug is a curl. There is no shell session in the pipeline at any point — your private key never has to be on the box.

  2. ENDPOINTS USED2

    One cron entry to schedule the run. One exec route that runs the script. The third URL — the proxy bundle PATCH — lives inside the script and reloads the cert.

  3. FIELDS · CRON SCHEDULE5

    Standard 5-field expression "0 4 * * 0" — Sunday 04:00. Or `@weekly` if you don't care which day. The cron service accepts both, plus auto-expiration for one-shot renewals.

Cron service: 5-field expressions and macros (`@hourly`, `@daily`, `@weekly`, `@monthly`, `@yearly`), per-user isolation, optional `expires_at`. Exec: V8 isolates, Bun runtime, magic comments for mode/timeout/CORS. ACME flow handled in your script — Hoody doesn't run the certbot for you, it runs your code on a schedule.

use-cases / cert-renewal-no-ssh / punchline

Renewal is a curl on a schedule — no shell session, no key, no jump host.

old · saturday before the certs expirednew · the cron has been running for nine months
WHAT THE OLD STACK LOOKED LIKEssh prod && certbot renew && systemctl reload nginxkey in ~/.ssh · bastion in front · prayer that the cron line survived the last reboot
WHAT IT LOOKS LIKE NOW
Read the exec docs
use-cases / cert-renewal-no-ssh / replaces

What this replaces

The standard ways to keep TLS valid in production. Each one wants either a shell session, a control-plane service, or both. The cron-plus-exec pair wants neither.

  • SSH + manual certbotA shell session, a key, a renew step you forget when you're on holiday
  • ssh-bastion + scriptA jump host you maintain just to run a one-line script
  • AWS Certificate Manager (closed-loop)Free, but only on AWS — and the cert never leaves their proxy
  • HashiCorp Vault PKIA whole secrets daemon to issue certs you renew once a week
  • cert-manager on KubernetesA controller, a CRD, a webhook — for what is two HTTP calls
  • custom renewal cron + jump host pipelineGlue scripts on a bastion, a key per CI runner, no audit trail
use-cases / cert-renewal-no-ssh / cta

Stop logging in to renew certs. POST the schedule once. Watch the proxy reload itself every Sunday morning.

Read the kit docs
use-cases / cert-renewal-no-ssh / related

Read the others