π Recent release history (last 2 versions)
[2.214] - 2026-08-25
Security
- Certificates: a valid, non-revoked X.509 certificate can no longer be deleted β deletion removed the record while the certificate stayed trusted until expiry, with no CRL/OCSP trace; revoke it first so relying parties see the change (single delete returns 409, bulk delete reports the item as failed). Expired or revoked certificates, and CSR-only records, are unaffected (#296, contributed by @gb-123-git & PMGA Tech LLP)
- Revocations now survive certificate deletion β a new persistent
revoked_serialsstore keeps every revoked or superseded serial on the CRL and answeringrevokedover OCSP until the original certificate's notAfter, even after the certificate record itself is deleted. A revocation and its persistent record are written in one transaction, and removing a certificate hold deletes both atomically, so relying parties can never observe a half-applied state (migration 078) (#297, contributed by @gb-123-git & PMGA Tech LLP) - Renewing a revoked certificate is refused (409) instead of silently clearing its revocation state β issue a new certificate instead (#297, contributed by @gb-123-git & PMGA Tech LLP)
- Renewed private keys are encrypted at rest exactly as at issuance β bulk renewal previously stored them unencrypted, silently downgrading deployments with a master key configured (#297, contributed by @gb-123-git & PMGA Tech LLP)
Changed
- Certificate renewal is now in-place β manual, bulk and automatic renewal share a single implementation that updates the existing certificate record:
idandrefidnever change (API consumers and integrations keep their stored references),created_atpreserves the original issuance date, and newrenewed_at/renewed_timesfields track the renewal history. The superseded serial is published on the CRL (reasonsuperseded) and over OCSP until the old expiry. Certificates whose key UCM holds are re-keyed on renewal; protocol-enrolled certificates (SCEP/EST/ACME) are re-signed with their existing public key, since the private key lives on the client (#297, contributed by @gb-123-git & PMGA Tech LLP)
Added
- CA signed by an external CA (CSR) β a new CA creation mode for the canonical offline-root / online-issuing-CA pattern: UCM generates the key pair (local or HSM-backed) and a CA-flavored CSR (
basicConstraints CA:TRUEandkeyCertSign/cRLSign, both critical), and the CA waits in an "awaiting certificate" state with the CSR downloadable β the private key never leaves UCM. Uploading the certificate signed by the external root (PEM or DER) activates the CA after strict checks: the certificate's public key must match the stored private key, CA BasicConstraints/KeyUsage are enforced, the effective pathLen comes from the signed certificate, and the chain links automatically when the external root was imported (certificate only; AKIβSKI match with issuer-DN fallback, signature verified). Renewal re-issues a CSR from the same key so the SKI stays stable across renewals, and a pending CA is excluded from every signing, CRL, OCSP and protocol path until activated (migration 079). Smoke-tested end-to-end on SQLite and PostgreSQL. Requested in #298 - Optional auto-purge of stale revoked-serial records (expired ones only) during CRL generation β default off, so records are kept as renewal-chain history unless an admin enables it (#297, contributed by @gb-123-git & PMGA Tech LLP)
Fixed
- Auto-renewal notification settings are now honored β the notify toggles were never read back (notifications always sent) and boolean values did not survive a settings round-trip; malformed JSON in the auto-renewal configuration no longer crashes the scheduler (#297, contributed by @gb-123-git & PMGA Tech LLP)
[2.213] - 2026-08-19
β οΈ Upgrade note β in-flight ACME proxy orders placed with a bare JWK must be
re-created. The proxy'snew-ordernow requires a kid (RFC 8555 Β§6.2) and binds
every order to an RFC 7638 thumbprint. A pending proxy order created before this
release through a jwk-signed new-order carries only the legacy thumbprint of the
raw JWK dict, which no longer equals the value derived from the requesting key β and
the account-row reconciliation cannot resolve it either, because that legacy hash
never equals a stored account thumbprint. Those orders become inaccessible and must
be re-created. ACME automation self-heals (the client simply places a new order on
its next run), and already-issued certificates are unaffected β but a client that
polls a saved order URL forever will need a nudge.
Security
- ACME proxy
new-orderrequires a kid β a jwk-signed JWS is verified against its own inline key with no account lookup, so accepting one skipped the account existence/status check and the per-EAB identifier restrictions whenever EAB was not mandatory, and left the resulting order with no owner recorded β which the ownership check then served to every other client of that proxy. Identifier and challenge-type validation still runs first, so a client with an unsupported identifier keeps gettingunsupportedIdentifierrather than an authentication error (#260) - Deactivated and revoked accounts are refused on
new-orderβ the status check only covereddeactivated, so a revoked account could keep ordering certificates - Proxy resources require a positive owner match β authz, challenge, order and certificate access previously allowed any verified requester when an order's owner binding was half-populated (an account id without a stored thumbprint, or a thumbprint that resolved to no account): the absence of a contradiction was treated as authorization. A match is now required on one of the two fields, and a half-populated binding is reconciled through the owning
AcmeAccountrow rather than waved through (#260) - Owner thumbprints follow RFC 7638 β the thumbprint was computed over the raw JWK dict, so a client sending optional members (
alg,kid,use) turned a legitimate owner into a mismatch; only the required members are hashed now, by a single implementation shared with the value stored on the account - Upstream authorizations are matched exactly β the candidate lookup is a
LIKE '%url%'prefilter, which also matches a prefix of a stored URL (.../authz-v3/99inside.../authz-v3/999); a client could bind a foreign authorization to an order it does own and pass the ownership check with it. Prefilter hits are now re-checked against the decoded URL list - Challenge-to-order resolution has no approximate fallback β the owning order is resolved from the recorded authz mapping, from the challenge's parent authz path, or from the upstream
Link rel="up"header only. There is no host-only match and no "most recent pending order" guess, either of which could run DNS-01 automation against a foreign domain;rel="up"is also selected explicitly rather than taking the first link, sincerel="index"is commonly listed first - The certificate-download fallback scan is owner-scoped β resolving an untracked certificate URL scanned every pending proxy order and signed one upstream POST per candidate, letting any caller drive the proxy's upstream account (cross-tenant probing, rate-limit burn). The scan is now restricted to rows the requester could own and capped
- Ownership is verified before any upstream call β a denial no longer costs a signed request to the CA
- A challenge whose identifier cannot be determined fails closed β on a multi-SAN order the first domain is not necessarily the one being validated; publishing the DNS-01 TXT record under that name was wrong. The request now fails with a problem document naming the cause
- SSH certificates: a valid, non-revoked certificate can no longer be deleted β deletion silently dropped the record while the certificate stayed trusted by servers until expiry; revoke it first (the API returns 409 otherwise) (#292, contributed by @gb-123-git)
Fixed
- Azure Key Vault HSM provider: key generation passed an invalid
hsmargument tocreate_rsa_key/create_ec_keyand failed with aTypeErrorβ the SDK keyword ishardware_protected(#295, contributed by @jeancarlor) - Discovery: large scheduled scans no longer exhaust memory and crash-loop the service β probes are submitted through a bounded window with connection-level failures discarded immediately, a scan is capped at 65,536 hostΓport probes (rejected with a clear error beyond), a profile's next-run timestamp advances when a scan starts (not when it completes), scans orphaned by a restart are marked failed at startup, and reverse-DNS lookups are now lazy (#293)
- Bulk Actions: pagination, search and status/CA filters for certificates and CSRs now run server-side against the complete inventory β the page-size selector is respected, selections survive page changes, resource count chips show real totals, and the CSR list endpoint gained server-side search (#294)
- Background DNS-01 threads no longer share ORM state with the request that started them β the worker rebound the request's account object to its own session, racing the request thread and the sibling threads a multi-domain order starts; each worker now signs through its own service instance
Changed
- Proxied ACME requests reuse the upstream directory, finalize URL and challenge mapping, and pool
Replay-Noncevalues β every proxied call previously paid an extraGET /directoryplusHEAD /new-nonceupstream. All caches are advisory: a miss falls back to the upstream round-trip, so a restart or a second worker costs performance, never correctness
Full history: CHANGELOG.md
Installation
Docker (Recommended)
# From Docker Hub
docker pull neyslim/ultimate-ca-manager:2.215-rc1
# Or from GitHub Container Registry
docker pull ghcr.io/neyslim/ultimate-ca-manager:2.215-rc1
# Run
docker run -d -p 8443:8443 \
-e SECRET_KEY=$(openssl rand -hex 32) \
--name ucm neyslim/ultimate-ca-manager:2.215-rc1Debian/Ubuntu
wget https://github.com/NeySlim/ultimate-ca-manager/releases/download/v2.215-rc1/ucm_2.215.rc1_all.deb
sudo dpkg -i ucm_2.215.rc1_all.deb
sudo apt-get install -fFedora/RHEL
wget https://github.com/NeySlim/ultimate-ca-manager/releases/download/v2.215-rc1/ucm-2.215.rc1-1.fc43.noarch.rpm
sudo dnf install ./ucm-2.215.rc1-1.fc43.noarch.rpmSilent/Automated Install
# Skip firewall prompts for CI/automation
sudo UCM_PORT=8443 UCM_FIREWALL=no dpkg -i ucm_2.215.rc1_all.debDefault Credentials
- Username:
admin - Password:
changeme123
Change the password immediately after first login!
Documentation
- Installation Guide
- API Documentation