← Back to Wiki
Networking / Automation

Automating UniFi's Certificate Manager Without Ever Touching a Password

Every device on a LAN behind an internal certificate authority can get real, browser-trusted HTTPS — except the UniFi console itself, whose certificate manager is a browser-only click-through with no documented API. Rather than store a real UniFi account credential in a cron job (or steal a live session cookie), reverse-engineering the console's own UI found a much better path.

Share on X

Step one: get real SSH access, without any key-upload UI

UniFi OS's console settings expose an SSH toggle and a password-only "Change Password" control — there's no key-upload option at the local console level (a per-admin "SSH Keys" feature some UniFi documentation mentions belongs to the cloud identity system, not this local console UI). The path to real, durable key-based access: set a one-time password through that control, log in exactly once with it, and use that single session to append a real public key to ~/.ssh/authorized_keys directly. Password auth is never needed again after that — every future automated session uses the key.

Step two: find the real API by intercepting the UI's own requests

The certificate manager's actual request shapes aren't published anywhere — they were recovered by monkey-patching window.fetch in the console's own page context immediately before triggering each UI action for real, then reading back exactly what got sent. That surfaced the real, if undocumented, routes:

ActionRequest
List certsGET /api/userCertificates
Upload newPOST /api/userCertificates — JSON body {"name", "cert", "key"}, not multipart; the cert field is the leaf + intermediate chain concatenated into one PEM string
ActivatePUT /api/userCertificates/{id}/status{"active": true}
DeleteDELETE /api/userCertificates/{id}

The real find: the same routes exist, unauthenticated, on localhost

UniFi's backend process listens on an internal localhost port and answers these exact same routes with no session or CSRF token required at all — confirmed directly from the console's own shell: a plain curl against that local port returns the real certificate list with zero authentication. The public HTTPS port is where session auth actually gets enforced (via the reverse proxy in front); the backend process itself trusts anything arriving from localhost unconditionally. This is presumably the exact mechanism UniFi's own built-in Let's Encrypt auto-renewal uses internally.

This is what makes real automation possible without ever handling a UniFi account password or a stolen session cookie in a script: SSH in as a key-authenticated user (from step one), then curl against the local port directly. No credentials to rotate, no session to keep alive, no browser automation required at all for the actual renewal step.

A real gotcha building the renewal automation

The upload endpoint rejects a second certificate with the same "name" field as an existing entry — even if that existing entry is currently inactive — with a "Certificate with such name or fingerprint already exists" error. This breaks a naive "upload new → activate → delete old" sequence if the new upload reuses the domain name as its label. Fix: suffix the upload's name field with a timestamp. This field is purely a UI label with no bearing on the certificate's actual content or hostname matching, so the suffix is cosmetic — it just needs to be unique.

Worth keeping the safer operation order despite the extra step: create the new cert first, verify it's actually active, then delete the old one — rather than deleting first. That way a failure partway through never leaves the console with zero valid certificates installed, just a leftover old one alongside the new.

The result

A daily cron job checks the currently-active certificate's real expiry date via the local API, and once within a threshold of expiring, mints a fresh certificate from the internal CA, uploads it, activates it, and removes the previous one — the exact same renewal shape used for every other internally-certificated service, just targeting a completely different, undocumented install mechanism under the hood. Verified end-to-end for real (an actual renewal run, not just the skip-path when nothing needs renewing) before trusting it unattended.