Certbot with trust.certlocker.io: deliver CertLocker certs to your hosts
Day-2 shape most teams want: CertLocker talks to the CA and keeps the key.
Stock Certbot on the host only pulls from trust.certlocker.io and reloads nginx/Apache.
Trust is not a second Let's Encrypt.
Happy path (production)
- ACME Workflow against Let's Encrypt (or your CA) with Auto renew on
- CERTIFICATE token with HTTPS + ACME, Single use off
- Install helper + token file on the host
- Run helper once, verify digests, point nginx/Apache at the live path, reload
- Daily systemd timer for delivery; Trust handles CA renewals on its schedule
~20m
First successful run
Stock Certbot
No custom client
HTTPS+ACME
One dual-protocol token
CSR mode
Key stays in CertLocker
Who this guide is for
Built for people who already run TLS in production:
- You have nginx/Apache (or a classic PEM bind) and still want stock Certbot on the box.
- You want CertLocker to own issuance, renewals, and the private key — hosts only receive PEMs.
- You are migrating off a host-local Let's Encrypt lineage without rewriting every reload script.
Skip this guide if the consumer is CertLocker edge HAProxy with native ACME (Part 1, Part 2). That path does not use Certbot.
The mental model: three keys
Most Certbot tutorials blur these together. Separate them once and the rest of the guide makes sense.
- ACME account key. Created and kept by Certbot. It signs ACME requests. CertLocker stores the matching public key against your token after registration.
- TLS / domain private key. Stored with the certificate in CertLocker. The helper downloads that key over the HTTPS token protocol, installs it on the host, and builds a CSR from it. Certbot never invents a new domain key in this flow.
- CA signing key. Stays with the CA. You never see it. The ACME certificate response returns certificates only, never a private key.
That is why this guide uses Certbot's certonly --csr mode plus a small helper.
CertLocker is delivering a certificate you already hold (with a matching key), not minting a brand-new keypair on the client.
Important difference from “normal” Certbot
Against Let's Encrypt, Certbot usually generates the domain key, proves HTTP-01 or DNS-01, and gets a brand-new cert.
Against trust.certlocker.io, inbound delivery returns the certificate already linked to your token.
Issuance and CA renewals happen earlier in CertLocker (outbound ACME Workflows). Certbot presents a CSR for the stored key and installs the leaf Trust returns — it does not turn Trust into a public CA.
Endpoints you will use
Hosted CertLocker (SaaS):
- ACME directory:
https://trust.certlocker.io/rest/acme/directory - API base for the HTTPS bundle helper:
https://trust.certlocker.io/rest/api - UI: https://trust.certlocker.io/
If you run CertLocker on-prem, swap the hostname for your installation.
The path shape (/rest/acme/directory and /rest/api) stays the same.
Prerequisites checklist
- A CertLocker account on
trust.certlocker.io(or on-prem) with rights for Certificates, Tokens, and ACME Workflows. - DNS control (or HTTP-01 reachability) for the first outbound order.
- A Linux host that can reach Trust over HTTPS;
sudo/root on that host. - The hostname in the cert SAN (examples use
certbot.example.com).
No custom Certbot plugin is required for inbound delivery from Trust.
Part A — Get a real certificate into CertLocker (then deliver with Certbot)
This is the part that trips people up. trust.certlocker.io inbound ACME is a delivery directory for certificates CertLocker already holds.
It is not Let's Encrypt. Certbot does not ask Trust to invent a brand-new leaf from scratch.
Trust returns the certificate linked to your token, and the CSR must match the private key CertLocker already stores.
Two ACME directions (do not reverse them)
- Outbound ACME (issue / renew): CertLocker is the ACME client against Let's Encrypt (or another CA). ACME Workflows prove DNS/HTTP, store the new leaf and private key in Certificates inventory. That is how certificates get created and updated.
- Inbound ACME (deliver): Certbot is the ACME client against
trust.certlocker.io. The helper reuses the CertLocker-held key, builds a matching CSR, and Certbot downloads the stored leaf. That is how hosts stay in sync with inventory.
So: you do not start this guide by clicking Certificates → Create (self-signed) for production. You start by putting a CA-issued cert + key into CertLocker (workflow or upload), mint a delivery token, then point Certbot at Trust. When the leaf needs updating later, outbound ACME (or a new upload) updates the CertLocker record first; the same Certbot helper run then pulls the new material using the same key lineage.
Which path should you use?
- 1a — ACME Workflows (recommended): CertLocker issues/renews from the CA into inventory and keeps the key. This is what you run in prod.
- 1b — Upload: migrate an existing leaf + key from an old Certbot host or vault, then move renewals to Workflows.
1a. Issue or renew with CertLocker ACME Workflows (recommended)
Open the ACME side of CertLocker and run a normal outbound order so inventory owns both the leaf and the private key:
- Create an ACME client for your issuer (Let's Encrypt staging first, then production).
- Attach a DNS provider (Cloudflare, etc.) when you want automated DNS-01.
- Open ACME Workflows → create an order for
certbot.example.com(or your hostname). - Leave Auto renew on so CertLocker schedules the next CA fetch (~30 days before expiry, or the 75% point for short-lived certs).
- Wait until the workflow is issued / complete. CertLocker writes the certificate into Certificates with its private key.
Full UI walkthrough with issuer picker and workflow screens: ACME end-to-end and ACME renewal. After the workflow succeeds, continue this guide from the inventory check — then the delivery token and Certbot.
How does Trust / Certbot know how to update the cert?
Short answer: Certbot does not drive CA renewals against Trust. CertLocker already re-fetches from the CA when you leave Auto renew on the ACME workflow. Certbot only re-pulls what inventory already has.
- Outbound workflow with Auto renew on: CertLocker schedules the next CA order (~30 days before expiry, or the 75% point toward expiry for short-lived certs).
- When that order runs, inventory updates the same certificate record (leaf + key).
- Delivery token stays pointed at that cert (renew the token only if the token itself expired).
- Host timer runs
certlocker-certbot-csr, verifies digests, reloads the consumer.
Trust already talks to the CA on a schedule. A daily host timer is enough to notice inventory after those renewals land. The domain key stays in CertLocker unless you deliberately rotate it.
1b. Upload: import an existing certificate and private key
Migrating from an old Certbot/Let's Encrypt host? Upload the leaf and the matching private key so CertLocker becomes source of truth, then deliver with this guide. Open Certificates → Upload.
- Name: a clear label, usually the primary hostname
- Certificate (PEM/CRT): required — paste or choose the leaf
- Private key (PEM/KEY): required for Certbot CSR delivery — paste or choose the matching key
- PFX / PKCS#12 is not supported here — convert to PEM first if needed
# Convert a PFX to PEM leaf + key (example) openssl pkcs12 -in site.pfx -clcerts -nokeys -out leaf.pem openssl pkcs12 -in site.pfx -nocerts -nodes -out key.pem
After upload, ongoing renewals should move to CertLocker ACME Workflows (1a) so the next leaf is issued into the same inventory record. Do not keep renewing only on the old host and hoping Trust updates itself.
Lab-only footnote: Certificates → Create can mint a self-signed pair to test tokens and the helper. Do not use that for production traffic.
2. Confirm the certificate in inventory
Back on Certificates, you should see an ACTIVE row for your hostname with a stored private key. Open it and confirm domain / SAN, algorithm, and expiry. This is the record the delivery token binds to.
3. Create a CERTIFICATE token with HTTPS and ACME
Go to Tokens. You need a token with all of the following:
- Type: CERTIFICATE
- Linked to the certificate from the workflow (or upload)
- Protocols: HTTPS and ACME (both)
- Status: ACTIVE
- Single use unchecked (renewals and re-runs need a reusable token)
- Not expired
Why both protocols? ACME is what Certbot talks to for register / new-order / finalize / download. HTTPS is what the helper uses to fetch the private-key bundle before Certbot starts. ACME alone is not enough for this flow.
Fastest path: use the green Certificate wizard on the Tokens page.
You can also use the main Create flow: choose type CERTIFICATE, then on step 2 enable both protocols and select the certificate.
When the UI shows the raw token value, copy it once into a password manager or paste it straight into the host file in the next section. Do not put it in Slack, tickets, git, or shell history.
Part B — Prepare the Certbot host
4. Install packages (first-time Certbot users)
On Debian / Ubuntu:
sudo apt-get update sudo apt-get install -y certbot openssl curl ca-certificates
Confirm Certbot is on your PATH: certbot --version.
Existing Certbot users can skip the install if the binary is already present and reasonably current (2.x is fine).
5. Install the CertLocker CSR helper
The helper fetches the CertLocker-held private key, builds a matching CSR, runs stock Certbot, and verifies the downloaded certificate before reporting success. Download it from this article and install it root-owned:
sudo curl -fsSL https://certlocker.io/downloads/certlocker-certbot-csr.sh.txt \ -o /usr/local/sbin/certlocker-certbot-csr sudo chmod 0755 /usr/local/sbin/certlocker-certbot-csr sudo chown root:root /usr/local/sbin/certlocker-certbot-csr
Prefer to review the script first? Open certlocker-certbot-csr.sh.txt, read it, then copy it into place.
6. Store the token on the host (never on the command line)
Copy the secret once from step 3. Do not put it in shell history, cron env files in git, Slack, or tickets.
sudo install -d -o root -g root -m 0700 /etc/letsencrypt-certlocker sudo install -o root -g root -m 0600 /dev/stdin \ /etc/letsencrypt-certlocker/token # paste token, Enter, Ctrl-D
The helper refuses group- or world-readable token files. That is intentional. On first install you can skip the rest of this section and come back when a timer starts failing with 401/403.
Day-2: token expired, rotated, suspended, or revoked
The host file is only a copy of the current secret. Fix the token in CertLocker, then rewrite the file — you do not need to reissue the certificate.
- EXPIRED → Renew in Tokens (usual fix)
- Rotate → new secret on the same token; old secret dies immediately
- SUSPENDED → Resume when access should continue
- REVOKED → create a new CERTIFICATE token (step 3); prefer renew/rotate when you only need more life or a fresh secret
sudo install -o root -g root -m 0600 /dev/stdin \ /etc/letsencrypt-certlocker/token # paste new token, Enter, Ctrl-D sudo ls -l /etc/letsencrypt-certlocker/token # expect -rw------- root root # then rerun the helper
Alert on token expiry before 03:15 discovers it. Keep Single use off for delivery tokens.
7. Check connectivity before the first run
curl --fail --silent --show-error \ https://trust.certlocker.io/rest/acme/directory >/dev/null echo "directory ok"
If this fails, fix DNS, firewall, or TLS trust on the host first. Do not set insecure TLS flags outside an isolated lab.
Part C — First run and verify
Point Certbot at Trust and run the helper. Trust returns the leaf already linked to the token; Certbot is not asking Let's Encrypt for a new public cert here.
8. Run the helper
Replace the domain with your certificate's exact hostname:
sudo env \ CERTLOCKER_ACME_DIRECTORY=https://trust.certlocker.io/rest/acme/directory \ CERTLOCKER_API_BASE=https://trust.certlocker.io/rest/api \ CERTLOCKER_TOKEN_FILE=/etc/letsencrypt-certlocker/token \ CERTLOCKER_DOMAIN=certbot.example.com \ /usr/local/sbin/certlocker-certbot-csr
What the helper does, in order:
- Validates token-file permissions.
- Fetches the certificate / private-key bundle over HTTPS without printing secrets.
- Installs the TLS key mode
0600and creates a SAN-bearing CSR. - Runs stock Certbot
certonly --csrwith stable account state under/etc/letsencrypt-certlocker/. - Verifies public-key match and certificate SAN.
- Writes
cert.pem,chain.pem, andfullchain.pemmode0644.
Default paths:
- Outputs:
/etc/letsencrypt/live/<domain>/ - Certbot account state:
/etc/letsencrypt-certlocker/<domain>/ - Work files:
/var/lib/letsencrypt-certlocker/<domain>/
Override with CERTLOCKER_OUTPUT_DIR, CERTLOCKER_STATE_DIR, and CERTLOCKER_WORK_DIR if your layout differs.
It is safe to rerun with the same reusable token and state directory. The helper replaces those three certificate files; it does not delete account state or the private key.
9. Verify the result on the host
sudo openssl x509 -in /etc/letsencrypt/live/certbot.example.com/cert.pem -noout -subject -ext subjectAltName sudo openssl x509 -in /etc/letsencrypt/live/certbot.example.com/cert.pem -pubkey -noout \ | openssl dgst -sha256 sudo openssl pkey -in /etc/letsencrypt/live/certbot.example.com/privkey.pem -pubout 2>/dev/null \ | openssl dgst -sha256
The two digests must match. If they do not, do not reload nginx, HAProxy, Apache, or anything else until you fix the CertLocker certificate/key pair and rerun.
10. Point nginx or Apache at the live path
The helper writes the same layout stock Certbot uses under /etc/letsencrypt/live/<domain>/.
Wire the vhost to those files, then reload only after digests match.
nginx
ssl_certificate /etc/letsencrypt/live/certbot.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/certbot.example.com/privkey.pem; # after a successful helper run: sudo nginx -t && sudo systemctl reload nginx
Apache (mod_ssl)
# Debian/Ubuntu: a2enmod ssl; site under sites-available/ # RHEL: httpd + mod_ssl; paths under /etc/httpd/ SSLEngine on SSLCertificateFile /etc/letsencrypt/live/certbot.example.com/cert.pem SSLCertificateKeyFile /etc/letsencrypt/live/certbot.example.com/privkey.pem SSLCertificateChainFile /etc/letsencrypt/live/certbot.example.com/chain.pem # Or use fullchain as the cert file if your Apache build prefers a single chain: # SSLCertificateFile /etc/letsencrypt/live/certbot.example.com/fullchain.pem # after a successful helper run: sudo apachectl configtest && sudo systemctl reload apache2 # RHEL/CentOS/Alma: sudo systemctl reload httpd
Same rule as nginx: never reload on a failed helper exit.
In the timer below, swap reload nginx for reload apache2 (or httpd) to match the consumer on that host.
11. Confirm the CertLocker audit trail
Back in the UI, open Audit. A healthy first run typically shows ACME registration, new-order, and certificate download events against the token.
Notes for existing Certbot users
- Do not point your old Let's Encrypt lineage at CertLocker and expect
certbot renewto keep working unchanged. CSR mode does not create a normal Certbot-owned renewable lineage because Certbot does not own the domain key. -
Keep your existing Let's Encrypt account directories alone if you still need them.
This helper uses
/etc/letsencrypt-certlocker/for CertLocker account state so the two do not collide. -
Your web server config can keep using
/etc/letsencrypt/live/<domain>/if that is already how nginx/Apache/HAProxy load PEMs. Just make sure reloads happen after the helper succeeds. -
If you previously used
--standalone,--webroot, ordns-cloudflareagainst a public CA, that authenticator is not what proves ownership against Trust's inbound delivery directory. Leave the helper defaults alone for production Trust delivery.
Keep the host in sync (cron or systemd)
certbot renew alone is not enough here — Certbot does not own the domain key.
CA renewals stay on the ACME Workflow (how updates work).
On the host you only need a timer that runs the helper, checks exit code 0, then reloads the consumer.
Example: root cron
# /etc/cron.d/certlocker-certbot SHELL=/bin/bash PATH=/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin 15 3 * * * root env \ CERTLOCKER_ACME_DIRECTORY=https://trust.certlocker.io/rest/acme/directory \ CERTLOCKER_API_BASE=https://trust.certlocker.io/rest/api \ CERTLOCKER_TOKEN_FILE=/etc/letsencrypt-certlocker/token \ CERTLOCKER_DOMAIN=certbot.example.com \ /usr/local/sbin/certlocker-certbot-csr \ && systemctl reload nginx # Apache: ... && systemctl reload apache2 # RHEL: ... && systemctl reload httpd
Example: systemd timer (preferred on fleets)
Prefer systemd where you already manage Certbot/nginx/Apache as units.
For Type=oneshot, ExecStartPost runs only after a successful helper exit — same idea as chaining with && in cron.
Change the reload unit to apache2 or httpd when that is the consumer.
# /etc/systemd/system/certlocker-certbot.service [Unit] Description=CertLocker Certbot CSR delivery After=network-online.target Wants=network-online.target [Service] Type=oneshot Environment=CERTLOCKER_ACME_DIRECTORY=https://trust.certlocker.io/rest/acme/directory Environment=CERTLOCKER_API_BASE=https://trust.certlocker.io/rest/api Environment=CERTLOCKER_TOKEN_FILE=/etc/letsencrypt-certlocker/token Environment=CERTLOCKER_DOMAIN=certbot.example.com ExecStart=/usr/local/sbin/certlocker-certbot-csr ExecStartPost=/bin/systemctl reload nginx # Apache: ExecStartPost=/bin/systemctl reload apache2 # RHEL: ExecStartPost=/bin/systemctl reload httpd # /etc/systemd/system/certlocker-certbot.timer [Unit] Description=Daily CertLocker Certbot delivery [Timer] OnCalendar=*-*-* 03:15:00 Persistent=true RandomizedDelaySec=20m [Install] WantedBy=timers.target # enable: # sudo systemctl daemon-reload # sudo systemctl enable --now certlocker-certbot.timer # sudo systemctl list-timers | grep certlocker
How to keep auto-renew healthy
- Delivery token: leave Single use off. Before expiry, use Renew in CertLocker (see
token lifecycle), then rewrite
/etc/letsencrypt-certlocker/token. A stale token is the most common reason a previously working timer starts failing. - Certificate material: leave workflow Auto renew on so Trust re-fetches from the CA on schedule (~30 days before expiry / 75% point). The host helper only needs to pull afterward. Do not renew only on an old Certbot host and expect Trust to notice.
- Account state: keep
/etc/letsencrypt-certlocker/<domain>/across runs so Certbot reuses the ACME account. Do not delete it on every cron tick. - Reload only on success: chain reload with
&&orExecStartPostafter a zero exit. A failed helper must not bounce the proxy onto a bad PEM. - Probe the live endpoint: after delivery, use a CertLocker probe (or equivalent) against the hostname:443 so you know clients see the new leaf, not only that files changed on disk.
- Watch the timer:
systemctl status certlocker-certbot.timer, journal logs, and CertLocker Audit for register / order / download failures. - Do not mix with stock
certbot renewfor the same lineage. If you still renew other hosts against Let's Encrypt with normal Certbot, keep those jobs and directories separate from/etc/letsencrypt-certlocker/.
Advanced: DNS-01 / HTTP-01 notes (usually not needed for Trust delivery)
Outbound ACME Workflows (Part A) may use HTTP-01 or DNS-01 against the public CA. That is configured on the workflow in CertLocker — not by Certbot on the host.
Inbound delivery against trust.certlocker.io does not require you to publish a TXT record.
Skip authenticator plugins for the happy path in this guide.
Only if you deliberately point the helper at another ACME directory that requires DNS-01 (lab / staging CA),
install a plugin such as python3-certbot-dns-cloudflare, keep credentials mode 0600, and set
CERTLOCKER_CERTBOT_AUTHENTICATOR / CERTLOCKER_CLOUDFLARE_CREDENTIALS /
CERTLOCKER_ACME_CONTACT_EMAIL. That is not the production Trust delivery path.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Invalid URL 'None' | Gateway older than the change that returns RFC 8555 finalize on new-order | Upgrade CertLocker / confirm curl of the order object includes a usable finalize URL |
| Account registration unauthorized | Token inactive, single-use already spent, missing ACME, expired, rotated secret not updated on host, or revoked | In Tokens: Renew if expired, Resume if suspended, or Rotate if the secret must change. Then rewrite /etc/letsencrypt-certlocker/token. Only create a new token if the old one is revoked. |
| Bundle download unauthorized | Token missing HTTPS, wrong cert/account/group scope, or host still has a pre-rotate secret | Enable HTTPS on the same token, confirm certificate linkage, renew/rotate in CertLocker if needed, replace the host token file, rerun |
| Worked yesterday, fails today with 401/403 | Token expired or was rotated/suspended in CertLocker while the host file stayed old | Open the token in CertLocker → Renew (or Resume) → update host token file → rerun helper. Do not recreate the certificate unless the cert/key pair itself changed. |
| Order / domain rejection | CSR SAN does not equal the token-linked certificate domain | Set CERTLOCKER_DOMAIN to the exact primary domain / SAN |
| Key mismatch after download | CertLocker cert and private key are not a matching pair | Update cert + key together in CertLocker, rerun helper, do not reload services yet |
| Helper refuses token file | Token file mode is group/world readable | sudo chmod 0600 /etc/letsencrypt-certlocker/token |
| Token revoked; renew not offered | Revoke is permanent for that secret/record path | Create a new CERTIFICATE token (step 3) on the same cert with HTTPS + ACME, store the new secret on the host, leave Single use off |
Security notes
- Keep the token file, Certbot account state, work directory, and any diagnostic logs root-only.
- Never commit token values, private keys, PEM bundles, or account-state files.
- The bundle API includes the token in its request path, so proxy and gateway access logs need sensitive-data retention controls.
- Do not use
CERTLOCKER_CERTBOT_INSECURE=trueoutside an isolated local test.
HAProxy: Certbot PEMs vs native ACME
Two supported paths talk to CertLocker's ACME directory. Pick one per host — do not mix them.
| Path | Client | When to use it |
|---|---|---|
| This guide | Stock Certbot + helper → /etc/letsencrypt/live/… | nginx, Apache, classic HAProxy crt file bind, any PEM consumer that already uses Certbot |
| HAProxy native ACME | HAProxy 3.3 acme + crt-store (no Certbot) | Edge HAProxy that speaks ACME itself with a seeded key (reuse-key on) |
If the consumer is edge HAProxy with native ACME, stop here and use those guides instead of this helper:
Classic HAProxy that only loads a PEM from disk can still use this Certbot path: helper timer, then
systemctl reload haproxy (or your reload hook) — not HAProxy's native acme renew socket command.
Quick checklist
- Issue/renew the leaf into CertLocker with ACME Workflows (or Upload a matched leaf+key) — not Certificates → Create for production
- ACTIVE certificate with its private key (CN/SAN = Certbot domain)
- Reusable CERTIFICATE token with HTTPS + ACME linked to that cert
- Token stored at
/etc/letsencrypt-certlocker/tokenmode0600 - Know how to Renew / Rotate the token in CertLocker and rewrite the host file when it expires
- Helper installed at
/usr/local/sbin/certlocker-certbot-csr curlto the ACME directory succeeds- Helper run succeeds; pubkey digests match; SAN correct
- nginx or Apache vhost points at
/etc/letsencrypt/live/<domain>/; reload only after verification - Audit shows register / order / download
- Auto-renew via systemd timer/cron calling the helper — not only
certbot renew - Renew delivery tokens before expiry; probe the live endpoint after delivery
- For CertLocker edge HAProxy, use native ACME + seeded
certlocker.acme.pem, not this Certbot helper
Summary
What ops actually runs: CertLocker owns CA issuance and renewals; a reusable HTTPS+ACME token authorizes the host;
certlocker-certbot-csr pulls leaf + key with stock Certbot CSR mode; nginx or Apache reloads only after digests match.
Treat Trust as delivery, not a second public CA — and do not expect stock certbot renew to drive this lineage.
Issue once in CertLocker. Deliver with Certbot.
Store the cert and key in inventory, mint a delivery token, and keep hosts in sync with a timer and an audit trail.
Get startedRelated articles
CertLocker ACME for IIS with win-acme
Same inbound directory idea on Windows IIS.
ACME end-to-end in CertLocker
Workflows, DNS clients, inventory, and tokens.
HAProxy native ACME key seeding
Three-PEM model, reuse-key on, and promoting into live.pem.
HAProxy 3.3 native ACME field notes
What broke getting native ACME working against CertLocker.
CertLocker vs Certbot
Client automation compared with lifecycle operations.