UniFi OS Server 5.1.21: What Actually Changed
Ubiquiti released UniFi OS Server 5.1.21 on July 7, 2026. The official changelog is short:
Fixed an issue where "Remember Me" local login sessions expired sooner than expected.
At UniHosted, we like to look a little deeper than the changelog. Whenever a new UniFi OS Server release is published, we diff it against the previous version to understand what actually changed. Sometimes that confirms the release notes. Sometimes it uncovers interesting implementation details that don't fit into a few bullet points.
This matters even more now that we've published our own Docker image for UniFi OS Server:
https://github.com/unihosted/unifi-os-server-docker
Running UniFi OS Server in containers means we need to understand not only whether a release works, but also what changed in the services around it: authentication, nginx, startup hooks, bundled binaries, and the filesystem layout.
Version 5.1.21 is a good example.
After comparing unifi-core and the root filesystem with 5.1.19, we found that this release contains a substantial update to the authentication stack. JWT signing moves from HS256 to RS256, a proper JWKS implementation is introduced, and key rotation is redesigned so long-lived sessions continue to work as expected.
Let's walk through the changes.
The Network application didn't change
Both 5.1.19 and 5.1.21 ship with UniFi Network 10.4.57. The Java application inside ace.jar is identical between the two releases.
That immediately narrows the scope. Almost every meaningful change lives in unifi-core, the Node.js service responsible for authentication, session management, and the web frontend.
JWT signing moves to RSA
The biggest change in 5.1.21 is how UniFi signs authentication tokens.
In 5.1.19, JWTs were signed using HS256. A single shared secret was used to both create and verify tokens.
function createToken(user):
secret = currentKey.secret
return JWT.sign(payload, secret, {
algorithm: "HS256"
})
In 5.1.21, UniFi switches to RS256.
Each key rotation now generates an RSA key pair. The private key signs tokens while the public key verifies them.
{ kid, privateKey } = currentKey()
JWT.sign(payload, privateKey, {
algorithm: "RS256",
keyid: kid
})
Verification selects the correct public key using the JWT's kid header.
publicKey = trustedKeys.get(kid)
JWT.verify(token, publicKey, {
algorithms: ["RS256"]
})
This is the architecture used by most modern identity systems. Public keys can safely be shared between services because they can verify signatures but cannot create new ones.
A new JWKS implementation
Supporting RS256 requires a way to distribute public keys.
Version 5.1.21 introduces a JSON Web Key Set at:
/run/unifi-core/jwks.json
JWKS is the standard format for publishing public signing keys.
The implementation is well thought out. Updates are written atomically, the file is protected against symlink attacks using O_NOFOLLOW, and only public keys are written to disk.
Each entry contains the expected RSA metadata:
{
"kty": "RSA",
"kid": "...",
"alg": "RS256",
"use": "sig",
"n": "...",
"e": "AQAB"
}
For anyone integrating additional services with UniFi OS, this follows the same pattern used by OAuth and OpenID providers.
Why the "Remember Me" fix works
The authentication changes also explain the only item in the release notes.
In 5.1.19, signing keys rotated every 24 hours. The previous key remained available for only a short overlap period before being discarded.
That worked well for short-lived sessions, but not for a 30-day "Remember Me" token. The token itself was still valid, yet the signing key no longer existed, making verification impossible.
In 5.1.21, the rotation strategy changes.
Keys still rotate every day, but the system now retains 31 public keys. That number matches the maximum 30-day session lifetime.
Every token issued during the previous month can still be verified, while old private keys are discarded immediately after rotation.
From a user's perspective, the result is simple: "Remember Me" sessions now behave as expected.
Why this matters for containers
This release also shows why containerising UniFi OS Server is not just a matter of wrapping a binary and publishing an image.
A UniFi OS Server release contains several moving parts:
unifi-core- nginx configuration
- startup hooks
- public and private runtime files
- bundled Go services
- installer and helper binaries
- mounted persistent data
When authentication starts writing runtime files like /run/unifi-core/jwks.json, or when nginx compatibility changes, the container image needs to preserve those assumptions.
That is one of the reasons we published unihosted/unifi-os-server-docker publicly. It gives the community a reproducible way to run UniFi OS Server in Docker, and it gives us a clear baseline for testing releases as they come out.
Other improvements
A few additional changes stood out during the comparison.
Token API
The token creation API still limits tokens to 30 days, but the behaviour changes slightly.
Previously, requests exceeding 30 days returned a validation error.
Now they're automatically capped to the maximum supported lifetime instead.
nginx compatibility
Every nginx configuration changes from:
http2 on;
to:
listen 443 ssl http2;
This isn't a behavioural change.
Instead, it lowers the minimum supported nginx version from 1.30.1 to 1.24.0, improving compatibility with distributions that still package nginx 1.24 as their stable release.
Startup scripts
The startup hooks receive a couple of small cleanups.
Recursive file removal is replaced with file-only deletion in one location, reducing the chance of accidentally removing directories, and the nginx PID path is standardised to /var/run/nginx.pid.
Go services
Several internal Go services receive routine version bumps, including ulp-go and the uid-agent family.
These appear to be normal maintenance updates rather than significant feature changes.
Frontend
The bundled @ubnt/unifi-portal frontend is rebuilt for 5.1.21, but the underlying application version remains the same.
The login UI itself is effectively unchanged.
What didn't change
Some areas remain identical between the two releases.
The bundled UniFi Network application stays on version 10.4.57, and the Java code inside ace.jar is unchanged.
We also observed that two previously identified edge cases remain present:
- The internal peer proxy on port 11083 still relies on client certificate validation without additional URI filtering.
- The WebRTC path validation still doesn't account for double-encoded traversal sequences.
These require a trusted internal position rather than public Internet access, so they are quite different from externally reachable issues, but they're still interesting observations from the diff.
Summary
| Area | Change |
|---|---|
| Authentication | JWT signing moves from HS256 to RS256 |
| Key distribution | New JWKS implementation |
| Session handling | 31 public signing keys retained to support 30-day sessions |
| Token API | Expiry values are capped instead of rejected |
| nginx | Compatibility improved for nginx 1.24 |
| Startup | Safer cleanup logic |
| Go services | Routine maintenance updates |
| Docker | Relevant for our public UniFi OS Server Docker image |
| Network application | Unchanged, still 10.4.57 |
Closing thoughts
At first glance, 5.1.21 looks like a small maintenance release.
Looking at the code tells a more interesting story.
Most of the work in this release focuses on the authentication layer. Moving to RSA-signed JWTs, introducing JWKS, and redesigning key rotation all make the implementation more robust while also fixing the "Remember Me" behaviour.
It also reinforces why we spend time diffing these releases at UniHosted. If you're hosting UniFi OS Server at scale, or running it through a container image, the interesting changes are often in the supporting services around the application.
The release notes tell you what changed. Looking at the implementation explains how it changed.
We'll continue publishing these deep dives as new UniFi OS Server versions are released.