Automated internal TLS with ACME¶
Give internal services and ingress auto-renewing TLS certificates with the standard ACME protocol (RFC 8555) — the same clients you'd point at Let's Encrypt (certbot, acme.sh, lego, Caddy, Traefik, cert-manager), pointed at your own HSM-backed CA instead.
| File | Purpose |
|---|---|
config.yaml |
Server: ACME enabled against an issuing CA, all three challenge types, rate limiting |
Reference: docs/protocols/acme.md (EAB, wildcards/dns-01, ARI,
client-selectable profiles, STAR short-lived certs, MPIC).
1. Provision an issuing CA¶
Best practice is a root that only signs an intermediate, and an issuing intermediate that signs leaves:
$ secsy-ca -config config.yaml init-root \
-cn "Example Root" -label "Example Root"
$ secsy-ca -config config.yaml issue-intermediate \
-parent "Example Root" -cn "Example Issuing CA" -label "Example Issuing CA"
acme.ca_label in the config points at Example Issuing CA.
2. Run the server¶
The ACME directory is served at <base_url>/acme/directory:
Publish the root certificate to your clients' trust stores (it's the anchor the whole chain verifies to) — e.g. via your OS/base-image config-management.
3. Point a client at it¶
certbot (http-01)¶
$ certbot certonly \
--server https://pki.example.com/acme/directory \
--standalone -d app.example.com \
--agree-tos -m ops@example.com
lego (dns-01 — required for wildcards)¶
$ lego --server https://pki.example.com/acme/directory \
--email ops@example.com --dns <provider> \
--domains "*.example.com" run
Caddy (automatic HTTPS)¶
# Caddyfile
{
acme_ca https://pki.example.com/acme/directory
email ops@example.com
}
app.internal.example.com {
reverse_proxy localhost:8080
}
cert-manager (Kubernetes)¶
Use the ready-made issuer manifest in
deploy/cert-manager/clusterissuer-acme.yaml
(set server: to https://pki.example.com/acme/directory), then request
certificates as usual. See docs/deployment/kubernetes.md.
The bundled host agent¶
For non-Kubernetes hosts, secsy-agent is a pure-Go
EST/ACME renewal daemon with atomic installs, reload hooks, and ARI-aware
scheduling — a lighter alternative to a full ACME client + cron.
Restricting who can enroll (recommended for a private ACME server)¶
A private ACME server usually shouldn't let any client register. Turn on External Account Binding so only clients holding a pre-shared key you issued can create accounts:
acme:
require_eab: true
eab_hmac_keys:
team-web: "base64url-or-base64-hmac-key" # one per team/client, handed out of band
$ certbot register \
--server https://pki.example.com/acme/directory \
--eab-kid team-web --eab-hmac-key <the-key> \
--agree-tos -m ops@example.com
Notes¶
- Wildcards require
dns-01(offered here).http-01/tls-alpn-01validate single names. - Renewal is automatic with any ACME client's timer; the server advertises ACME Renewal Information (ARI) so clients renew around the CA's suggested window instead of a fixed fraction.
- Rate limiting is on in this config — a public ACME endpoint should always
meter requests and guard the HSM (see
docs/security/rate-limiting.md). - For very short-lived certificates without per-renewal ACME round-trips,
see ACME STAR (
acme.star,docs/protocols/acme.md).