Restore access to Unifi OS Server
Introduction
I migrated my Proxmox host to new hardware and reinstalled my Unifi OS Server. I ran the Proxmox helper script to install it, and all went smooth. At the first boot, the UI loaded and I set it up with a temporary password. Then I restored my backup and the problem started: Login failed. I tried a couple of times with varieties of passwords and usernames but it was to no avail. So after a month I decided to tackle this problem by either reinstalling and starting fresh (because I would only lose metrics I do not use) or try to fix it. In this blog I will detail my coop troubleshooting session with Claude to help other people that somehow locked themselves out as well. Without further ado, let's continue.
A rough start
I started Googling a few times to check if there was an easy command to reset the password. There I struggled already because there was very little documentation or few posts to be found about the new Unifi OS Server. I searched a couple of times for quick fixes but could not find anything. Almost every result talks about MongoDB. Some mention x_shadow for the password hash. But none of them mention that UniFi OS Server moved the actual login auth to PostgreSQL or how to reset this. I spent half an hour with Claude before I found out that MongoDB not the only thing to change.
The situation
UniFi OS Server is not UniFi Network Application with a new name. It runs in a rootless Podman container — so podman ps as root shows nothing, which is the first thing that will make you think the service is broken when it isn't. It has three separate places that touch authentication, and most guides only know about one of them.
- MongoDB (
acedb, port 27117) stores network config and legacy admin accounts with SHA-512x_shadowhashes and this is where almost every password reset tutorial on the internet will send you. However, it is not where the OS login happens. - PostgreSQL (
ulp-godb) is the real OS-level identity store.usertable with Argon2id hashed passwords. This is what the login UI actually checks. I didn't even know of the existence of Argon2id but that's all on me. /data/unifi-core/config/cache/users.jsonis a snapshot of the user state thatunifi-coreserves instead of hitting the DB on every request. If it's stale, your DB changes do nothing.
I didn't know any of this going in. I leveraged Claude to fix this. Or at least, I knew enough to ask the right questions and recognise what the output meant when I pasted it back. I worked through it with Claude and got it fixed.
Getting my (or Claudes) hands dirty
If you're running the community-scripts Proxmox LXC version, the first advantage you have over hardware appliance owners is direct shell access:
pct enter <CTID>
The service name tripped me up. It's uosserver.service — not unifi-os.service, not unifi.service. Claude suggested checking the wrong name first (unifi-os.service), got "unit not found", and immediately corrected course:
systemctl list-units | grep -i uos
systemctl status uosserver.service
The Podman container runs under a non-root user, which is why podman ps looked empty. To get inside:
ps aux | grep podman # find the user
su - <user> -s /bin/bash -c "podman exec -it uosserver bash"
The community script setup the user uosserver so the command was easy:
su - uosserver -s /bin/bash -c "podman exec -it uosserver bash"
We're in!
MongoDB: The Wrong Answer, Done Properly
Claude's first suggestion was MongoDB. That's where every UniFi password reset guide points, and it's a reasonable starting assumption. I noticed duplicate admin accounts from the cross-instance restore (I think). Double accounts with the same name and a different ID. This is actually a real problem worth fixing, just not the one blocking login.
Find the legitimate admin by cross-referencing the privilege collection:
mongo --port 27117 ace --eval 'db.privilege.find().pretty()'
The admin_id field tells you which account actually has admin rights. Match it:
mongo --port 27117 ace --eval 'db.admin.find({"_id": ObjectId("<admin_id>")}, {name:1, email:1}).pretty()'
Remove the orphan:
Watch out, if you delete the wrong ID you have te reinstall fully!
mongo --port 27117 ace --eval 'db.admin.remove({"_id": ObjectId("<orphaned_id>")})'
I set x_shadow hashes to be sure that wouldn't intervene:
openssl passwd -6 "Welcome123!"
The I updated my ID with the newly generated password:
mongo --port 27117 ace --eval '
db.admin.update(
{"_id": ObjectId("<object-id>")},
{$set: {"x_shadow": "<hash from openssl>"}}
)'
I restarted the service to log in and it didn't work and I told Claude that. Then Claude "realised" I was using Unifi OS Server. Why it didn't register this I don't know - I actually stated Unifi OS Server in my first prompt:
I reinstalled my Unifi OS Server and restored a backup. The backup is loaded. However, it seemed to set the password differently and now I cannot get back in. How can I regain access?
The Actual Fix: PostgreSQL
I pivoted to fix the login in PostgreSQL with guidance from Claude. Please take it easy on me: I'm not a DB admin so I was following Claude on this one.
First Claude asked me to check for the ulp-go database:
psql -U postgres -l
There it was: ulp-go. Claude recognised the name as it's the auth database Ubiquiti moved to in newer versions of the platform. Apparently I'd been fixing the wrong database.
Check your user:
psql -U postgres -d "ulp-go" -c 'SELECT id, password, status, only_local_account FROM "user";'
Two problems arose, both caused by the cross-instance restore:
only_local_account = false — the account was configured with Ubiquiti cloud/SSO auth from the reinstall. In my old instance I had turned it off.
psql -U postgres -d "ulp-go" -c 'UPDATE "user" SET only_local_account = true WHERE id = 1;'
Then I updated the password hash:
# Check if argon2 is available
which argon2 || apt-get install -y argon2
# Generate hash
HASH=$(echo -n "Welcome123!" | argon2 $(openssl rand -base64 12 | tr -d '/+=') -id -m 16 -t 1 -p 2 -l 32 -e)
echo $HASH
# Update password
psql -U postgres -d "ulp-go" -c "UPDATE \"user\" SET password = '$HASH', password_revision = password_revision + 1 WHERE id = 1;"
Verify it wrote correctly before moving on:
psql -U postgres -d "ulp-go" -c 'SELECT password FROM "user" WHERE id = 1;'
Claude asked me to delete the cache and restart. So I deleted the cache file rm /data/unifi-core/config/cache/users.json, exited the podman container exit and ran systemctl restart uosserver.service.
That worked.
The mobile app still wouldn't connect. Removing and re-adding the console in the app fixed it in thirty seconds.
What Actually Broke
| Layer | Problem | Fix |
|---|---|---|
| MongoDB | Duplicate admin accounts | Remove the orphan |
PostgreSQL user | only_local_account = false, password unknown | Flip the flag, regenerate hash with bash variable |
users.json cache | Stale snapshot served instead of DB | Delete it |
| Mobile app | Cached tokens from old instance | Remove and re-add console |
Claude knew how to guide me through layers two and three, luckily.
The user error
When I reinstalled the previous instance last month, the UI enforced a 12-character minimum password. I set a new password that met the requirements and restored my backup. I forgot about the password change and then I spent the next month occasionally trying to log in with the old, shorter password which obviously failed. And then to think I also updated my password manager with the new password.
What now?
I messed around with the databases and I don't know what the outcome will be if I update anything. So I pulled a backup from my restored instance, spun up a new container and restored it. All while using the same new password with the minimum 12-character requirement.
I hope this helps anyone having the same issue. If not: Claude is your pal!