Skip to content
JVKellerPublic

About

TRMM Nginx Reverse Proxy Certificate Sync Setup

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

Repository files navigation

Cert-Sync

Zero-touch sync of renewed Let's Encrypt wildcard certificates from a certbot host to an nginx-based reverse proxy over SSH.

When you renew a wildcard cert with certbot, this tool pushes the new fullchain.pem / privkey.pem to your proxy and reloads it — automatically on every renewal. No manual copying, no proxy reconfiguration, no downtime.

It is a single self-contained bash script with an interactive installer. Run it once; renewals take care of themselves from then on.

Use case examples:

  • Certbot on a TacticalRMM server → Nginx Proxy Manager
  • Dedicated cert box → SWAG container
  • VPS with certbot → jwilder nginx-proxy on another server
  • Any nginx variant (nginx, OpenResty, Angie, Tengine) → any nginx-based proxy

Why this exists

Certbot renews a cert on host A, but the cert is actually served by a reverse proxy on host B (Nginx Proxy Manager, SWAG, plain nginx, etc.). Nothing in stock certbot bridges that gap. People end up copying PEM files by hand and forgetting, until a site starts serving an expired certificate.

Cert-Sync installs a certbot deploy hook on host A that, on every renewal:

  1. Confirms the local cert is actually valid (never pushes an expired cert).
  2. Asks the proxy itself where the certs for your domain live (via nginx -T) — so it keeps working even when the proxy renumbers its cert directories.
  3. Copies the renewed cert to the right place and reloads the proxy.

A daily drift watchdog compares the cert your proxy is actually serving against the one on disk and warns you if a sync was ever missed.


Supported proxies

Discovery is done by running nginx -T on the target, so anything nginx-based is auto-discovered with zero changes to the proxy:

Backend What it is Notes
baremetal-npm Nginx Proxy Manager (bare metal) Reloads via openresty; pushes through NPM's API so the UI expiry date stays correct
docker-npm Nginx Proxy Manager (Docker) Same API push, delivered via docker cp / docker exec
nginx Plain nginx / OpenResty / Angie / Tengine / freenginx (bare metal) Any nginx -T-capable binary
docker-nginx Any non-NPM nginx container — SWAG, jwilder nginx-proxy, plain nginx Discovered inside the container
static (fallback) Anything else You provide a fixed cert directory + reload command

Not supported for auto-discovery (not nginx — they use their own config formats and usually self-manage certificates): Traefik, Caddy, Pangolin, HAProxy, Apache, Envoy. You can still target these via the manual static destination fallback, but that requires configuring the proxy yourself.


Two editions

Both scripts are functionally identical — only the UI differs:

  • setup-certsync.sh — uses whiptail (preinstalled on Debian/Ubuntu). Classic blue fullscreen dialogs. Start here.
  • setup-certsync-gum.sh — uses gum for styled inline prompts. If gum isn't installed it offers to install it (from Charm's apt repo), and falls back to plain text prompts otherwise.

Either way, if no TUI is available the script degrades gracefully to plain read/echo prompts, so it still works over a dumb console.


Requirements

On the certbot host (any Linux box running certbot):

  • bash, ssh, scp, openssl, certbot
  • whiptail (or gum) for the nice UI — optional
  • certbot ≥ 2.3 for fully automatic syncing. Deploy hooks only fire on certbot certonly from 2.3 onward. If yours is older, the installer detects it and offers to upgrade certbot via a pip venv (/opt/certbot) — useful in Proxmox LXC containers where snap can't update. Below 2.3 everything still works; you just run sudo certsync after each renewal.

On the proxy host:

  • SSH access (the installer sets up a dedicated key for you)
  • nginx (any deployment) or docker with an nginx-based container
  • For NPM API push: NPM's /data/keys.json readable (default on NPM installs)

Usage

Copy the script to your certbot host and run it as the user that owns certbot (it uses sudo where needed):

chmod +x setup-certsync.sh
./setup-certsync.sh

The installer walks you through:

  1. Proxy connection — host/IP and SSH user. It generates a dedicated SSH key and authorizes it on the proxy (asks for the SSH password once).
  2. Certificate — auto-detected from your certbot lineages, or entered manually.
  3. Expiry pre-flight — if the local cert is missing or expired it stops with a red alert and offers to renew it right now (guided DNS-01 challenge).
  4. Discovery — deploys a small probe to the proxy, detects the backend, and finds where your certs are served from. You confirm or override.
  5. Hook install — installs the deploy hook, a sudo certsync manual trigger, registers a renew_hook, and (optionally) the drift watchdog.
  6. Initial sync — runs the whole pipeline once as an end-to-end test.
  7. (Optional) Reload services — if running on TacticalRMM, offers to run update.sh --force to apply changes; otherwise, you restart your services manually as needed.

Renewing certificates

Renew exactly as you do now, e.g. a manual DNS-01 wildcard:

sudo certbot certonly --manual --preferred-challenges dns \
  -d '*.example.com' -m you@example.com --agree-tos --no-eff-email
  • certbot ≥ 2.3 — the sync fires automatically when renewal completes.
  • certbot < 2.3 — run the sync by hand afterwards:
sudo certsync

sudo certsync is idempotent, re-checks expiry before touching anything, and logs every run.


What gets installed

On the certbot host:

Path Purpose
/etc/letsencrypt/renewal-hooks/deploy/copy-to-nginx.sh The deploy hook
/usr/local/bin/certsync Manual sync trigger (sudo certsync)
/usr/local/bin/certsync-check Drift watchdog (optional)
/etc/cron.d/certsync-watch Daily watchdog schedule (optional)
/var/log/certsync.log Timestamped log of every sync run
~/.ssh/id_trmm_to_nginx Dedicated SSH key for the proxy
A renew_hook line in the lineage's renewal/<name>.conf

On the proxy host:

Path Purpose
~/cert-probe.sh Cert-location discovery probe
~/cert-push.js NPM API push agent (NPM backends only)

Logs & troubleshooting

Every sync (automatic or manual) is logged with timestamps:

sudo tail -f /var/log/certsync.log

The first question for "did the sync run?" is always that file. A healthy run ends with cert-sync run finished OK.

The drift watchdog (/usr/local/bin/certsync-check) runs daily, stays silent when everything matches, and emails root (and exits non-zero) if the proxy is serving a different cert than the local one. It also works as a TacticalRMM script check. Run it manually any time:

sudo /usr/local/bin/certsync-check

NPM shows the old expiry date in its UI

For NPM backends the cert is pushed through NPM's own API, which updates its database — so the UI date should be correct. If you ever see a stale date after a direct file sync (fallback path), it's cosmetic: nginx serves the correct cert off disk; only NPM's stored expires_on is behind. Re-running the sync via the API path refreshes it.


Security notes

  • A dedicated SSH keypair is generated for this purpose only.
  • The deploy hook refuses to push a cert that fails openssl x509 -checkend 0, so an expired/stale cert can never clobber what the proxy is serving.
  • Cert discovery uses an exact allow-list (the literal *.domain block plus the subdomains you specify) — it will never touch a sibling subdomain that has its own dedicated certificate.
  • The NPM API push only updates Custom certificates matching your wildcard; NPM-managed Let's Encrypt entries are never overwritten.

License

Add your chosen license here (e.g. MIT) before publishing.

About

TRMM Nginx Reverse Proxy Certificate Sync Setup

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages