Skip to content

Instantly share code, notes, and snippets.

@AmazingTurtle
Last active May 9, 2026 18:06
Show Gist options
  • Select an option

  • Save AmazingTurtle/e8a68a0cbe501bae15343aacbf42a1d8 to your computer and use it in GitHub Desktop.

Select an option

Save AmazingTurtle/e8a68a0cbe501bae15343aacbf42a1d8 to your computer and use it in GitHub Desktop.
restore access to unifi controller

Restore access to a unifi controller

When you are unable to login to the unifi controller or forgot admin password, you can restore access using SSH and manipulating mongodb directly.

Warning

Do not uninstall unifi controller - most of the data is not stored in mongodb. In case you thought a mongodb backup would be sufficient, you may have fucked up already, just like me. However I managed to write this "tutorial" for anyone to not run into the same trap.

Apparently this guide no longer works with recent unifi controller versions (starting nov/dec 2022). Since I no longer use unifi hardware in my home system, I can not update the guide myself. In case you've gotten here to recover your data, you're likely doomed. But giving it a try won't hurt anyway, therefore: good luck.

Steps

1. Generate password

Use quickhhash.com to generate a new password. Use sha512 / crypt(3) / $6$ with the any salt you like (I used 9Ter1EZ9$lSt6 in the example below, but it really doesn't matter).

I have generated a dummy password for you if you want to leave this step out. It is Ch4ngeM3VeryQu!ck:

$6$9Ter1EZ9$4RCTnLfeDJsdAQ16M5d1d5Ztg2CE1J2IDlbAPSUcqYOoxjEEcpMQag41dtCQv2cJ.n9kvlx46hNT78dngJBVt0

2. SSH to controller

SSH to the server running the unifi controller. In my case it's running on a raspberry pi.

3. Connect to mongodb

By default unifi comes with mongodb running on port 27117. To connect to it, use the mongo cli tool. Make sure it is installed.

Connect using the following command:

mongo --port 27117

When connected to mongo, execute the following commands to switch the database and verify the installation

use ace;
show collections;

It should show a list of collections, e.g. account, admin, alarm, broadcastgroup, ....

4. Fix

It is very likely that you got here because of power/data loss. You want to check if admins are still in the database. To do so, execute the following command in the mongo cli:

db.admin.find()

If the result is blank or you don't remember your password, there's two ways. Make sure to replace variables before executing commands.

4.1. Change password of existing user

db.admin.update({ name: "<YOUR-NAME-GOES-HERE>" }, { $set: { "x_shadow": "<PASSWORD-HASH-FROM-STEP-1-GOES-HERE>" } });

4.2. Create a new user

db.admin.insert({ "email" : "<YOUR-EMAIL-GOES-HERE>", "last_site_name" : "default", "name" : "<YOUR-NAME-GOES-HERE>", "time_created" : NumberLong(100019800), "x_shadow" : "<PASSWORD-HASH-FROM-STEP-1-GOES-HERE>" })

5. Get admin id

db.admin.find()

Will output something like this:

> db.admin.find()
{ "_id" : ObjectId("5d0a2e7e8f01c49af4cbe3cd"), "email" : "...", ... }

Take the contents of _id, in this case it is 5d0a2e7e8f01c49af4cbe3cd. You should remember it for the next steps.

6. Fix permissions

You will need to attach the admin role using db.privilege to the newly created user. The privilege belongs to an admin and a site_id.

Make sure to get your site_ids using the following command:

db.site.find()

It will show something like this:

> db.site.find()
{ "_id" : ObjectId("5d07b088280f9002d7676c87"), "name" : "super", "key" : "super", "attr_hidden_id" : "super", "attr_hidden" : true, "attr_no_delete" : true, "attr_no_edit" : true }
{ "_id" : ObjectId("5d07b088280f9002d7676c88"), "name" : "default", "desc" : "Default", "attr_hidden_id" : "default", "attr_no_delete" : true }

Once you know the ids of your sites, you can continue with creating privilege entries. You will need the admin id from step 5.

Use the following command for each site you got from db.site.find()

db.privilege.insert({ "admin_id" : "<ADMIN-ID-GOES-HERE>", "permissions" : [ ], "role" : "admin", "site_id" : "<SITE-ID-GOES-HERE>" });

Optionally verify that all privileges have been created using the following command:

> db.privilege.find()
{ "_id" : ObjectId("5d0bb7573d70717df47d5af6"), "admin_id" : "5d0a2e7e8f01c49af4cbe3cd", "permissions" : [ ], "role" : "admin", "site_id" : "5d07b088280f9002d7676c87" }
{ "_id" : ObjectId("5d0bb7573d70717df47d5af7"), "admin_id" : "5d0a2e7e8f01c49af4cbe3cd", "permissions" : [ ], "role" : "admin", "site_id" : "5d07b088280f9002d7676c88" }

7. Test

Now you're all set. You eventually want to restart the unifi controller using service unifi restart. You can login now. Good Luck.

@ThomasDBosboom
Copy link
Copy Markdown

@Zippo2000 In my case, it looks like the moved these settings into a Postgres database.

Thanks for the hint; This is the set of steps that worked for me:

⏺ UniFi CloudKey G2 — Reset Local Admin Password via DB

Context: Modern UniFi OS stores local user credentials in PostgreSQL (ulp-go database, user table) using Argon2id — not in Mongo's ace.admin (which only holds UniFi Network app profile data, no x_shadow).

Steps

  1. SSH to the CloudKey

ssh root@

  1. Open the ulp-go database

su - postgres -c "psql ulp-go"

  1. Find the target user and note the Argon2 parameters

select id, unique_id, status, only_local_account,
left(password, 40) as pw_prefix, password_revision
from "user"
order by id;
Note the m=, t=, p= values in pw_prefix (e.g. $argon2id$v=19$m=65536,t=1,p=8$...).
login is encrypted (bytea) — identify the user by id/only_local_account/creation order rather than by login name.

  1. Generate a matching Argon2id hash offline (on your Mac)

pip3 install argon2-cffi # NOT the "argon2" package
python3 -c "from argon2 import PasswordHasher; \
print(PasswordHasher(memory_cost=65536, time_cost=1, parallelism=8).hash('NEW_PASSWORD'))"
Match memory_cost/time_cost/parallelism to the m/t/p from step 3.

  1. Update the row in psql

update "user"
set password = '',
password_revision = password_revision + 1,
updated_at = now()
where id = <target_id>;

  1. Verify and log in

select id, left(password,40), password_revision, updated_at from "user" where id = <target_id>;
Log in with the new password, then rotate it again via the UI so the change flows through the supported path.

Notes

  • Bumping password_revision invalidates existing sessions/tokens.
  • Don't use online Argon2 generators — the cleartext leaves your machine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment