Skip to content

Configuration

gapcert reads YAML. Unknown fields are rejected, so a typo fails loudly instead of silently disabling a setting. Secrets are referenced by file path (kept under ~/.config), never inlined.

Config files by mode

Mode File Loaded by
Standalone ~/.config/gapcert/config.yaml (or $GAPCERT_CONFIG) gapcert issue/renew/status
Agent ~/.config/gapcert/agent.yaml (or $GAPCERT_AGENT_CONFIG) gapcert agent …
Broker operator-chosen path gapcertd …
Courier inside/outside operator-chosen paths gapcert courier export/run/import

Common fields

  • caletsencrypt, letsencrypt-staging, zerossl, or any ACME directory URL (an internal ACME CA such as step-ca works too). Plain http is refused except on loopback, so a mistyped scheme can't silently downgrade the channel.
  • email — the ACME account contact; required.
  • ca_bundle — a PEM bundle trusted for an internal ACME CA's TLS certificate.
  • data_dir — where certificates, keys, and account material live. Defaults to ~/.local/share/gapcert.
  • key_type (per certificate) — ec256 (default), ec384, rsa2048, rsa4096.
  • renew_before_days (per certificate) — renewal window; default 30.
  • hook (per certificate) — a shell command run after a successful (re)issue, with GAPCERT_NAME, GAPCERT_CERT_PATH, GAPCERT_KEY_PATH, and GAPCERT_LIVE_DIR in its environment. Bounded to a 10-minute timeout.

Split-horizon / airgapped resolvers

In split-horizon networks the local resolvers may never see the public TXT record gapcert just published, so the propagation pre-check can hang. Point it at resolvers that do see public DNS, or disable the pre-check and rely on the CA's own validation:

dns_resolvers: ["9.9.9.9:53"]
disable_propagation_check: true

DNS-01 solvers

The recommended pattern is acme-dns / a dedicated challenge zone: pre-create _acme-challenge.<name> CNAME <id>.acme.example.com so the solver credential can only ever write TXT records in a throwaway zone — never production DNS.

solvers:
  challenge-zone:
    type: acme-dns
    api_base: https://acme-dns.example.com
    storage_path: ~/.config/gapcert/acme-dns.json
solvers:
  cf:
    type: cloudflare
    token_file: ~/.config/gapcert/cloudflare-token   # zone-scoped token
solvers:
  aws:
    type: route53          # creds via env / ~/.aws / instance profile
    region: us-east-1
    hosted_zone_id: Z0000000000000
solvers:
  bind:
    type: rfc2136
    nameserver: ns1.example.com:53
    tsig_key: acme-update
    tsig_algorithm: hmac-sha256
    tsig_secret_file: ~/.config/gapcert/tsig-secret

Scope every credential as tightly as the provider allows (Route53 condition keys, Cloudflare zone-scoped tokens, TSIG limited to the challenge zone). RFC2136 refuses unauthenticated dynamic update.

Broker, wildcard, and courier fields

Those modes add a few fields — listen/server_names/policies for the broker, strategy: wildcard + agents for wildcards, and allow for the courier companion. They're documented on the broker, wildcard, and courier pages. A full annotated example lives in examples/config.example.yaml.

At-rest encryption (wildcard mode)

The broker encrypts shared wildcard keys with a master key resolved from GAPCERT_MASTER_KEY (base64 of exactly 32 bytes), else a persisted per-install random key at <data_dir>/master.key (mode 0600). There is no hardcoded default — if neither is available, the broker fails closed rather than encrypting with a known key.