SSH Certificate Authority
Watchgrid includes a built-in SSH Certificate Authority (CA) that issues short-lived certificates, so you don't have to maintain authorized_keys files on every device.
How It Works
Instead of copying SSH public keys to every device, Watchgrid signs your SSH key with a trusted CA certificate. Every provisioned device already trusts this CA, so your signed certificate grants access automatically.
| Certificate Type | Validity | Use Case |
|---|---|---|
| User certificates | 24 hours | SSH into devices |
| Host certificates | 365 days | Devices prove their identity |
Security Features
- Short-lived certificates — user certs expire after 24 hours, limiting exposure
- Source address restriction — user certs are locked to the WireGuard subnet (
100.64.0.0/10) - Certificate revocation — revoked certs are tracked in a CRL that devices check on each heartbeat
- Full audit trail — every certificate issuance is logged
Requesting a User Certificate
- Go to System → PKI / SSH CA
- In the Request User Certificate section:
- Paste your SSH public key (e.g., contents of
~/.ssh/id_ed25519.pub) - Optionally enter a username
- Click Request Certificate
- Click Download to save the signed certificate file (
*-cert.pub) - Place the certificate alongside your private key:
# If your key is ~/.ssh/id_ed25519, save the cert as:
~/.ssh/id_ed25519-cert.pub
# Then SSH normally — the cert is used automatically
ssh admin@pi-sensor-1.wg
The certificate is valid for 24 hours. After that, request a new one.
CA Status
The PKI page displays:
- CA operational status — whether the CA keys are loaded and functional
- Total certificates issued
- Active / Expired / Revoked counts
- User vs. Host certificate breakdown
Certificate Audit
A table at the bottom lists all issued certificates:
| Column | Description |
|---|---|
| Type | User or Host |
| Username | The principal on the certificate |
| Serial | Unique certificate serial number |
| Issued | When the certificate was signed |
| Expiry | When the certificate expires |
| Status | Active (green), Expired (gray), or Revoked (red) |
Host Certificates
Host certificates are issued automatically during device provisioning. They prove the device's identity to connecting SSH clients, preventing man-in-the-middle attacks.
To trust host certificates on your workstation, add this to ~/.ssh/known_hosts:
The CA host public key is available at:
CA Key Storage
CA keys are stored in a Docker volume (watchgrid-ssh-ca) mapped to /etc/watchgrid/:
ca_user_key/ca_user_key.pub— signs user certificatesca_host_key/ca_host_key.pub— signs host certificates
Back up your CA keys
Losing the CA private keys invalidates all issued host certificates. Every device would need to be reprovisioned. Back up the watchgrid-ssh-ca volume in production.
Backup & Restore
Why this matters
Losing the SSH CA private keys invalidates every host certificate in the fleet. Devices will fail StrictHostKeyChecking and need to be fully reprovisioned. Back up the CA keys before any production incident occurs.
Automated daily backup
scripts/backup-ssh-ca.sh creates an AES-256-CBC encrypted tarball of all four CA key files and copies it to a configurable destination. It retains the last 14 backups by default.
Installation
# Copy the script to the server
sudo cp scripts/backup-ssh-ca.sh /usr/local/bin/watchgrid-ca-backup.sh
sudo chmod 0750 /usr/local/bin/watchgrid-ca-backup.sh
# Create an env file with your passphrase (protect it!)
sudo mkdir -p /etc/watchgrid
sudo bash -c 'cat > /etc/watchgrid/backup.env' <<'ENV'
WG_BACKUP_PASSPHRASE=<strong-random-passphrase-store-separately>
WG_BACKUP_DEST=/var/backups/watchgrid-ssh-ca
WG_BACKUP_KEEP=14
ENV
sudo chmod 0600 /etc/watchgrid/backup.env
# Install and start the systemd timer
sudo cp scripts/systemd/watchgrid-ca-backup.{service,timer} /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now watchgrid-ca-backup.timer
# Verify the timer is active
systemctl list-timers watchgrid-ca-backup.timer
Manual backup
WG_BACKUP_PASSPHRASE=<passphrase> \
WG_BACKUP_DEST=/var/backups/watchgrid-ssh-ca \
/usr/local/bin/watchgrid-ca-backup.sh
Remote backup (rsync)
WG_BACKUP_PASSPHRASE=<passphrase> \
WG_BACKUP_DEST=backup@offsite-host:/backups/watchgrid-ssh-ca \
/usr/local/bin/watchgrid-ca-backup.sh
Ensure the server has SSH key-based access to the remote host.
Restore procedure
RTO target: < 15 minutes for key restore; device reprovisioning is not required if keys are restored before any device reconnects.
Step 1 — Locate the latest encrypted backup
Step 2 — Decrypt and extract
BACKUP_FILE=/var/backups/watchgrid-ssh-ca/watchgrid-ssh-ca-2026-04-22T03-00-00.tar.gz.enc
RESTORE_DIR=/tmp/ca-restore
mkdir -p "$RESTORE_DIR"
openssl enc -d -aes-256-cbc -pbkdf2 -iter 600000 \
-pass "pass:<passphrase>" \
-in "$BACKUP_FILE" | tar -xz -C "$RESTORE_DIR"
ls "$RESTORE_DIR/ca-keys/"
# → ca_user_key ca_user_key.pub ca_host_key ca_host_key.pub
Step 3 — Restore into the running container
CONTAINER=watchgrid-server # adjust to your container name
docker cp "$RESTORE_DIR/ca-keys/ca_user_key" "${CONTAINER}:/etc/watchgrid/"
docker cp "$RESTORE_DIR/ca-keys/ca_user_key.pub" "${CONTAINER}:/etc/watchgrid/"
docker cp "$RESTORE_DIR/ca-keys/ca_host_key" "${CONTAINER}:/etc/watchgrid/"
docker cp "$RESTORE_DIR/ca-keys/ca_host_key.pub" "${CONTAINER}:/etc/watchgrid/"
# Fix ownership inside container
docker exec "$CONTAINER" chown root:root /etc/watchgrid/ca_*
docker exec "$CONTAINER" chmod 600 /etc/watchgrid/ca_user_key /etc/watchgrid/ca_host_key
docker exec "$CONTAINER" chmod 644 /etc/watchgrid/ca_user_key.pub /etc/watchgrid/ca_host_key.pub
Step 4 — Verify
# Confirm keys are present and readable
docker exec "$CONTAINER" ls -la /etc/watchgrid/ca_*
# Test issuing a certificate (use any valid username)
curl -s -H "Authorization: Bearer <admin-jwt>" \
https://YOUR_SERVER/api/ssh/sign-cert \
-d '{"username":"test-verify"}' | jq .
Step 5 — Clean up
Volume restore (full stack rebuild)
If the Docker volume itself is lost (e.g., disk failure), restore using the backup before starting the server:
# Decrypt the tarball as above, then copy into the Docker volume:
docker volume create watchgrid-ssh-ca
docker run --rm \
-v watchgrid-ssh-ca:/etc/watchgrid \
-v "$RESTORE_DIR/ca-keys":/src:ro \
alpine sh -c "cp /src/ca_* /etc/watchgrid/ && chmod 600 /etc/watchgrid/ca_user_key /etc/watchgrid/ca_host_key && chmod 644 /etc/watchgrid/ca_user_key.pub /etc/watchgrid/ca_host_key.pub"
# Start the stack — the server will pick up the restored keys on boot.
docker compose -f docker-compose.prod.yml up -d
Backup verification checklist
Run monthly to confirm backups are usable:
- [ ] Latest backup exists and is within 25 hours old (
ls -lt /var/backups/watchgrid-ssh-ca/ | head -2) - [ ] Decrypt succeeds without errors (Step 2 above)
- [ ] Extracted files include all four key files
- [ ] Passphrase stored separately from backup files (secrets manager, password vault, or offline)
- [ ] Off-host copy transferred if using local-only backup