SSH module
The SSH module copies the certificate + private key + chain to a target host using SCP/SFTP, then runs post-deployment actions (typically systemctl reload nginx or equivalent). It is the most-used distribution module and the only one with full rollback — every file we overwrite is first saved as .bak, so a rollback restores the exact previous state.
Overview
- Transport: SSH v2 (SCP for writes).
- Auth: username + password, SSH private key (with or without passphrase), or TLS client certificate.
- Path set: defines where each file goes (cert, key, chain) with ownership + mode.
- Action set: shell commands run after writes, typically to reload the consuming service.
- Rollback: full. Previous cert/key are kept as
<path>.bak; rollback swaps them back and re-runs the action set. - Concurrency: per-target-group batch concurrency is capped by
DistributionSSHMaxConcurrency(Settings → General).
Prerequisites
- An SSH module credential (key or password) saved under Settings → Distribution → Credentials.
- Target hosts reachable from the backend. The backend's pod or VM must have SSH egress to every target.
- A user account on each target with write permission to the cert/key directories, plus
sudo(NOPASSWD) for the action set if it uses privileged commands. - A path set and an optional action set.
Create an SSH target
- Settings → Distribution → Targets → New.
- Module type: SSH.
- Hostname + port (default
22). - Username: the account on the remote host.
- Credential: pick the SSH key credential you created.
- Host-key strategy:
- Trust on first use (TOFU) (default) — accept the first host key seen and pin it thereafter.
- Pinned — paste a known-good host key fingerprint. The module refuses to connect if it changes.
- Save → health check runs a connect +
echo ok.
Path set
A path set defines the files you want CertAutoPilot to write, with ownership and mode. Example for nginx:
/etc/nginx/ssl/${{ CAP_DOMAIN_SLUG }}.crt root:root 0644 <cert+chain>
/etc/nginx/ssl/${{ CAP_DOMAIN_SLUG }}.key root:root 0600 <private key>
/etc/nginx/ssl/${{ CAP_DOMAIN_SLUG }}.ca root:root 0644 <chain only>
Paths use ${{ NAME }} placeholder substitution — not Go templates. CAP_DOMAIN_SLUG is a reserved certificate-context variable (the primary domain lowercased to [a-z0-9-], wildcard *. stripped so it stays path-safe); the other reserved vars are CAP_PRIMARY_DOMAIN, CAP_CERT_TOKEN, and CAP_DEPLOY_NAME. You can reference project variables (Variables) the same way, but secret-flagged variables are rejected in paths. Typical practice is one path set per service template (nginx, haproxy, apache, postgres, dovecot, etc.) and reuse it across many targets.
Action set
Actions run over SSH in sequence after every file has been written. Example:
sudo nginx -t
sudo systemctl reload nginx
Use nginx -t-style preflight to fail fast before reloading. A non-zero exit status marks the target as failed and triggers rollback if it's enabled for the distribution.
By default the action runs only when a written file actually changes (hash-based skip), so reloads stay idempotent on no-op redistributes. Set run_always on the action set to run it on every distribution even when nothing changed — for verification/diagnostic actions that must run each time.
Execute a distribution
- On the cert detail page → Distribution tab → Add distribution.
- Pick the SSH module, a target or target group, a path set, and an action set.
- Optionally enable validation endpoints so we confirm the cert is live post-deploy (see Validation).
- Click Dry-run first. The module returns the exact list of files it will write and commands it will execute, per target. Review.
- Execute. Progress shows per target; a green tick means files written + action set returned 0. Red means failure — open the target's log to see whether it was transport (
network), credential (auth), or post-action (validation) failure.
Rollback
SSH rollback is the gold standard:
- Before any overwrite, the module copies the existing file to
<path>.bak. - On rollback, the module swaps:
<path>becomes<path>.rollback-tmp,<path>.bakbecomes<path>, the temp file is deleted. - The action set re-runs (most services expect a reload after cert change).
Rollback metadata is stored on the distribution (LastRollbackMeta). The UI surfaces a Rollback button when this metadata is present.
If a distribution was the first one to put a cert at the configured path (no prior file), rollback will delete the cert rather than restore — there is no .bak to swap in. The service's reload will then fail, which is usually what you want (it surfaces the rollback clearly).
Troubleshooting
SSH auth fails despite a valid key
Permission issue on the remote side. The user needs write on the path-set directories and passwordless sudo on the action-set commands. Check ~/.ssh/authorized_keys contains the right public key and the sudoers entry is specific enough.
Host key mismatch after a rebuild
You rebuilt the target VM and the host key changed. If you were using TOFU, clear the pinned fingerprint on the target detail page; if pinned, paste the new fingerprint (and verify it out-of-band).
Action set times out
Long-running reload (systemd's default is 90 s). Edit the action set timeout on the distribution; the action will be killed and the target marked failed if it exceeds.
See also
Multi-certificate hosts
Two mechanisms let one SSH target serve many certificates:
- Per-distribution PathSet/ActionSet — each certificate's distribution can select its own PathSet (where files land) and ActionSet (what runs afterwards) from the certificate's Distributions → Overrides drawer (
ssh { path_set_id, action_set_id }, with a*default row). A selected override must resolve — a deleted or wrong-kind resource fails the run instead of silently using the target's own libraries. - Certificate-context variables in paths — PathSet paths may contain
${{ CAP_PRIMARY_DOMAIN }},${{ CAP_DOMAIN_SLUG }},${{ CAP_CERT_TOKEN }}or${{ CAP_DEPLOY_NAME }}, rendered per certificate at distribution time:/etc/ssl/${{ CAP_DOMAIN_SLUG }}/fullchain.pem. PreferCAP_DOMAIN_SLUGoverCAP_PRIMARY_DOMAINin paths: on a wildcard certificate the primary domain is*.example.comand the literal*lands in the directory name, while the slug renders asexample-com. Project variables also render; secret variables are rejected in paths; a missing variable fails that target. Note that substitution always applies: a literal${{ ... }}in a path or command must be escaped with a backslash (\${{ ... }}). Rollback restores the rendered paths recorded at deploy time.
When several certificates share a host, put ${{ CAP_CERT_TOKEN }} or ${{ CAP_DEPLOY_NAME }} in the path (or use distinct PathSets) so files never collide.