Skip to content

// docs · sample configs

Sample configurations

A starter for every CA and challenge. Each shows only the part that differs — the [store], [[distribute]], and [logging] blocks are the same across all of them.

Every example lives in examples/ and is ready to copy to /etc/syscert/syscert.toml and edit. The full annotated reference is full.toml — it documents every option. The deliver/store/logging tail below is identical in all of them:

[store]
path = "/var/lib/syscert"

[[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"          # key-bearing → not world-readable

[logging]
level  = "info"
format = "text"

What changes between setups is the CA and the challenge. Pick the one that matches how the CA can reach (or not reach) your host.

Let’s Encrypt · DNS-01 (Gandi)

Public certificate validated by a DNS TXT record — works even with no inbound ports. The most internal-friendly public option.

[cert]
hostname = "host.example.com"
key_type = "ec256"

[acme]
ca        = "letsencrypt"
email     = "you@example.com"
challenge = "dns-01"

[acme.dns]
provider = "gandiv5"            # creds via env: GANDIV5_PERSONAL_ACCESS_TOKEN
  • Needs: a DNS provider lego supports and its API token in the environment (e.g. GANDIV5_PERSONAL_ACCESS_TOKEN).
  • No inbound :80/:443 required — good for hosts behind NAT or a firewall.
  • Set propagation_check = "authoritative" if the host’s resolver is split-horizon / on a VPN / slow to see public DNS.
  • File: letsencrypt-dns-01-gandiv5.toml

Let’s Encrypt · HTTP-01

Simplest public setup — no DNS provider. The CA reaches the host on :80 to verify a token.

[cert]
hostname = "host.example.com"
key_type = "ec256"

[acme]
ca        = "letsencrypt"
email     = "you@example.com"
challenge = "http-01"           # CA validates over :80
  • Needs: the host publicly reachable on port 80.
  • Binding :80 as the unprivileged syscert user needs CAP_NET_BIND_SERVICE — add it to the unit’s AmbientCapabilities/CapabilityBoundingSet, and open the firewall (RHEL: firewall-cmd --add-service=http --permanent && --reload).
  • File: letsencrypt-http-01.toml

Let’s Encrypt · TLS-ALPN-01

Modern :443-only challenge (RFC 8737) — no DNS provider and no port 80.

[cert]
hostname = "host.example.com"
key_type = "ec256"

[acme]
ca        = "letsencrypt"
email     = "you@example.com"
challenge = "tls-alpn-01"       # CA validates over :443
  • Needs: the host publicly reachable on port 443.
  • Same CAP_NET_BIND_SERVICE + firewall note as HTTP-01 (for :443 / https).
  • File: letsencrypt-tls-alpn-01.toml

HashiCorp Vault · HTTP-01

Internal certificate from Vault’s PKI ACME endpoint. The defining change is ca = "custom" plus a directory_url.

[cert]
hostname = "app01.internal.lan"
key_type = "ec256"

[acme]
ca            = "custom"
directory_url = "https://vault.example.com:8200/v1/pki/acme/directory"
email         = "ops@example.com"
challenge     = "http-01"          # Vault: http-01 or tls-alpn-01 (NOT dns-01)

# bootstrap trust if the host doesn't trust Vault's CA yet, then `trust install`:
# ca_bundle   = "/etc/syscert/vault-ca.pem"
  • Vault PKI ACME has no dns-01 — use http-01 or tls-alpn-01.
  • Use IPv4 in the directory URL (Vault has an IPv6-ACME quirk). The mount needs ACME enabled (vault write pki/config/acme enabled=true + a pki/config/cluster).
  • If the host doesn’t trust Vault’s CA yet, set ca_bundle to bootstrap the ACME connection, then sudo syscert trust install for host-wide trust — see Troubleshooting.
  • If Vault requires EAB, set [acme.eab].kid + SYSCERT_EAB_HMAC in the env.
  • File: vault-http-01.toml

Smallstep step-ca · DNS-01

Internal certificate from a step-ca ACME provisioner. step-ca supports all three challenges; this one uses dns-01, so no inbound :80/:443.

[cert]
hostname = "app01.internal.lan"
key_type = "ec256"

[acme]
ca            = "custom"
directory_url = "https://ca.example.com:9000/acme/acme/directory"
email         = "ops@example.com"
challenge     = "dns-01"

[acme.dns]
provider = "cloudflare"            # creds via env: CLOUDFLARE_DNS_API_TOKEN
  • directory_url format: https://<ca-host>:9000/acme/<provisioner>/directory.
  • DNS-01 means no inbound ports — combine an internal CA with internal DNS.
  • If the provisioner has requireEAB, set [acme.eab].kid + SYSCERT_EAB_HMAC.
  • Bootstrap trust with ca_bundle, then sudo syscert trust install, the same as Vault.
  • File: stepca-dns-01.toml

Validate any of these offline before issuing: sudo -u syscert syscert dry-run --config-only --config ./syscert.toml.

Next: Configuration reference · Quick start