doveadm pw -t said the password matched. The reset tool said it succeeded. Auth still failed. A walk through the passdb/userdb split that turns a 'wrong password' into a 'user doesn't exist' and why resetting the password three times was never going to help.
A user couldn't log in to webmail. Simple enough. I reset their password. The reset tool said `Password has been reset.` I tested it. Auth failed.
I reset it again, manually this time, with a freshly generated hash. Failed. A third time, same result. At that point I'd spent twenty minutes on a wrong password problem, and I had a nagging feeling I was solving the wrong problem entirely.
The password was fine the whole time. The account wasn't being looked up at all. This is the post I wish I'd had twenty minutes earlier.
The symptom, precisely
The user was an admin account on an iRedMail box (iRedAdmin over a Dovecot/MariaDB backend). Two login paths failed:
- Webmail login authentication failed
- iRedAdmin login `INVALID_CREDENTIALS`
So it wasn't a webmail bug. It was the account's authentication, at the Dovecot level. Good that narrows it. The cleanest test for Dovecot auth is `doveadm auth test`, which exercises the same passdb path a real login uses:
doveadm auth test admin@example.net
Password:
auth failed
Fails. Now, the crucial next command — the one I skipped for too long:
doveadm user admin@example.net
If the account exists and resolves, this prints the user's mail fields. If the account can't be resolved, it prints `user doesn't exist` (or nothing useful). That single command splits the entire problem in half:
`doveadm user`succeeds,`auth test`fails → password/hash problem (passdb).`doveadm user`fails the account isn't being found (userdb / query problem).
I eventually ran it. It failed. Which meant every password reset I'd done every one that succeeded was changing a hash on a row that the auth stack was never reaching.
The database was correct. That was the trap.
Here's what made this so confusing. I checked the database directly, and everything looked right:
MariaDB [vmail]> SELECT username, active, isadmin, isglobaladmin FROM mailbox WHERE username='admin@example.net';
| username | active | isadmin | isglobaladmin |
| admin@example.net | 1 | 1 | 1 |
Row present, account active, admin flags set. The password hash had a `{SSHA512}` prefix and a plausible length. And the decisive check:
doveadm pw -t '{SSHA512}...stored-hash...' -p 'the-password'
Password matches
It matched. The stored hash was correct for the password I'd been given. So the password was right, the hash was right, the row was right — and auth still failed.
When every component checks out individually and the system still fails, the bug is in how the components are wired together, not in any one of them.
The actual cause: a query joining on the wrong thing
Dovecot resolves an account in two stages, and it's worth being precise about them because the distinction is the whole story:
- passdb proves the password. Answers is this the right password for this user?
- userdb locates the user. Answers does this user exist, and where's their mail?
`doveadm pw -t` only tests the hash. It doesn't touch userdb. So it can happily say password matches for a user that userdb refuses to resolve at all. That's exactly what was happening.
In this setup the userdb query joined against a domain-admins table. That table had a row for this account with the domain column set to a literal `ALL` (the way this particular admin had been granted global admin), rather than an actual domain. The mailbox lookup expected a real domain there, the join produced nothing, userdb reported no such user, and Dovecot never got as far as checking the password which, again, was correct the entire time.
So the user-facing message was invalid credentials. The real message was I can't find this account. Those are very different errors wearing the same disguise
How to actually tell them apart
If you're staring at a login that fails while the hash checks out, do these in order. They cost seconds and they save the twenty minutes I lost.
1. Confirm the account resolves.
doveadm user someuser@example.net
Success! userdb is fine, look at passdb (hash/scheme). Failure! userdb is your problem, and password resets are a waste of time.
2. Turn on auth debugging, temporarily.
In the Dovecot config, enable:auth_debug = yes
auth_debug_passwords = yes
auth_verbose = yes
Restart Dovecot and attempt one login. Then read the journal:
journalctl -u dovecot --since "2 min ago"
The log tells you which side failed. Two messages matter:
`unknown user`userdb couldn't resolve the account. Query/join problem. Not a password problem.`password mismatch`userdb found the account, passdb rejected the hash. Now it's a hash/scheme problem.
These two lines lead to completely different fixes, and no amount of resetting will help if you're in the first case.
Turn the debugging back off.
`auth_debug_passwords = yes` logs credentials. It is for a one-off diagnostic, not for steady state. I have a note in my runbook that says this in bold, because I've forgotten it exactly once, and once was enough.
Finely The fix:
Once I saw `unknown user` in the log, the fix was a one-line data correction on the admin row setting the domain column to the real domain instead of the `ALL` sentinel the join choked on after which `doveadm user` resolved, `doveadm auth test` succeeded, and both webmail and the admin panel accepted the login. The password had never been the problem, so the fix didn't involve the password at all.
The important part isn't the specific column. It's that the fix was in the lookup, not in the credential. I'd been editing the credential three times for a bug that lived somewhere else entirely.
What I'd do differently
- I'd run
`doveadm user`before touching the password. Always remember It's the first fork in the road and it takes one second. The instinct to just reset it is strong and wrong. - I'd stop trusting
`doveadm pw -t` as proof the account works. It proves the hash matches the password. It says nothing about whether the account is findable. Two different questions; I'd been answering one and asking the other. - I'd read the log's exact wording as a branch point, not noise. Unknown user and password mismatch are not interchangeable failure messages. One means "not found, one means found but rejected. The first thing I do now with any auth failure is get to the point where I can read which one I'm looking at.
If you're currently on your second or third password reset for the same user, stop. Run `doveadm user`. You're probably not fixing a password you're fixing a lookup, and you just haven't looked at it yet.