---
title: Certificate Agent
path: sdks/certificate-agent
status: published
---

# Certificate Agent

A small daemon-less program that keeps TLS certificates current on a host: renews them before they expire, writes the bundle where your service expects it, and reloads that service — only when something actually changed.

It closes the last gap in certificate automation. ScaiVault already renews certificates on a schedule, but until now getting the renewed material *onto* the machine that serves it was manual.

## Install

```bash
sudo ./install.sh scaivault_agent-1.0.0-py3-none-any.whl
```

Get the wheel from <https://www.scailabs.ai/downloads> under ScaiVault.

It depends on `cryptography` and `httpx` and nothing else — it does **not** require the ScaiVault server, so installing it on a web server does not put a secrets manager's database drivers in your DMZ. Python 3.11+, Linux/systemd.

## Enrol the host

Rather than copying a long-lived API key onto every machine, mint a short-lived token and let the host trade it for its own credential:

```bash
# on an admin machine — single-use, expires in an hour
curl -sS -X POST https://scaivault.scailabs.ai/api/v1/enrollment/tokens \
  -H "X-API-Key: $ADMIN_KEY" -H 'Content-Type: application/json' \
  -d '{"roles":["pki-agent"],"ttl_seconds":3600,"fqdn_pattern":"web*.example.com"}'

# on the host
scaivault-agent enroll --token enroll_...
```

The host receives its **own** service account, named after its hostname, so it can be revoked individually. See [Enrollment](../reference/enrollment) for the full API.

A long-lived key still works if you prefer: write it to `/etc/scaivault/agent.key` with mode `0600`.

## Add a certificate

```bash
scaivault-agent add www.example.com --service nginx
```

This infers the four things you would otherwise have to look up — the certificate path, the CA to sign against, the output directory, and the correct reload command — then prints the config to paste:

```
[[certificate]]
path        = "hosts/web01.example.com/www.example.com"
mode        = "csr"
ca_id       = "ca_intermediate"
common_name = "www.example.com"
out_dir     = "/etc/ssl/scaivault/www.example.com"
reload      = "nginx -t && systemctl reload nginx"

Add to your nginx config:

    ssl_certificate     /etc/ssl/scaivault/www.example.com/fullchain.pem;
    ssl_certificate_key /etc/ssl/scaivault/www.example.com/privkey.pem;
```

Then:

```bash
scaivault-agent --dry-run     # says what it would do, changes nothing
scaivault-agent               # issue and deploy
sudo systemctl enable --now scaivault-agent.timer
```

`scaivault-agent services` lists the services it knows and marks the ones installed on this host.

## Key modes

Set per certificate, because not every certificate type can use a locally generated key.

| Mode | Key handling | Scope needed |
|---|---|---|
| `csr` (default) | Generated on the host; only a CSR is sent. The private key never crosses the network. | `pki:write` |
| `managed` | Key lives in ScaiVault and is downloaded. Needed where the key is not the agent's to generate — ACME above all. | `pki:export-key` |

Prefer `csr`. A stolen agent credential can then request certificates but cannot exfiltrate existing private keys. Scope `managed` service accounts to only the paths that host serves.

The agent will not silently fall back from `csr` to `managed`: a CSR that will not sign is an error to report, not to work around.

## Supported services

Each profile knows how to reload safely and which files the service wants.

| Service | Files it uses | Reload |
|---|---|---|
| nginx | `fullchain.pem` + `privkey.pem` | `nginx -t && systemctl reload nginx` |
| Apache | `certificate.pem` + `chain.pem` + `privkey.pem` | `apachectl configtest && systemctl reload apache2` |
| HAProxy | combined `haproxy.pem` (built by the hook) | `haproxy -c -f … && systemctl reload haproxy` |
| Postfix | `fullchain.pem` + `privkey.pem` (group-readable) | `postfix check && systemctl reload postfix` |
| Dovecot | `fullchain.pem` + `privkey.pem` (group-readable) | `doveconf -n && systemctl reload dovecot` |

Every reload validates the configuration first. That turns "the reload broke the site" into "the reload refused, the old certificate is still serving".

Postfix and Dovecot run unprivileged, so those profiles set a group and a `0640` key mode automatically — a root-only `0600` key would silently break them.

## Files written

| File | Contents |
|---|---|
| `certificate.pem` | leaf only |
| `chain.pem` | issuer chain |
| `fullchain.pem` | leaf + chain |
| `privkey.pem` | private key, `0600` unless the service needs otherwise |

Writes are atomic: each file goes to a temporary file in the same directory with ownership and mode already applied, then `rename()`. A service reloading concurrently never reads a half-written PEM.

## Scheduling

The agent is a one-shot process, not a daemon. The bundled systemd timer runs it twice daily with up to an hour of jitter so a fleet does not stampede the API. A run with nothing due makes no API calls at all.

## Exit codes

| Code | Meaning |
|---|---|
| `0` | everything current, or renewed and reloaded cleanly |
| `1` | configuration error |
| `2` | a certificate failed to renew, or a reload hook failed |
| `3` | ScaiVault unreachable |

`2` and `3` are distinct on purpose: "one host's reload hook is broken" is a different alert from "the CA is down".

## Fleet visibility

After each run the agent reports what it manages, so you can see which hosts are about to break without ssh-ing into each one. See [Fleet](../reference/fleet).

Set `report_status = false` under `[agent]` for hosts that must not phone home. A check-in failure never affects renewal — by that point the certificates are already deployed.

## Other platforms

Linux/systemd today. The wire contract — config schema, state file, exit codes, reload semantics — is specified language-agnostically in `PROTOCOL.md` inside the distribution, so a Go or C# build for Windows/IIS is a port rather than a redesign.

## What's next

- [Enrollment](../reference/enrollment) — minting and redeeming tokens.
- [Fleet](../reference/fleet) — which hosts are about to break.
- [PKI](../api-guides/pki-certificates) — the certificate API the agent uses.
