Skip to main content

Manage CAP's own TLS certificate

CertAutoPilot ships with a self-signed certificate so the web UI is reachable over HTTPS on first boot (browsers will warn — that is expected). Once the platform is running you can have CertAutoPilot manage its own TLS: issue a real certificate for the controller's hostname, distribute it to the node(s) that serve the UI, and let the normal renewal machinery keep it fresh. No extra software and no product feature to enable — this is the ordinary SSH distribution flow pointed at CertAutoPilot's own hosts.

This works identically on a single host and a multi-node cluster — you simply list every node as a target.

Where the served certificate lives

On a standalone install, nginx terminates TLS and reads its certificate and key from:

/etc/certautopilot/tls/server.crt (root:root, mode 0644) — leaf + chain (fullchain)
/etc/certautopilot/tls/server.key (root:root, mode 0600) — private key

The backend itself listens as plain HTTP on 127.0.0.1; nginx is the only TLS terminator. Replacing these two files and reloading nginx swaps the served certificate without restarting the backend.

Other topologies
  • Kubernetes / Helm: the served certificate is not on disk — it is the <release>-tls Kubernetes Secret (type kubernetes.io/tls), consumed by both the backend and the frontend pods. Use cert-manager (the idiomatic K8s option) or the Kubernetes distribution module to write that Secret instead of the SSH recipe below.
  • Docker Compose: the shipped compose runs plain HTTP (dev only) — there is no TLS to manage.

Recipe (SSH distribution)

Prerequisites:

  • A certificate whose SAN covers the controller's public hostname (e.g. cap.example.com), issued from any CA you have configured, with auto-renew ON.
  • Root SSH access to every node (or a user with password-less sudo). Writing to /etc/certautopilot/tls, setting root:root ownership, and running systemctl reload nginx all require privilege. Key-based SSH as root is the simplest.
  • For the first certificate you need a working ACME challenge path — HTTP-01 (port 80 reachable to the controller) or DNS-01. Until that first certificate is issued and distributed, the self-signed one stays in place.

Steps (all in Settings → Distribution):

  1. Targets — add one SSH target per node (the two cluster IPs, e.g. cap-a and cap-b), or add both to a target group. A node distributing to itself over SSH is fine — no special handling needed.

  2. Path set — map the certificate artifacts to the exact paths nginx reads. This is a PathSet; the important columns are source, path, owner, and mode:

    fullchain /etc/certautopilot/tls/server.crt root:root 0644
    private_key /etc/certautopilot/tls/server.key root:root 0600

    Use fullchain (leaf + intermediates) for server.crt so clients get the full chain, and private_key for server.key at mode 0600. These match exactly what the installer's self-signed generator produced, so nginx needs no config change.

  3. Action set — reload nginx after the files are written. This is an ActionSet:

    sudo nginx -t
    sudo systemctl reload nginx

    nginx -t fails fast on a bad config before the reload. By default the action runs only when a written file actually changed, so a no-op redistribute won't reload.

  4. Module config + link — create an SSH module config referencing the target(s)/group, then link a distribution on the certificate to it.

That is all. On every renewal CertAutoPilot re-distributes automatically (the finalize step marks the distribution pending and enqueues it), so each node receives the new certificate and reloads nginx with zero manual steps.

Cluster notes

  • The distribution job is picked up by whichever node's worker is free and it SSHes out to every listed target, so all nodes get the certificate regardless of which node ran the job — including the node CertAutoPilot itself runs on.
  • Because the source of truth is the issued certificate (stored once in the shared database) and each node is an explicit SSH target, adding a third node is just adding a third target — nothing else changes.
  • Prefer a target group over individual targets so a new node joins by group membership and inherits the same path set / action set automatically.

Verifying

After the first distribution:

# On each node
openssl x509 -in /etc/certautopilot/tls/server.crt -noout -subject -enddate
# From a client
echo | openssl s_client -connect cap.example.com:443 -servername cap.example.com 2>/dev/null \
| openssl x509 -noout -issuer -enddate

The distribution's status in the UI shows per-target success; a failed reload marks that target failed (and, if auto-rollback is enabled, a rollback re-deploys the previous retained certificate version through the same paths).

See also