Repository navigation
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.
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:
- Pushes the chosen files over SFTP — certificate, private key and/or full chain, or the CRL, each to its configured absolute path
- 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.
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_keyson the target. - Test connection verifies SSH + SFTP without writing anything.
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.
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.
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.
# 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-deployReload command on the binding: sudo systemctl reload nginx.
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:
- Create the dedicated
ucm-deployaccount shown above on the UCM host. - Install the deploy target's public key in that account's
authorized_keys. - Create the destination directory with
ucm-deployas owner and add the narrowly scoped sudoers reload rule. - In Settings → Deployment, add a target with Host
127.0.0.1and SSH userucm-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.
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
| 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.
Getting started
Certificates and CAs
- CA Management
- CA Offline Mode
- Certificate Operations
- Certificate Templates
- Certificate Export
- Import and Export
- Import from OPNsense
- Certificate Toolbox
- Certificate Discovery
- Trust Store
- Approval Workflows
- Key Recovery
- Code Signing
- Deploy Hooks
- HSM Support
- SSH Certificate Authority
Protocols
- ACME
- SCEP
- EST
- OCSP Responder
- CRL and CDP
- AIA CA Issuers
- TSA (Timestamp Authority)
- Certificate Transparency
Integrations
Security and access
- Security
- FIPS Mode
- Compliance Profiles
- SSO
- mTLS Authentication
- WebAuthn / FIDO2
- User Management
- User Groups
- Audit Logs
Operations
- System Configuration
- Database Backend
- Backup and Restore
- Monitoring
- Notifications
- Reports
- Dashboard
- Themes
- User Settings
Development
- Building from Source
- Contributing
- Developer Patterns
- Architecture
- API Reference
- API Documentation
- Roadmap
Releases