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"
  }
}
keydefaultdescription
port443HTTPS port; must differ from server.port
cert / keypaths to fullchain and private-key PEM (mode 1)
acme.domainsone certificate covers all of them (SAN); apex + www is typical
acme.emailcontact for expiry notices
acme.dir./tlswhere 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.

← All articles in this group