If you landed here, you probably just typed something like “reset UniFi OS Server admin password Mac” into Google, clicked the first three results, and discovered that none of them work. They all confidently tell you to connect to MongoDB on port 27117, run a quick mongosh update on the ace.admin collection, and you’re back in. That advice is correct — for the classic UniFi Network controllerthat Ubiquiti has been shipping since the 2010s. It is completely wrong for UniFi OS Server, the newer self-hosted product that runs on macOS (and Linux).
I know because I just spent an evening figuring it out. This post is the guide I wish I’d found at the start.
At a glance
| Tested on | UniFi OS Server 5.0.6 on macOS 15.x (Apple Silicon) |
| Last verified | May 2026 |
| Time required | ~15 minutes if you’re comfortable in Terminal |
| What this modifies | A vendor-managed PostgreSQL database (ulp-go) inside UniFi OS Server’s container |
| Risk level | Medium — direct database edit, but with a clean rollback path (Step 3) |
If you’d rather skip the story and just want the gist of what to do, jump to the TL;DR at the bottom — though I’d genuinely recommend reading at least the architecture section first, because if you don’t understand why the standard guides fail, you’ll trip on the same things I did.
What’s actually different about UniFi OS Server
When you install the UniFi OS Server app on a Mac, it doesn’t just run a Java process and a Mongo database the way the old controller did. It installs an entire Linux VM. Specifically, on Apple Silicon Macs it spins up an ARM64 Linux guest using Apple’s vfkit virtualization framework, and inside that guest it runs Podman. Inside Podman, it runs a single big container (docker.io/library/uosserver) that hosts everything — UniFi OS itself, the Network controller, the auth services, the databases, all of it.
The login screen you see at https://localhost:11443 is not the classic Network admin login. It’s the UniFi OS dashboard, served by a Node service called unifi-core and authenticated by a Go service called ulp-go (UniFi Login Provider). Both of those run inside the container. Your password isn’t stored in Mongo. It’s stored in PostgreSQL.
That’s why every guide telling you to update ace.admin in Mongo on port 27117 fails. The Mongo on 27117 is still there — it backs the legacy Network controller — but logging into UniFi OS doesn’t touch it.
When this guide is for you
You’ve got a problem that looks like this:
- You installed the UniFi OS Server app on a Mac (the one in
/Applications/UniFi OS Server.app, not the older “UniFi” Java app). - You’re locked out of
https://localhost:11443. - The login page does not show a “Forgot Password” link, because you never set up Ubiquiti SSO or SMTP.
- You don’t want to nuke the install and re-adopt every device.
If “Forgot Password” is visible, click it. Use email recovery. You’re done. The rest of this post is for the rest of us.
What this guide doesn’t cover
This is specifically for the UniFi OS Server app / dashboard at https://localhost:11443. It does not apply to the legacy UniFi Network application at https://localhost:8443 (which uses MongoDB on port 27117 and where the standard reset guides actually do work), to UniFi device SSH credentials (managed through the controller UI), or to Ubiquiti SSO account passwords (reset those at account.ui.com).
The architecture, in one diagram
Spending five minutes on this saves an hour of confusion later.
Your Mac
└── UniFi OS Server.app
└── vfkit → Linux VM (ARM64, managed by Podman)
└── container: uosserver
├── unifi-core Node service, login UI (port 443 → 11443 on host)
├── ulp-go Login provider, owns the password
├── PostgreSQL 14 Multiple databases: ulp-go, uid, unifi-core, ...
├── MongoDB :27117 Legacy Network controller (NOT used for OS login)
└── MongoDB :27017 System mongod (no app data on my install)
The OS owner password lives in PostgreSQL → database ulp-go → table "user" → column password, stored as an argon2idPHC string. To get to it, you need to reach into the VM, into the container, and into Postgres. That’s three layers of indirection, none of which the standard guides know about.
The plan, in seven steps
Before you start typing, here’s the whole route from locked-out to logged-in:
| # | Step | Where it runs |
|---|---|---|
| 1 | Find the Podman socket and verify the container is up | Mac Terminal |
| 2 | Open a bash shell inside the container | Mac Terminal |
| 3 | Locate the password row in ulp-go and back it up | Inside container |
| 4 | Generate a new argon2id hash with matching parameters | Inside container |
| 5 | Apply the new hash and bump password_revision | Inside container |
| 6 | Decode your username from the login column | Inside container |
| 7 | Log in at https://localhost:11443 | Browser |
If at any point something doesn’t match what I describe, stop and check the Caveats section before improvising. Most failures here come from a small mismatch (wrong id, wrong argon2 parameters, wrong shell quoting) and they’re all recoverable from the backup you’ll take in Step 3.
A note for Windows users. This guide was written and verified against UniFi OS Server on macOS, where the host runs
vfkitand Podman to manage the Linux VM. The Windows version of UniFi OS Server uses WSL2 or Hyper-V instead, which changes the host-side specifics — thepodmanbinary lives somewhere underC:\Program Files\Ubiquiti\..., the API endpoint is a Windows named pipe rather than a unix socket, and the shell is PowerShell so the heredoc in Step 5 won’t work (write the SQL to a temp file and feed it topsqlwith-finstead). The good news: everything from Step 3 onward — once you’re inside the container — is identical, because the container image is the same Linux build either way. Same PostgreSQL, sameulp-godatabase, same argon2 parameters, same Node script. If you’re on Windows, treat Steps 1 and 2 as “find your local equivalent of the Podman binary and socket,” and the rest of the guide applies unchanged. I haven’t personally verified the Windows paths, so if you work them out, drop a comment.
Step 1: Find the Podman socket
The container is already running (the app is running, after all), but you can’t podman ps directly because the connection name uosserver is registered only in the app’s private Podman config, not in your shell’s. Don’t waste time on it. Connect to the API socket directly.
1a. Find the socket path. In Terminal:
ls /var/folders/*/*/T/podman/uosserver-api.sock 2>/dev/null
You should see: a single path like /var/folders/dw/zjzjpq4j6pg_0pr8psyp1tq00000gn/T/podman/uosserver-api.sock. The middle directories vary per Mac. If multiple paths come back (which happens if you’ve switched macOS users since installing), use the most recent one.
1b. Set up two shortcuts you’ll reuse the rest of the way:
PODMAN="/Applications/UniFi OS Server.app/Contents/Resources/resources/podman"
SOCK="/var/folders/<your path>/T/podman/uosserver-api.sock"
1c. Verify Podman can talk to the VM:
"$PODMAN" --url "unix://$SOCK" ps
You should see: one container, status Up and (healthy), with a name like uosserver_5dda2867. That’s our target. If you see no containers, the VM isn’t fully booted — wait 30 seconds and retry.
1d. Capture the container name as a variable:
CONTAINER="uosserver_5dda2867" # replace with your actual name
You’re ready to go inside.
Step 2: Step inside the container
"$PODMAN" --url "unix://$SOCK" exec -it "$CONTAINER" bash
You should see: your prompt change to root@<hash>:/#. You’re now inside the Linux container. Every command from here through Step 6 runs in this shell. If you see a bash error or your prompt doesn’t change, the container name in $CONTAINER is probably wrong — re-run Step 1c.
Step 3: Locate the password row and back it up
This is where I burned the most time. There are a lot of places UniFi OS stores user-shaped data, and most of them are red herrings:
mongo --port 27117 ace.admin— empty / legacypsql -d unifi-directory -t scim_user— emptypsql -d unifi-core -t unique_logins— emptypsql -d uid -t users— emptypsql -d uid -t user_credential— empty
The actual record lives in a database I almost missed: ulp-go. Inside it is a table literally called user (which, since “user” is a reserved word in Postgres, you have to quote everywhere as "user").
3a. Confirm the row exists and looks right:
psql -U postgres -d ulp-go -c 'SELECT id, status, length(password) AS pwlen, substring(password,1,30) AS hash_head, ubnt_sso_id, only_ui_account, only_local_account, password_revision FROM "user";'
You should see: exactly one row. The interesting columns:
id— note this number; it’s almost always1but check anyway, you’ll need it in Step 5.hash_head— the first chunk of the argon2 hash. For my install it was$argon2id$v=19$m=65536,t=1,p=5. Note the parameters; you’ll need to match them exactly.ubnt_sso_id/uid_sso_id— if either is populated, stop. Your account is linked to Ubiquiti SSO and you should be resetting the password ataccount.ui.com, not here. The rest of this guide won’t help you.password_revision— a Unix epoch the service uses to invalidate cached sessions. We’ll bump it in Step 5.
3b. Save a backup of the current row before you change anything:
psql -U postgres -d ulp-go -c 'SELECT id, password, password_revision FROM "user";'
Copy that output into a text file on your Mac. If anything goes sideways later, you can write it back with a single UPDATE. Don’t skip this — it’s the rollback path the rest of the guide assumes you have.
Step 4: Generate a new argon2id hash with matching parameters
This is the tricky bit, and it’s where you can’t just generate “any” argon2 hash. UniFi OS uses very specific parameters: m=65536, t=1, p=5, outputLen=32. If you generate a hash with different parameters, the format will look right but ulp-go won’t validate it.
Inside the container, the unifi-core Node app ships with @node-rs/argon2, a Rust-backed argon2 binding. We can borrow it. The binary is node22 (not node), and the library is in unifi-core’s node_modules.
4a. Move into the unifi-core app directory (so the require resolves):
cd /usr/share/unifi-core/app
4b. Run the hash generator. Replace YOUR_NEW_PASSWORD with what you actually want:
/usr/bin/node22 -e "
const argon2 = require('@node-rs/argon2');
argon2.hash('YOUR_NEW_PASSWORD', {
algorithm: argon2.Algorithm.Argon2id,
memoryCost: 65536,
timeCost: 1,
parallelism: 5,
outputLen: 32
}).then(h => console.log(h));
"
You should see: a single line that looks like:
$argon2id$v=19$m=65536,t=1,p=5$5njsc+M4oyLu0B/mThvipA$3A+XnFIICLdkKES4ghpd1QDRHnSu5ocM9BH1PhQclj8
4c. Copy the full line somewhere safe. The hash is one-way; sharing it with yourself in a notes file is fine. Make sure the parameters chunk in the middle (m=65536,t=1,p=5) matches exactly what you saw in Step 3a — if it doesn’t, double-check your Step 4b parameters before continuing.
Step 5: Apply the new hash
Single quotes inside Postgres SQL plus dollar signs everywhere in the hash plus a shell that loves to interpret both is a recipe for off-by-one quoting hell. The cleanest way to avoid that is a quoted heredoc.
5a. Run the update. Paste your full hash from Step 4 in place of the placeholder. Note the 'SQL' (in quotes) on the opening line — that’s what tells bash to leave $ alone:
psql -U postgres -d ulp-go <<'SQL'
UPDATE "user"
SET password='$argon2id$v=19$m=65536,t=1,p=5$<SALT>$<HASH>',
password_revision=EXTRACT(epoch FROM now())::bigint
WHERE id=1;
SQL
You should see: UPDATE 1. If you see UPDATE 0, the WHERE id=1 filter didn’t match — your user has a different id, which you’d have spotted in Step 3a. Re-run with the correct value.
The password_revision=EXTRACT(epoch FROM now())::bigint part is critical. That column is a Unix timestamp ulp-go uses to invalidate any cached sessions or tokens. Bumping it forces re-authentication and ensures your new hash is honored immediately rather than fighting some in-memory cache.
5b. Verify the change landed:
psql -U postgres -d ulp-go -c 'SELECT id, status, length(password) AS pwlen, substring(password,1,30) AS hash_head, password_revision FROM "user";'
You should see:
hash_headstill matching the argon2id signaturepwlenaround 95–100password_revisiona fresh epoch (within the last few seconds)
If any of those look wrong, restore from your Step 3b backup before going further.
Step 6: Find your username
This part nearly tripped me up. The login column in the user table is bytea, so it doesn’t render as text by default.
6a. Decode the column:
psql -U postgres -d ulp-go -c 'SELECT id, encode(login, '"'"'escape'"'"') AS login_text FROM "user";'
You should see: readable text like andy or admin. That’s your username. If it looks like binary noise, the column is encrypted at rest with a service-managed key — in that case, just use whatever username you originally created the account with.
6b. Exit the container:
exit
You’re back on your Mac.
Step 7: Log in
7a. Open a browser to:
https://localhost:11443
7b. Use the username from Step 6 and the password from Step 4.
You should see: the UniFi OS dashboard. Done.
If for some reason it doesn’t work on the first try, quit the UniFi OS Server app from the Mac menu bar, wait ten seconds, reopen it, give the VM about thirty seconds to come back up, and try again. That’s belt-and-suspenders for any in-memory cache ulp-gomight be holding — but with password_revision bumped, it shouldn’t be necessary.
What to do once you’re back in
A few minutes of housekeeping will save you from doing this again:
Change the password through the UI. Settings → Admins → your account. Setting it through the official UI runs all the proper hooks — token revocation, audit logging, SSO sync if any — that we deliberately bypassed. Treat the password you set in Step 4 as temporary.
Link a Ubiquiti SSO account. Without SSO or SMTP, the dashboard hides the “Forgot Password” link entirely. That’s why you got locked out in the first place. Linking SSO at minimum gives you the email recovery flow.
Configure SMTP as a backup recovery channel for the same reason.
Delete the rollback hash you saved in Step 3 once you’ve confirmed the new password survives an app restart.
Why this took so long to figure out
A few things conspire to make this hard:
- The product name is overloaded. “UniFi” can mean the device firmware, the cloud, the Network app, the Network Server (Java/Mongo), the Cloud Gateway hardware running UniFi OS, or the new self-hosted UniFi OS Server. Search results blur all of them together.
- Almost every reset guide online predates UniFi OS Server. The MongoDB-on-27117 method is real and works for the classic Network controller. Google ranks those guides highly because they’ve been linked for a decade. They’re just for a different product.
- The Mac App Store sandboxing hides everything. All the data lives under
~/Library/Containers/, the binaries are tucked inside the.appbundle, and the Podman socket is in a per-session/var/folders/...path. None of it is where Linux/Windows guides expect it to be. - The argon2 parameters aren’t documented anywhere I could find. I had to read the existing hash to discover that
m=65536, t=1, p=5is what UniFi OS expects.
Caveats
This is an undocumented procedure that pokes at UniFi OS Server’s internal databases. A few things to keep in mind:
- The exact argon2 parameters may change in a future UniFi OS version. Always read the existing
hash_head(Step 3) and match it. - If Ubiquiti changes the schema — column name, table name, encryption — this guide will need updating. The principle (find where
ulp-gostores the password, generate a matching argon2id hash, bumppassword_revision) should still hold. - This is not an officially supported recovery path. Ubiquiti’s officially supported recovery for a fully locked-out self-hosted install is to uninstall and reinstall, then re-adopt your devices. This guide avoids that.
- Take a backup of
~/Library/Application Support/UniFi OS Server/before doing anything destructive if you’re nervous.
TL;DR
Get into the container, generate an argon2id hash with m=65536, t=1, p=5 matching the existing parameters, write it to ulp-go."user".password, bump password_revision to the current epoch, log in, change the password through the UI, link SSO so you never have to do this again.
Hopefully this saves the next person an evening.