@fitfak/acme
v1.0.5
Published
Advanced ACME (RFC 8555) client — HTTP-01/DNS-01/TLS-ALPN-01, multi-domain/wildcard support, EAB (GTS/ZeroSSL) with automatic GTS OAuth2 provisioning, uncached authoritative DNS pre-verification, and automatic renewal.
Maintainers
Readme
FITFAK ACME Engine — Advanced Edition
A pure Node.js ACME (RFC 8555) client built on @fitfak/ssl. Works with
Let's Encrypt, Google Trust Services (GTS), and ZeroSSL.
What changed from the original acme.js?
| Problem (old acme.js) | Fix (new architecture) |
|---|---|
| Single global certs/account.json; different providers overwrote each other | accounts/<provider-host>/<name>.json — separated per provider + account |
| EAB re-sent on every run even when the account already existed → "already used" on GTS | If kid is on disk, newAccount is never called again |
| Only one domain, only HTTP-01 | Multi-domain (SAN), wildcard, HTTP-01 / DNS-01 / TLS-ALPN-01 |
| No DNS-01 (wildcards were impossible) | DNS-01 + Cloudflare/manual driver |
| DNS record could be missing and ACME was still queried (wasted rate limit) | TXT record confirmed directly against authoritative nameservers (uncached) before notifying ACME |
| Public-resolver propagation checks were slow, especially for wildcards | Authoritative-server queries bypass recursive-resolver caching entirely — much faster |
| GTS EAB required a manual Cloud Console visit every time | EAB minted automatically via Google's Public CA API, using an OAuth2 token or a service account |
| No automatic renewal | src/renew.js (cron/daemon capable) |
Install
npm installQuick Start — All 3 Providers, Practically
Each provider works out of the box with a single command once you have its
credentials (or none, for Let's Encrypt). --provider is a shorthand for
the correct ACME directory URL — you never need to type it manually.
# 1) Let's Encrypt — no account/EAB setup needed at all
node index.js --domain example.com --provider letsencrypt
# 2) Google Trust Services — EAB minted automatically from a service account
node index.js --domain example.com --provider gts \
--gts-service-account ./gcp-service-account.json --gts-project my-gcp-project
# 3) ZeroSSL — EAB minted automatically from just an email (no API key needed)
node index.js --domain example.com --provider zerossl --zerossl-email [email protected]All three write the resulting certificate to certs/example.com/{cert.pem,key.pem}
and a renewal.json that src/renew.js uses for automatic renewal later —
no extra configuration required per provider.
Usage
# Simple HTTP-01 (Let's Encrypt, requires port 80)
node index.js --domain fitfak.net
# Wildcard + apex together (dns-01 is forced automatically; manual TXT entry)
node index.js --domain fitfak.net --domain "*.fitfak.net"
# Automatic DNS-01 via Cloudflare
node index.js --domain fitfak.net --domain "*.fitfak.net" \
--challenge dns-01 --dns-driver cloudflare --cf-token $CF_TOKEN
# Google Trust Services with a manually obtained EAB
node index.js --domain fitfak.net \
--acme-url https://dv.acme-v02.api.pki.goog/directory \
--eab-kid $GTS_KID --eab-hmac $GTS_HMAC
# Google Trust Services with AUTOMATIC EAB provisioning (no Cloud Console needed)
node index.js --domain fitfak.net \
--acme-url https://dv.acme-v02.api.pki.goog/directory \
--gts-service-account ./gcp-service-account.json --gts-project my-gcp-project
# TLS-ALPN-01 (port 443 must be free)
node index.js --domain fitfak.net --challenge tls-alpn-01Run node index.js --help for the full option list.
Google Trust Services — Automatic EAB
Instead of manually running gcloud publicca external-account-keys create
or clicking through Cloud Console every time you register a new account,
this client can call Google's official Public Certificate Authority API
directly:
--gts-oauth-token— pass an already-obtained OAuth2 access token (e.g.$(gcloud auth print-access-token)).--gts-service-account <path>— pass a service-account JSON key file; the client performs the standard JWT-Bearer OAuth2 exchange itself and mints a fresh access token automatically, so nothing needs to be copied by hand.--gts-project <project-id>— required with either option above.
One-time setup on the Google Cloud side (not needed per run):
gcloud services enable publicca.googleapis.com
gcloud projects add-iam-policy-binding PROJECT_ID \
--member=serviceAccount:YOUR_SA_EMAIL \
--role=roles/publicca.externalAccountKeyCreatorZeroSSL — Automatic EAB
ZeroSSL's own REST API can mint EAB credentials directly, avoiding a visit to the dashboard's "Developer" tab for every account:
--zerossl-access-key <key>— an API access key generated once in the ZeroSSL dashboard (Developer -> API Access Key). Since March 2022, ZeroSSL EAB credentials are reusable, so one key comfortably backs many issuances/renewals.--zerossl-email <email>— simplest option, no API key at all: ties EAB directly to an email address. ZeroSSL automatically creates a free account for that address if one doesn't already exist.
Automatic Renewal
Every certificate issuance automatically creates
certs/<domain>/renewal.json (contains NO secrets — DNS API tokens etc.
are read from environment variables).
# Environment variables needed for certificates using Cloudflare:
export FITFAK_DNS_DRIVER=cloudflare
export FITFAK_DNS_CF_API_TOKEN=xxxx
# One-off check (renews anything with fewer than 30 days left)
node src/renew.js --once
# Long-running daemon (checks once a day)
node src/renew.js --daemonExample crontab entry:
0 3 * * * cd /path/to/fitfak-acme && FITFAK_DNS_DRIVER=cloudflare FITFAK_DNS_CF_API_TOKEN=xxxx node src/renew.js --once >> renew.log 2>&1Directory Layout
src/
acme-client.js Low-level JWS/HTTP ACME protocol layer
acme-issue.js Main orchestrator (account + order + challenge + finalize)
account.js Provider+domain scoped account persistence
providers.js Let's Encrypt/GTS/ZeroSSL profile detection, EAB pre-check
gts-eab.js Automatic GTS EAB provisioning via OAuth2/service account
dns-verify.js Uncached authoritative-nameserver DNS pre-verification
dns-driver-env.js Environment-variable driver selection for cron/daemon
renew.js Expiry check + automatic renewal
challenges/
http01.js HTTP-01 (port 80)
dns01.js DNS-01 (forced for wildcards, pre-verified)
tlsalpn01.js TLS-ALPN-01 (port 443, RFC 8737)
dns-drivers/
cloudflare.js Cloudflare API driver
manual.js Manual (interactive) driver
index.js CLI entry pointKnown Limitations
- TLS-ALPN-01 requires port 443 to be directly reachable (not behind a reverse proxy). If a web server is already bound to 443, use DNS-01 instead.
- Only Cloudflare has a ready-made DNS driver. For other providers (Route53,
DigitalOcean, etc.), implement the same interface
(
setTxtRecord/removeTxtRecord) undersrc/dns-drivers/. - The authoritative-nameserver DNS pre-verification requires outbound UDP/TCP port 53 access from wherever this client runs (to reach nameservers directly) — it will not work behind a network that only permits DNS through a fixed internal resolver.
