中文版本 (中文)/Chinese Version | English
SSL certificate management CLI (certbot + DNS plugins).
Current first-class DNS providers:
- Aliyun DNS
- Cloudflare DNS
- AWS Route53
Current first-class delivery targets:
- filesystem / generic bundle
- nginx / openresty-like file layouts
- Kubernetes TLS Secret (
k8s-ingress) - AWS ACM import (
aws-acm)
CertMan now exposes four runtime surfaces built on the same config and service layer:
certman: local operator CLI fornew,renew,check,exportcertman-server: FastAPI control plane for/health, job submission, job query, webhook subscriptioncertman-worker: background job runner for queuedissueandrenewjobscertman-agent: node-side polling agent scaffold for controlled execution modecertman-scheduler: independent renewal scheduler (loop/cron/once)
Typical local commands:
uv run certman --help
uv run certman-server --data-dir data --config-file config.toml
uv run certman-worker --data-dir data --config-file config.toml --once
uv run certman-agent --data-dir data --config-file config.toml --once
uv run certman-scheduler run --data-dir data --config-file config.toml --loop
uv run certman-scheduler once --data-dir data --config-file config.toml --force-enablecertman(CLI local mode): no dependency on Kubernetes or Docker; can run directly via Python/uv.certman-server+certman-worker: no dependency on Kubernetes or Docker; Docker is optional for packaging/deploy.certman-scheduler: no dependency on Kubernetes or Docker; can run as standalone loop/cron/once process.certman-agent: no hard dependency on Kubernetes or Docker, but requires reachable control-plane endpoint.scripts/certman-docker.ps1/scripts/certman-docker.sh: requires Docker engine because they are docker wrappers.
Direct run examples (no k8s/docker required):
uv run certman --data-dir data entries
uv run certman --data-dir data check --warn-days 30 --force-renew-days 7
uv run certman --data-dir data oneshot-issue -d example.com -d *.example.com -sp aliyun --email ops@example.com --ak <ak> --sk <sk> -o /tmp/example.comuv run certman --data-dir data entries
uv run certman --data-dir data check --warn-days 30 --force-renew-days 7Default --data-dir is data/ (configurable).
data/conf/: ops-facing configconfig.example.toml: global config template (tracked)config.toml: global config (local, ignored)item_example.example.toml: entry template (tracked)item_*.toml: entry item configs (local, ignored).env: optional secrets (ignored).env.example: secrets template (tracked)
data/run/: runtime data (ignored)letsencrypt/: certbot state (recommended)
data/log/: execution logs (ignored), default keep 30 daysdata/output/: user-facing exported artifacts (ignored)
CertMan separates certificate issuance from certificate delivery.
Each entry can optionally declare one or more delivery_targets so that a
successful issue/renew job can continue with controlled post-processing, for
example:
- import the renewed certificate into AWS ACM
- update a Kubernetes TLS Secret consumed by Traefik or Ingress
Minimal example:
description = "example.com via route53"
primary_domain = "example.com"
dns_provider = "route53"
account_id = "AWS_DNS"
[[delivery_targets]]
enabled = true
type = "aws-acm"
account_id = "AWS_MAIN"
[delivery_targets.options]
regions = ["us-east-1"]
[[delivery_targets]]
enabled = true
type = "k8s-ingress"
scope = "kube-system/example-tls"
[delivery_targets.options]
mode = "apply"
rollback_on_failure = true
kubectl_bin = "kubectl"Notes:
aws-acmis the right target when the certificate will later be consumed by AWS managed services such as CloudFront or regional load balancers.- CloudFront requires the viewer certificate to exist in
us-east-1. k8s-ingresswrites or applies a standardkubernetes.io/tlsSecret.- Delivery targets do not replace DNS issuance credentials; they are a second, independent post-issue pipeline.
- Each delivery target is optional and can be explicitly enabled or disabled
with
enabled = true|false.
Docker Hub: nickfan/certman
GitHub Container Registry (GHCR): ghcr.io/nickfan/certman
edge: built frommasterlatest+X.Y.Z: built from git tags likevX.Y.Z
If GHCR images are not pullable (403) even though workflow succeeded:
- Go to GitHub repo Packages
certmanPackage settings set Visibility to Public.
The repository includes a compose service in docker-compose.yml:
services:
certman:
build: .
entrypoint: ["uv", "run", "certman", "--data-dir", "/data"]
volumes:
- ./data:/data
certman-server:
build: .
entrypoint: ["uv", "run", "certman-server", "--data-dir", "/data", "--config-file", "config.compose-server.toml"]
volumes:
- ./data:/data
ports:
- "8000:8000"
certman-worker:
build: .
entrypoint: ["uv", "run", "certman-worker", "--data-dir", "/data", "--config-file", "config.compose-server.toml", "--loop", "--interval-seconds", "30"]
volumes:
- ./data:/dataCommon compose commands (configuration-driven):
# 1) validate one or more explicit entries (recommended default)
docker compose run --rm certman config-validate --name <entry-name>
# 1.1) validate all merged entries explicitly
docker compose run --rm certman config-validate --all
# 2) list merged entries
docker compose run --rm certman entries
# 3) issue certificate for one entry
docker compose run --rm certman new --name <entry-name>
# 4) renew one entry or all entries
docker compose run --rm certman renew --name <entry-name>
docker compose run --rm certman renew --all
# 5) export certificate files for one entry or all entries
docker compose run --rm certman export --name <entry-name>
docker compose run --rm certman export --all
# 6) start control plane and worker
docker compose up certman-server certman-worker
# 7) start scheduler (independent process)
docker compose up certman-server certman-worker certman-schedulerOptional scripted e2e validation (compose/k8s):
python scripts/e2e-test.py --compose-only
python scripts/e2e-test.py --k8s-onlyServer API quick checks:
curl http://127.0.0.1:8000/health
curl -X POST http://127.0.0.1:8000/api/v1/certificates \
-H 'content-type: application/json' \
-d '{"entry_name":"site-a"}'
curl -X POST http://127.0.0.1:8000/api/v1/webhooks \
-H 'content-type: application/json' \
-d '{"topic":"job.completed","endpoint":"https://example.test/hook","secret":"topsecret"}'If [server].token_auth_enabled = true, add Authorization: Bearer <token> for protected REST endpoints.
Control-plane API documentation endpoints:
http://127.0.0.1:8000/docs
http://127.0.0.1:8000/redoc
http://127.0.0.1:8000/openapi.json
Remote operator examples via certmanctl:
uv run certmanctl --endpoint http://127.0.0.1:8000 health
uv run certmanctl --endpoint http://127.0.0.1:8000 cert create --entry-name site-a
uv run certmanctl --endpoint http://127.0.0.1:8000 job list --subject-id site-a
uv run certmanctl --endpoint http://127.0.0.1:8000 webhook listMore detailed docs:
- 📖 Documentation Guide (English) - Full navigation and all guides
- Quick guide: docs/en/quickguide-docker-compose.md
- Cookbook: docs/en/cookbook-compose.md
- Layered quick guide: docs/en/quickguide-layered.md
- Layered cookbook: docs/en/cookbook-layered.md
- Layered manual: docs/en/manual-layered.md
- API & AI access: docs/en/api-access.md
- DNS Providers: docs/en/dns-providers.md
- 📖 中文文档导航 (Chinese Guide) - 完整导航和所有指南
Run (example):
docker run --rm \
-v "$(pwd)/data:/data" \
-e CERTMAN_DATA_DIR=/data \
nickfan/certman:edge --helpCommon command examples:
docker run --rm \
-v "$(pwd)/data:/data" \
-e CERTMAN_DATA_DIR=/data \
nickfan/certman:edge check --warn-days 30 --force-renew-days 7docker run --rm `
-v "${PWD}/data:/data" `
-e CERTMAN_DATA_DIR=/data `
nickfan/certman:edge check --warn-days 30 --force-renew-days 7To avoid repeatedly typing mount/env/image options, use wrapper scripts and keep certman subcommand arguments outside.
- Linux/macOS (bash):
scripts/certman-docker.sh - Windows (PowerShell):
scripts/certman-docker.ps1
Examples (arguments stay external):
bash scripts/certman-docker.sh check --warn-days 30 --force-renew-days 7
bash scripts/certman-docker.sh renew --all.\scripts\certman-docker.ps1 check --warn-days 30 --force-renew-days 7
.\scripts\certman-docker.ps1 renew --allOptional environment overrides for wrapper scripts:
CERTMAN_IMAGE: override image tag (default:nickfan/certman:edge)CERTMAN_DATA_DIR_HOST: override host data dir mounted to/data(default:<project>/data)
Use release scripts when you need a consistent local build + publish flow for both Docker Hub and GHCR.
- PowerShell:
scripts/docker-image-release.ps1 - Shell:
scripts/docker-image-release.sh
Examples:
./scripts/docker-image-release.ps1 -Tag edge
./scripts/docker-image-release.ps1 -Tag edge -Pushbash scripts/docker-image-release.sh --tag edge
bash scripts/docker-image-release.sh --tag edge --pushNotes:
- Ensure
docker loginis done for both Docker Hub and GHCR before-Push/--push. - Default tags are published to both
nickfan/certman:<tag>andghcr.io/nickfan/certman:<tag>.
On Windows, certbot may require an elevated shell.
- Recommended (most reliable):
gsudo uv run certman new -n <name> - Alternative: run the terminal as Administrator
To see certbot progress in the terminal, add -v/--verbose.
For server mode, keep one terminal for certman-server and another for certman-worker --loop.
Credentials priority (all providers):
- If an entry has provider-specific
credentials.*fields, certman uses them directly (supports${ENV_VAR}references). - Otherwise, if an entry has
account_id, certman reads provider-specific environment variables fromdata/conf/.envor the process environment.- account_id is normalized for env lookup: trim, uppercase, and replace
-with_. - Aliyun:
CERTMAN_ALIYUN_<account_id>_ACCESS_KEY_IDandCERTMAN_ALIYUN_<account_id>_ACCESS_KEY_SECRET - Cloudflare:
CERTMAN_CLOUDFLARE_<account_id>_API_TOKEN - Route53:
CERTMAN_AWS_<account_id>_ACCESS_KEY_ID,CERTMAN_AWS_<account_id>_SECRET_ACCESS_KEY,CERTMAN_AWS_<account_id>_REGION
- account_id is normalized for env lookup: trim, uppercase, and replace
- Certbot is always invoked with a runtime credential/config file under
data/run/credentials/. certman refreshes it beforenewandrenew.
Currently supported DNS providers:
- Aliyun DNS
- Cloudflare DNS
- AWS Route53
Installed certbot DNS plugins are managed by uv through pyproject.toml.
Provider-specific setup examples, .env naming conventions, and command examples are documented in docs/dns-providers.md.
Default config behavior:
- Global config defaults to
data/conf/config.toml - It scans
data/conf/item_*.toml(configurable viascan_items_glob) and merges them into entries .envis optional; entries can either referenceaccount_id(ops mode) or embed credentials directly (portable mode)
Only check and return exit code (no auto-renew by default):
# warn when <=30d, fail when <=7d or expired
uv run python -m certman.cli --data-dir data --config-file your.toml check --warn-days 30 --force-renew-days 7
# optional: check and auto-fix (runs new/renew + export)
uv run python -m certman.cli --data-dir data --config-file your.toml check --warn-days 30 --force-renew-days 7 --fixRecommended flow:
- Scheduled
check(cron) to alert / decide action. - Scheduled
renewor manualnew --forcewhen needed. new/renewdefault自动export到data/output/<entry_name>/(可用--no-export关闭)。- Run
exportanytime to sync cert/key todata/output/<entry_name>/.
run_mode = "server"requires a[server]block withdb_path,listen_host, andlisten_port.- Optional REST auth switch:
[server].token_auth_enabled(defaultfalse). - Token precedence for certificate/job APIs when auth is enabled:
entries[].token>global.token. - If auth is enabled but no effective token exists for the current target, API returns
500 AUTH_TOKEN_CONFIG_ERROR. - If auth is enabled and token is required: missing token returns
401 AUTH_MISSING_TOKEN, wrong token returns401 AUTH_INVALID_TOKEN. - Webhook subscriptions are stored in the control-plane database and receive signed HTTP POST callbacks.
certman-workerprocesses queued jobs from the same SQLite database used bycertman-server.certman-agentremains the controlled-node entrypoint; Phase 4 security primitives are now available for its next control-plane integration step.- The current AI integration surface includes REST + OpenAPI and a stdio MCP server (
certman-mcp) that wraps control-plane APIs.
- Certbot outputs PEM files by default:
cert.pem,chain.pem,fullchain.pem,privkey.pem. *.pem,*.crt,*.cerare often the same PEM-encoded data with different extensions.crt/cercan also be DER (binary) in some ecosystems, but certbot's files here are PEM text.
- Nginx commonly uses PEM content regardless of extension:
ssl_certificateusually points to a full chain PEM (e.g.fullchain.pem)ssl_certificate_keypoints toprivkey.pem
If you need specific extensions for tooling, it is typically safe to copy/rename PEM files, as long as the consuming tool expects PEM (text) not DER (binary).
0: OK10: warning (<= warn_days)20: force-renew needed (<= force_renew_days or expired)30: missing cert files / entry missing