Skip to content

Add support for custom ACME servers - #617

Open
Sausageroll2077 wants to merge 1 commit into
linuxserver:masterfrom
Sausageroll2077:custom-acme-server
Open

Add support for custom ACME servers#617
Sausageroll2077 wants to merge 1 commit into
linuxserver:masterfrom
Sausageroll2077:custom-acme-server

Conversation

@Sausageroll2077

Copy link
Copy Markdown
Contributor

linuxserver.io


  • I have read the contributing guideline and understand that I have made the correct modifications

Description:

Adds a third cert provider option alongside Let's Encrypt and ZeroSSL: a user-defined ACME server. This lets SWAG obtain and renew certificates from an internal/private ACME CA such as step-ca.

New environment variables (all only used when CERTPROVIDER=custom):

VariableRequiredDescription
CERTPROVIDERURLyesACME directory URL of the custom server, e.g. https://ca.example.com/acme/acme/directory. If unset, the container logs an error and sleeps.
ACMECABUNDLEnoTrust an internal CA so certbot can reach the ACME server over TLS. Either a path to a CA bundle PEM mounted into the container, or the base64-encoded contents of that PEM. Written to /config/cabundle.pem and exported as REQUESTS_CA_BUNDLE.
EAB_KID / EAB_HMAC_KEYnoExternal Account Binding credentials for servers that require EAB. Both must be set together, or neither.

Implementation notes:

  • The custom server URL is written to certbot's cli.iniserver key, the same mechanism used for Let's Encrypt/ZeroSSL.
  • REQUESTS_CA_BUNDLE is exported during both initial issuance (init-certbot-config) and renewals (le-renew.sh), since it is an env var and is not persisted in cli.ini.
  • A stale /config/cabundle.pem is removed when switching back to a non-custom provider.
  • The custom provider and URL are tracked in .donoteditthisfile.conf for change-detection and revocation, so switching servers revokes against, and re-issues from, the correct ACME server.
  • EAB is optional and only consumed at account registration, so it has no effect on later renewals (matching the existing ZeroSSL behaviour).
  • Docs added to readme-vars.yml under the CERTPROVIDER option (README is auto-generated), plus a changelog entry.

Benefits of this PR and context:

Resolves request #186 for custom ACME server support. Right now there's no way to point SWAG at an internal ACME CA (step-ca, etc.). This will let you set CERTPROVIDER=custom and go. It's fully backwards compatible; nothing changes unless you opt in.

It's a fresh take on the abandoned #204, ported to the current s6-overlay v3 / s6-rc.d layout, and also wires up CA-bundle trust on the renewal path plus optional EAB support.

How Has This Been Tested?

Built the image locally and ran an end-to-end integration suite against real ACME servers on a Docker network:

step-ca (smallstep/step-ca) — issuance, trust, renewal

  • Issuance with ACMECABUNDLE provided as a file path — cert issued by the custom CA; server= set in cli.ini; /config/cabundle.pem written; no stale eab-kid.
  • Issuance with ACMECABUNDLE provided as base64 — bundle decoded and cert issued.
  • Missing CERTPROVIDERURL — container warns and does not issue.
  • Renewal via le-renew.sh: without the CA bundle the renewal fails with CERTIFICATE_VERIFY_FAILED; with it, certbot establishes a trusted connection and reaches the renewing stage — confirming REQUESTS_CA_BUNDLE is correctly exported on the cron renewal path.

Pebble (letsencrypt/pebble) with EAB required — EAB happy path

  • Correct EAB_KID + EAB_HMAC_KEY — cert issued; eab-kid/eab-hmac-key written to cli.ini; log confirms EAB registration.
  • Wrong EAB_HMAC_KEY — registration rejected by the server, no cert.
  • Only one of EAB_KID/EAB_HMAC_KEY set — container warns and does not issue.

Switching provider back from custom

  • custom (step-ca) → Let's Encrypt — the old cert is revoked against the custom CA (no TLS verify error), /config/cabundle.pem is removed, and server= is repointed to Let's Encrypt.
  • custom + EAB (Pebble) → Let's Encrypt — stale eab-kid/eab-hmac-key are cleared from cli.ini and the cabundle is removed, so Let's Encrypt registers cleanly.

All scenarios passed.

Source / References:

@Sausageroll2077
Sausageroll2077force-pushed the custom-acme-server branch 4 times, most recently from 5cd2857 to af4e945CompareJuly 15, 2026 19:33
@Sausageroll2077
Sausageroll2077force-pushed the custom-acme-server branch 2 times, most recently from 5bb9610 to 7c41502CompareAugust 8, 2026 05:35
Allow using a custom/internal ACME server (e.g. step-ca) via
CERTPROVIDER=custom and CERTPROVIDERURL=<acme directory url>.
- ACMECABUNDLE (file path or base64 PEM) is written to
/config/cabundle.pem and trusted via REQUESTS_CA_BUNDLE for both
initial issuance and renewals (le-renew.sh).
- Optional External Account Binding via EAB_KID/EAB_HMAC_KEY for
servers that require it; both must be set or neither.
- Custom provider is tracked for change-detection and revocation, so
switching servers re-issues against the correct ACME server.
Closeslinuxserver#186
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: PRs

Development

Successfully merging this pull request may close these issues.

Support custom ACME servers

2 participants

@Sausageroll2077@LinuxServer-CI