tls (https)

Native HTTPS for the “facing the internet” setup (no nginx). Behind a reverse proxy the proxy terminates TLS — skip this. TLS is declared PER SITE, in the root settings.json’s site map, plus one shared server.tls_port; certificates are picked by SNI, so different sites serve different certificates on the same port. Two certificate sources per site, exactly one:

keydefaultdescription
server.tls_port443the HTTPS port; must differ from server.port
site tls.cert / tls.keypaths to fullchain and private-key PEM, kept in the site’s folder (mode 1)
site tls.acmeLet’s Encrypt: the certificate covers the site’s domain and its aliases without tls of their own; email optional — contact for expiry notices

Let’s Encrypt (automatic) — the binary obtains and renews the certificates itself:

"server": { "host": "0.0.0.0", "port": 80, "tls_port": 443, "default": "example.com" },
"site": {
  "example.com": {
    "dir": "default",
    "alias": [ { "domain": "www.example.com", "mode": "redirect" } ],
    "tls": { "acme": { "email": "admin@example.com" } }
  }
}

cms tune direct --acme example.com,www.example.com writes exactly this shape. ACME state (account key, certificates) lives in the site’s tls/ folder, chmod 600. The domain list is hot: edit the map and cms reload — added domains are issued within a minute, removed ones stop being served.

Your own PEM files (certbot, corporate CA, …):

"tls": { "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).

Ports change roles when tls is on. The site is served over HTTPS on server.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 the site’s tls/ folder 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