Skip to content

// docs · configuration

Configuration reference

Everything syscert reads from syscert.toml, section by section. Secrets are the one thing that never live here.

Configuration is TOML. The default location is /etc/syscert/syscert.toml; override it with --config <path> or the SYSCERT_CONFIG environment variable (precedence: --config flag → SYSCERT_CONFIG → default). The shipped systemd unit reads SYSCERT_CONFIG from /etc/default/syscert, so you can point the service at a different file once, with no unit edit.

Ready-to-edit files live in the repo’s examples/ — a fully-commented full.toml covering every option, plus focused starters for Let’s Encrypt, Vault and step-ca.

Secrets never go in this file. DNS-provider tokens, CA credentials and the EAB HMAC are read from the environment (typically /etc/syscert/secrets, mode 0640) and are never logged. See [acme.dns].

[cert] — certificate subject

KeyDefaultDescription
hostnamesystem FQDNThe name the cert is built around. If empty, syscert uses the host’s FQDN; if the host has no FQDN it errors and refuses to run (it never guesses).
sans[]Extra DNS Subject Alternative Names.
ip_sans[]IP SANs. Setting this forces the challenge to http-01/tls-alpn-01 (RFC 8738 forbids DNS-01 for IPs) and the CA must reach the host on :80/:443. Private (RFC 1918) IPs require an internal CA — a public CA is rejected up front.
key_typeec256ec256 · ec384 · rsa2048 · rsa4096. A fresh keypair is generated each renewal.
reuse_keyfalseKeep the same keypair across renewals — only needed if a consumer pins the public key.
[cert]
hostname = "host.example.com"
sans     = ["www.example.com"]
key_type = "ec256"

[acme] — CA and challenge

KeyDefaultDescription
carequiredletsencrypt (public CA — built-in directory URLs + --staging) · custom (any internal/other ACME CA: Vault, step-ca, … set via directory_url).
directory_urlper-CAThe ACME directory endpoint URL. Required when ca = "custom"; for letsencrypt it defaults to production.
emailrequiredACME account contact address.
challengedns-01dns-01 · http-01 · tls-alpn-01 · dns-persist-01. Auto-switched to http-01/tls-alpn-01 when ip_sans is set; dns-persist-01 is opt-in and capability-checked at runtime. http-01/tls-alpn-01 need the CA to reach this host on inbound :80/:443; dns-01 needs no inbound ports.
profile""ACME profile to request (e.g. shortlived → ~6-day certs, required for public-CA IP certs). Validated at runtime against the directory’s meta.profiles.
ca_bundle""Path to a PEM of the internal CA to trust for the ACME connection only (not the system store). Bootstraps issuance against a Vault/step-ca the host doesn’t trust yet; syscert warns when it’s set. See Troubleshooting.

Which directory_url for your CA

CAcadirectory_url
Let’s Encryptletsencryptleave empty → production. Set staging https://acme-staging-v02.api.letsencrypt.org/directory while testing.
HashiCorp Vault PKIcustomhttps://<vault>:8200/v1/<mount>/acme/directory. Requires ACME enabled on the mount.
Smallstep step-cacustomhttps://<ca-host>:9000/acme/<provisioner>/directory.
Any other ACME servercustomits RFC 8555 directory URL.

Challenge support differs by CA. Vault PKI ACME supports http-01 and tls-alpn-01 only — not dns-01. step-ca supports all three. Let’s Encrypt supports dns-01, http-01, tls-alpn-01.

# Let's Encrypt (production) via DNS-01
[acme]
ca        = "letsencrypt"
email     = "you@example.com"
challenge = "dns-01"

# HashiCorp Vault (internal CA) via HTTP-01
[acme]
ca            = "custom"
directory_url = "https://vault.example.com:8200/v1/pki/acme/directory"
email         = "you@example.com"
challenge     = "http-01"

[acme.dns] — DNS provider + credentials

Used only when challenge is dns-01 or dns-persist-01.

KeyDefaultDescription
provider""Any lego DNS-provider id (e.g. cloudflare, gandiv5, route53).
propagation_checkallall — visible on the local resolver and the authoritative NS (lego default). authoritative — verify only on the CA’s authoritative NS; skip the local check (use on split-horizon/VPN/slow resolvers). off — skip the local pre-check entirely.

Credentials are supplied via the environment (or a restricted secrets file), never in the config. Each lego provider reads its own variables — e.g. CLOUDFLARE_DNS_API_TOKEN (cloudflare), GANDIV5_PERSONAL_ACCESS_TOKEN (Gandi LiveDNS), or AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_REGION (route53). See the lego provider docs for the exact names.

[acme.dns]
provider          = "gandiv5"
propagation_check = "authoritative"   # only if the local resolver is slow to see public DNS

[acme.eab] — External Account Binding

Some CAs require External Account Binding to register an ACME account: they issue a Key ID + HMAC key out-of-band and the client proves possession at registration. Used by Vault (eab_policy), step-ca (requireEAB), and public CAs like ZeroSSL / Google / SSL.com.

KeyDefaultDescription
kid""EAB Key ID. Setting it enables EAB. An identifier, not a secret — fine in this file.

The HMAC key is a secret — supply it via SYSCERT_EAB_HMAC (the base64url key the CA gave you) in /etc/syscert/secrets, never in the TOML and never logged. EAB is checked by the CA only when the account is first created; syscert reuses its persistent account key afterwards.

[acme.eab]
kid = "kid-from-your-ca"          # + export SYSCERT_EAB_HMAC=<base64url-hmac> in /etc/syscert/secrets

[store] — canonical store

KeyDefaultDescription
path/var/lib/syscertWhere syscert keeps the source-of-truth cert material and ACME account state. Owned by the syscert user; key-bearing files kept 0600.

[bundle] — all-in-one file

Controls the composition of bundle.pem.

KeyDefaultDescription
order["cert","chain","root","key"]Components and their order. Omit a token to exclude it. Tokens: cert (leaf), chain (intermediates), root, key.
[bundle]
order = ["key", "cert", "chain"]   # key first, no root

The root is dropped automatically when the CA provides none (public CAs). If key is present, any target receiving bundle must use a non-world-readable mode.

[[distribute]] — delivering to consumers

Zero or more blocks; each copies one artifact to a path with the ownership/mode/context that consumer needs. syscert overwrites only the paths it manages and does not reload consumers — see Distributing certs.

KeyDescription
artifact (required)Which file to place: cert · privkey · chain · fullchain · bundle.
path (required)Destination path.
owner / groupFile owner and group.
modeOctal mode, e.g. "0644". privkey/bundle hold the key — a world-readable mode is rejected.
selinux_contextOptional SELinux file context (RHEL family), e.g. cert_t.
[[distribute]]
artifact = "fullchain"
path     = "/etc/nginx/tls/fullchain.pem"
owner    = "root"
group    = "root"
mode     = "0644"

[[distribute]]
artifact = "privkey"
path     = "/etc/nginx/tls/privkey.pem"
owner    = "root"
group    = "root"
mode     = "0600"

[renewal] / [logging]

KeyDefaultDescription
renewal.renew_before"" (auto)Empty = derive the window from the cert’s lifetime (short-lived certs renew ~daily; long-lived use a wide window). Set a duration like "30d" to override.
logging.levelinfodebug · info · warn · error.
logging.formattexttext (journald-friendly) · json.

Operational logs (events, errors, and lego’s ACME output) go to stderr; command results and prompts go to stdout. Secret values are never logged.


Next: Distributing certs · Troubleshooting · full.toml on GitHub