tls (https)
Optional native HTTPS for the “facing the internet” setup (no nginx). Behind a reverse proxy the proxy terminates TLS — you don't need this block. Two certificate sources, pick exactly one; remove the block to disable HTTPS entirely.
Your own PEM files (certbot, corporate CA, …):
"tls": {
"port": 443,
"cert": "./tls/fullchain.pem",
"key": "./tls/privkey.pem"
}The server never talks to Let's Encrypt in this mode. If certbot rewrites the files, they are picked up without a restart (./cms reload or an hourly check).
Let's Encrypt (automatic) — the binary obtains and renews the certificate itself:
"tls": {
"port": 443,
"acme": {
"domains": ["example.com", "www.example.com"],
"email": "admin@example.com",
"dir": "./tls"
}
}| key | default | description |
|---|---|---|
| port | 443 | HTTPS port; must differ from server.port |
| cert / key | — | paths to fullchain and private-key PEM (mode 1) |
| acme.domains | — | one certificate covers all of them (SAN); apex + www is typical |
| acme.email | — | contact for expiry notices |
| acme.dir | ./tls | where account key + certificate live (chmod 600) |
Ports change roles when tls is on. The site is served over HTTPS on tls.port. Your server.port (usually 80) becomes a tiny HTTP service: it answers the ACME challenge at /.well-known/acme-challenge/* (always, even past expiry — so renewal keeps working) and 301-redirects everything else to https. Ports below 1024 need a capability: setcap 'cap_net_bind_service=+ep' ./cms or run under systemd.
Before using ACME: the domain's A/AAAA record must point to THIS server and port 80 must be reachable from the internet — that's how Let's Encrypt validates you. On the very first run, before the certificate is issued, a self-signed placeholder is served (browsers warn); it is swapped for the real one automatically.
Testing. Let's Encrypt rate-limits the real service (~5 duplicate certs/week). While you sort out DNS and ports, use staging — set the environment variable, not a config field: CMS_ACME_STAGING=1 ./cms run. Staging certs are untrusted but the limits are generous. Clear acme.dir when switching between staging and production.
Failures are loud where it matters: if the first ACME issue fails the server refuses to start and tells you what to check (DNS, port 80). A failed renewal is only a warning — the site keeps serving the current certificate and retries every 12 hours.