Skip to main content

Full Configuration Example

A complete configuration file with every available option. All optional fields are included with their default values or representative examples.

Comments indicate which fields are required, optional, or mode-specific.

# Base directory for runtime files (SQLite, TLS cache, logs).
# Default: %ProgramData%\certeasy (Windows) | /var/lib/certeasy (Linux)
workdir: "C:\\ProgramData\\certeasy"

# ── Database ──────────────────────────────────────────────────────────────────
# Omit this section entirely to use SQLite with all defaults.
database:
driver: sqlite # sqlite | postgres | sqlserver
path: "" # SQLite only — defaults to %WORKDIR%/db.sqlite
dsn: "" # PostgreSQL and SQL Server connection string
ping-timeout-sec: 10
max-idle-conn: 2 # Default: 2 (SQLite) | 5 (postgres/sqlserver)
max-conn: 10

# ── Server ────────────────────────────────────────────────────────────────────
server:
url:
- "https://acme.corp.internal" # Public URL(s) for ACME clients — required
listen: "0.0.0.0:8443"
read-header-timeout: 5s
read-timeout: 10s
write-timeout: 30s
idle-timeout: 60s
max-body-bytes: 1048576 # 1 MB
shutdown-timeout: 10s
remote-ip-header: "X-Forwarded-For" # Only used when trusted-proxies is set
trusted-proxies:
- "10.0.0.0/8"

# ── Logs ──────────────────────────────────────────────────────────────────────
logs:
level: info # debug | info | warn | error
format: json # json | text
output: file # stderr | stdout | file
file: "C:\\ProgramData\\certeasy\\certeasy.log"
rotate:
max-size-mb: 100
max-backups: 10
services: # Per-service log level overrides
DB-Driver: warn
adcs: info
Certeasy-acme-server: info
Async-Acme-Pki-Handler: info
Async-Acme-Challenges: info
JWKS: warn
worker: info
http-server: info
tags: # Free-form labels added to every log entry (Grafana/Loki)
instance: cert-srv-01
region: eu-west

# ── TLS Certificate Manager ───────────────────────────────────────────────────
tls-certificate-manager:
acquire-timeout: 2m # pki mode only
renew-before: 720h # pki mode only — 30 days
pki-poll-interval: 2s # pki mode only
file-watch-interval: 5s # files mode only
local-pki-cache-dir: "%WORKDIR%\\server-certificate-cache" # pki mode only
bundles:
- name: public
hosts:
- "acme.corp.internal"
mode: pki # files | pki
authority: ca1 # pki mode — authority name
# files mode fields (use instead of authority):
# local-cert-file: "C:\\certeasy\\tls\\fullchain.pem"
# local-key-file: "C:\\certeasy\\tls\\privkey.pem"

# ── DNS Validation Profiles ───────────────────────────────────────────────────
dns-validation-profiles:
- name: internal-default
mode: local # local only (remote: not yet implemented)
timeout: "" # overall validation timeout
zones:
- suffixes:
- "corp.internal"
system: true # use system DNS resolver
dns-server: "" # explicit resolver (overrides system)
authoritative: false
dnssec: false
protocol: udp # udp | tcp
resolved-ip-policy:
allow-cidrs:
- "10.0.0.0/8"
deny-cidrs:
- "127.0.0.0/8"
- "169.254.0.0/16"
- "::1/128"
- "fe80::/10"

# ── Authorities ───────────────────────────────────────────────────────────────
authorities:
- name: ca1
type: adcs # adcs | fake
configuration:
ca-name: "PKI\\LAB-RootCA" # as shown by certutil -CA
certificate-template: "ACME-Template-Server"
certreq-path: "certreq.exe" # full path if not in PATH
default-timeout: 10m
cert-util-timeout: 30s

# Fake PKI for local testing — do not use in production
# - name: test-ca
# type: fake
# configuration:
# common-name: "Certeasy Test CA"
# password: "testpassword"
# key-size: 2048
# validity: 8760h

# ── Issuance Policies ─────────────────────────────────────────────────────────
issuance-policies:
- name: corp-server
dns-validation-profile: internal-default # required if multiple profiles exist
dns:
allow:
- ".corp.internal/3" # non-wildcard names, max 3 labels
- "*.corp.internal" # wildcard at zone root only
deny:
- "=forbidden.corp.internal" # exact match deny
signature:
allowed-algorithms:
- "RSA-SHA256"
- "RSA-SHA384"
- "RSA-SHA512"
- "ECDSA-SHA256"
- "ECDSA-SHA384"
- "ECDSA-SHA512"
- "ED25519"
min-rsa-bits: 3072
allowed-ec-curves:
- "P-256"
- "P-384"
# CSR Extended Key Usage whitelist. Default: serverAuth only.
# See SECURITY WARNING in configuration/issuance-policies.md before
# adding non-server purposes — back-end ADCS templates configured as
# "Supply in the request" will honor the CSR's EKU.
#
# clientAuth note: acme.sh's default OpenSSL template emits
# EKU=serverAuth,clientAuth. The CA/B Forum baseline forbids this
# combination on publicly-trusted certs from June 2026 onwards. Keep
# the entry below ONLY if you must support unmodified acme.sh; lego
# and certbot emit serverAuth only and don't need it. See
# configuration/issuance-policies.md for the full discussion.
# csr:
# allowed-extra-eku:
# - clientAuth
# # - codeSigning
# # - emailProtection
# # raw OID also accepted, e.g. Microsoft EFS:
# # - "1.3.6.1.4.1.311.10.3.4"

# ── Policy Bindings ───────────────────────────────────────────────────────────
# Can be omitted when there is exactly one policy and one authority.
policy-bindings:
- policy: corp-server
authorities:
- ca1
strategy: first_available # first_available | round_robin

# ── Workers ───────────────────────────────────────────────────────────────────
workers:
worker-id: "worker"
workers: 4
lease: 30s
idle-min: 50ms
idle-max: 200ms
base-backoff: 1s
max-backoff: 2m
queue-size: 4 # defaults to value of workers

# ── Rate Limiting ─────────────────────────────────────────────────────────────
# Omit this section entirely to apply the defaults shown below.
# Whitelist is intentionally empty — secure by default, no IP is auto-trusted.
rate-limiting:
whitelist: # OPTIONAL — entries bypass IP-based limits
# Add only if you have a specific reason
# (e.g. monitoring probes you control).
# Example values (commented out by default):
# - "127.0.0.1"
# - "10.42.0.0/16"

global: # Per-IP token bucket on every endpoint
enabled: true
requests-per-minute: 200
burst: 20

account-creation: # Per-IP cap on new-account
enabled: true
per-ip-per-hour: 5
burst: 2

order-creation: # Per-account caps on new-order
enabled: true
orders-per-account-per-hour: 20
order-burst: 5
san-budget-per-account-per-hour: 100

duplicate-certificate: # Anti-runaway: same FQDN set per account
enabled: true # Set to false to disable entirely
max-per-window: 5
window: 168h # 7 days

failed-validation: # Anti-misconfig: bucket per (account, hostname)
enabled: true # Counter lives in memory only
max-per-window: 5
window: 1h

pending-authorizations: # Anti-DoS: in-flight authzs per account
enabled: true
max: 30 # Calibrated for 1 machine = 1 account

# ── Renewal Information (ARI, RFC 9773) ───────────────────────────────────────
# Always active — endpoint advertised in /directory as renewalInfo.
# Omit this section to apply the defaults.
renewal-info:
lifetime-fraction: 0.66 # Window opens at notBefore + lifetime*0.66
window-width: 48h # Spread renewals across this duration
retry-after: 6h # Sent as Retry-After header on responses

# ── Audit log (HMAC-chained JSONL) ────────────────────────────────────────────
# Enabled by default. Omit this section to apply the defaults shown below.
# Verify the chain with: certeasy audit verify -f config.yml
audit:
enabled: true
path: "" # Empty → <workdir>/audit.log
rotate:
max-size-mb: 0 # 0 → no in-process rotation (let logrotate / Task Scheduler handle it)
max-backups: 0 # Ignored when max-size-mb is 0