Skip to content

Deploy Hooks

NeySlim edited this page Oct 19, 2026 · 8 revisions

Deploy Hooks

Push issued and renewed certificates, and updated CRLs (v2.233+), to remote hosts over SSH, then reload the consuming service — for systems that cannot enroll on their own (IoT devices, appliances, firewalls) or when one certificate (e.g. a wildcard) fans out to several frontends. Available since v2.215, admin-only.


Overview

A deploy target is a reusable SSH/SFTP connection to a remote host. Certificates and CRLs are bound to targets with fixed destination paths; on every issuance, renewal or CRL update (and on demand), UCM:

  1. Pushes the chosen files over SFTP — certificate, private key and/or full chain, or the CRL, each to its configured absolute path
  2. Runs the binding's fixed reload command over SSH (e.g. systemctl reload nginx)

Since v2.233 the target holds only the connection: destination paths, file options and the reload command belong to each binding, so one target can serve several services with different reloads. Migration 091 copied the reload command of existing targets to their bindings.

Pushes go through a durable delivery queue (same model as webhooks): the issuing operation is never blocked, deliveries survive restarts, retry with exponential backoff (5 attempts), and leave a per-delivery history plus a full audit trail.

Security posture: deploy targets make UCM hold credentials that can execute a command on your fleet, on top of the CA keys it already protects. The feature is admin-only, every push and failure is audited, and each target should use a dedicated low-privilege SSH account — write access to the certificate paths and permission to reload the service, nothing more.


Creating a deploy target

Settings → Deployment → Add target (admin only):

Field Notes
Name Unique label
Host / Port Hostname or IP, SSH port (default 22)
SSH user The dedicated account on the target
SSH private key Optional: leave empty and UCM generates an ed25519 keypair (ECDSA P-256 in FIPS mode)
  • The private key is stored encrypted at rest like every other secret in UCM and is never returned by the API.
  • After creation, UCM shows the public key: append it to the SSH account's authorized_keys on the target.
  • Test connection verifies SSH + SFTP without writing anything.

Host key pinning (trust-on-first-use)

The target's host key is recorded on the first successful connection and required to match on every later one — a changed host key fails closed (no push, no command). If the host was legitimately reinstalled, edit the target and reset the pinned host key (changing the host or port also re-pins automatically). The pinned key's SHA256 fingerprint is shown on the target so you can compare it with ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub on the host.

In FIPS mode, deployments accept only ECDSA or RSA host keys: compare with ssh_host_ecdsa_key.pub or ssh_host_rsa_key.pub instead, and reset the pin of a target pinned to an Ed25519 host key.


Binding certificates

From the certificate detail view → Deployment → Attach target: pick a target and set the destination paths —

File Content Mode
Certificate path Leaf certificate (PEM) 0644
Private key path Private key (PEM) — only for certificates whose key UCM holds 0600
Full chain path Leaf + intermediate CA certificates (PEM); the self-signed root only when Include Root CA is ticked 0644
Reload command Optional. One fixed command, run after a successful push; exit 0 = success. No templating, no uploaded scripts
  • Include Root CA (shown once a full chain path is entered) appends the trust anchor to the full chain file. TLS servers normally send only the leaf and the intermediates, so it is off for new bindings; bindings created before 2.232 keep the root, as they always did, until you detach and re-attach them without it.
  • Paths are absolute file paths on the target, per binding — the same certificate can land in different locations on different hosts. At least one path is required, and the three paths must be distinct files.
  • Files are written atomically (temp file + rename), so a reader never sees a half-written certificate.
  • The parent directory must already exist and be writable by the SSH account — UCM does not create directories.
  • The dialog names the certificate being attached; a binding can be edited (paths, reload command) and enabled or disabled from its edit dialog.
  • Attaching a target queues the first deployment immediately (drained by the scheduler within ~30 s, with retries); later renewals redeploy automatically (certificate.renewed). Deploy now pushes on the spot and reports the result inline. Failed deliveries can be retried from the API.

Protocol-enrolled certificates (SCEP/EST/ACME proxy) whose private key lives on the client can still be deployed — bind the certificate and/or full chain paths and leave the key path empty.

Pending retries show their last error and a live countdown to the next attempt, and the section refreshes on its own while waiting.


Deploying CRLs (v2.233+)

From CRL & OCSP → select a CA → CRL Deployment → Attach target: pick a target and set

Field Notes
CRL destination path Absolute path on the target
Format PEM or DER. DER is a single CRL
Include parent CRLs PEM only: the CA's CRL followed by the CRLs of its issuers, as one bundle
Reload command Optional, as for certificate bindings

Every time the CA publishes a new complete CRL, it is pushed through the same durable queue (retries, history, audit). Deploy now pushes on demand.


Setting up the target host

# 1. Dedicated low-privilege account
useradd --system --create-home ucm-deploy

# 2. Install UCM's public key (shown on the deploy target)
install -d -m 700 ~ucm-deploy/.ssh
echo 'ssh-ed25519 AAAA... ucm-deploy' >> ~ucm-deploy/.ssh/authorized_keys

# 3. Let it write the certificate paths
install -d -o ucm-deploy /etc/ssl/app

# 4. Allow exactly the reload it needs (sudoers drop-in)
echo 'ucm-deploy ALL=(root) NOPASSWD: /usr/bin/systemctl reload nginx' \
  > /etc/sudoers.d/ucm-deploy

Reload command on the binding: sudo systemctl reload nginx.

Deploying on the UCM host itself

The UCM service is sandboxed with ProtectSystem=strict, NoNewPrivileges=true, and write access limited to its own data and configuration directories. It therefore cannot write directly to locations such as /etc/nginx, change file ownership, or run a privileged reload. Use the existing SFTP transport through the host's SSH server instead:

  1. Create the dedicated ucm-deploy account shown above on the UCM host.
  2. Install the deploy target's public key in that account's authorized_keys.
  3. Create the destination directory with ucm-deploy as owner and add the narrowly scoped sudoers reload rule.
  4. In Settings → Deployment, add a target with Host 127.0.0.1 and SSH user ucm-deploy. The This host preset fills these values.

Bind the certificate and key to their consumer paths as usual, with Reload command sudo systemctl reload nginx on the binding. SFTP keeps the writes atomic, the SSH account supplies the file owner, and the reload runs outside the UCM service sandbox.

For Docker deployments, point the target at the host or at the consuming container if it runs an SSH server. A container cannot reload a service on its host directly.


API

All endpoints require the admin-only deploy resource — read:deploy for reads, write:deploy for create/update/test/deploy/retry, delete:deploy for deletions. See API Reference for details.

GET/POST            /api/v2/deploy/targets
GET/PATCH/DELETE    /api/v2/deploy/targets/<id>
POST                /api/v2/deploy/targets/<id>/test
GET/POST            /api/v2/deploy/bindings          ?certificate_id= | ?target_id=
PATCH/DELETE        /api/v2/deploy/bindings/<id>
POST                /api/v2/deploy/bindings/<id>/deploy
GET/POST            /api/v2/deploy/crl-bindings
PATCH/DELETE        /api/v2/deploy/crl-bindings/<id>
POST                /api/v2/deploy/crl-bindings/<id>/deploy
GET                 /api/v2/deploy/deliveries        ?binding_id= | ?certificate_id=
POST                /api/v2/deploy/deliveries/<id>/retry

Troubleshooting

Symptom Cause / fix
SSH authentication failed Public key not in the account's authorized_keys, wrong user, or authorized_keys/.ssh permissions too open on the target
Host key verification failed The target presents a different host key than the pinned one — verify the host, then reset the pin on the target if the change is legitimate
SFTP write to ... failed Parent directory missing or not writable by the SSH account
Reload command exited N The command failed on the target — stderr is captured in the delivery detail; test it manually as the deploy user
Delivery stuck pending Retried with backoff (up to 1 h between attempts, 5 attempts) — check the last error on the delivery, fix the cause, or use retry after it fails
Binding refuses a key path UCM does not hold this certificate's private key (protocol-enrolled) — deploy certificate/full chain only

Deliveries and their errors are visible on the certificate's Deployment section and via GET /api/v2/deploy/deliveries; every push, failure and configuration change is in the audit log.


Related Pages

Clone this wiki locally