What's Changed
⚠️ 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
📜 Recent release history (last 2 versions)
[2.212] - 2026-08-17
Added
- API keys: proper revoke/delete lifecycle — revoke an active key, permanently delete a revoked or expired one; expired keys no longer count against the per-user limit; "Last used" now shown in the account page (#291, contributed by @Hemsby)
- ACME local domains: bare private TLDs (e.g.
local,internal) can now be registered, covering all their subdomains through the existing parent matching (#290, contributed by @gb-123-git)
Fixed
- ACME server: http-01 validation now follows HTTP redirects (RFC 8555 §8.3) — a site-wide http→https 301 on the challenge path no longer fails with "Key authorization mismatch". Up to 5 hops, http/https on default ports only, TLS not verified on https hops (the target usually serves the certificate being renewed), and every hop re-vetted by the SSRF policy (cloud metadata always refused; targets resolved, vetted and pinned when private IPs are disallowed)
- Deleting a certificate (single or bulk) or a CA now removes its cert/key/csr files on disk instead of leaving them orphaned (#289, contributed by @Hemsby)
app.config['DATA_DIR']is now set, so the session cleanup task no longer silently falls back to the default data directory on custom layouts (#290, contributed by @gb-123-git)
[2.211] - 2026-08-15
Added
- Certificates: mutable display name independent from the CN — rename from the certificates list, defaults to CN or first SAN DNS name for CN-less certificates (CA/B Forum profiles, Let's Encrypt tlsserver/shortlived), shown in lists and selectors with the key type to distinguish otherwise identical certificates (#286)
Fixed
- Packaging: DEB/RPM upgrades no longer silently drop manually installed Kerberos extras (
requests-kerberos,pyspnego[kerberos]) when rebuilding the virtualenv — they are detected before the rebuild and reinstalled after (#287, contributed by @Hemsby)
Full history: CHANGELOG.md
Installation
Docker (Recommended)
# From Docker Hub
docker pull neyslim/ultimate-ca-manager:2.213
# Or from GitHub Container Registry
docker pull ghcr.io/neyslim/ultimate-ca-manager:2.213
# Run
docker run -d -p 8443:8443 \
-e SECRET_KEY=$(openssl rand -hex 32) \
--name ucm neyslim/ultimate-ca-manager:2.213Debian/Ubuntu
wget https://github.com/NeySlim/ultimate-ca-manager/releases/download/v2.213/ucm_2.213_all.deb
sudo dpkg -i ucm_2.213_all.deb
sudo apt-get install -fFedora/RHEL
wget https://github.com/NeySlim/ultimate-ca-manager/releases/download/v2.213/ucm-2.213-1.fc43.noarch.rpm
sudo dnf install ./ucm-2.213-1.fc43.noarch.rpmSilent/Automated Install
# Skip firewall prompts for CI/automation
sudo UCM_PORT=8443 UCM_FIREWALL=no dpkg -i ucm_2.213_all.debDefault Credentials
- Username:
admin - Password:
changeme123
Change the password immediately after first login!
Documentation
- Installation Guide
- API Documentation