Error 18456 is the most common login failure in SQL Server, and the message you get is deliberately vague:
That is all the client is told, on purpose. If SQL Server explained exactly why a login failed, it would be handing an attacker a checklist. The real reason is written to the SQL Server error log as a state number, and that number is the whole game.
I have spent more time than I would like reading these states at awkward hours, so here is the version I wish I had found first.
Get the State Number First
Everything below depends on this. Without the state you are guessing.
-- Read the most recent login failures straight from the error log
EXEC xp_readerrorlog 0, 1, N'Login failed', NULL, NULL, NULL, N'DESC';
You are looking for a line like:
Login failed for user 'sa'. Reason: Password did not match that supplied for the login. [CLIENT: 10.0.0.42]
Error: 18456, Severity: 14, State: 8.
That State: 8 is the answer. The client normally receives only the generic State 1: the real reason is deliberately kept server-side so failed logins cannot be used to probe accounts, which is why the error log, not the client message, is where diagnosis starts.
If you cannot get into the instance at all, the log is still readable on disk, usually at ...\MSSQL\Log\ERRORLOG, and any text editor will open it.
What Each State Actually Means
| State | Meaning | What it usually is in practice |
|---|---|---|
| 2 | User ID not valid | Login does not exist. Often a typo, or a login that was dropped |
| 5 | Login not found | Same as 2 in practice. Check spelling and the domain part |
| 6 | Windows login used with SQL authentication | The connection string says SQL auth but the login is a Windows account |
| 7 | Login disabled and password wrong | Two problems at once. Fix the password after enabling |
| 8 | Password did not match | Genuinely the wrong password. The most common of all |
| 9 | Password not valid | Documented as its own state; in practice treat it exactly like 8 |
| 11 | Valid login, but server access denied | Windows login is valid, has no permission to connect. Often a missing group membership |
| 12 | Valid login, server access denied | As 11, usually a login that exists but was denied CONNECT SQL |
| 18 | Password must be changed | MUST_CHANGE is set on the login |
| 38 | Database not found | The login is fine. The database in the connection string does not exist or is offline |
| 46 | Database requested not found | Documented alongside 38, same practical meaning: check the database, not the credentials |
| 40 | Cannot open the default database | The login’s default database is offline, dropped, or in single user mode |
| 58 | SQL login used when only Windows auth is enabled | The instance is in Windows Authentication mode |
| 62 | Contained database SID mismatch | A Windows account reaching a contained database whose user SID no longer matches, usually after a restore between servers |
| 102–111, 132–133 | Azure AD / Entra failure | Cloud authentication problems on Azure SQL, not passwords |
| 122–124 | Empty user name or password | The connection string lost its credentials, common after config templating goes wrong |
| 126 | Database requested does not exist | The Azure-era sibling of 38 |
States 8, 38 and 40 cover the large majority of real incidents. If you are in a hurry, check those three first.
The Fixes, By State
Wrong Password
Confirm the login exists and is not locked out before assuming the password is simply wrong:
SELECT name,
is_disabled,
LOGINPROPERTY(name, 'IsLocked') AS is_locked,
LOGINPROPERTY(name, 'IsMustChange') AS must_change,
LOGINPROPERTY(name, 'BadPasswordCount') AS bad_password_count,
LOGINPROPERTY(name, 'PasswordLastSetTime') AS password_last_set
FROM sys.sql_logins
WHERE name = N'your_login';
A non-zero bad_password_count with a recent PasswordLastSetTime usually means an application is still holding an old password and retrying, which is also how accounts get locked out. Find the application before resetting the password, or you will do this again tomorrow.
The Database Is the Problem, Not the Login
This is the state that fools people, because the login is perfectly fine.
-- Is the database actually there and online?
SELECT name, state_desc, user_access_desc, is_read_only
FROM sys.databases
WHERE name = N'your_database';
If state_desc is anything other than ONLINE, that is your answer. RECOVERING, SUSPECT and OFFLINE all produce a login failure that looks like a permissions problem.
State 40 specifically means the login’s default database cannot be opened. The login can be fixed without touching the database:
ALTER LOGIN [your_login] WITH DEFAULT_DATABASE = [master];
Valid Login, No Access
Almost always a Windows login whose access came through a group that has changed. Check what the server actually thinks the login can do:
SELECT sp.name,
sp.type_desc,
sp.is_disabled,
spm.permission_name,
spm.state_desc
FROM sys.server_principals sp
LEFT JOIN sys.server_permissions spm
ON spm.grantee_principal_id = sp.principal_id
AND spm.permission_name = 'CONNECT SQL'
WHERE sp.name = N'DOMAIN\your_login';
No CONNECT SQL grant, or a DENY, is the cause:
GRANT CONNECT SQL TO [DOMAIN\your_login];
SQL Login on a Windows-Auth-Only Instance
-- 1 = Windows Authentication only, 0 = Mixed Mode
SELECT SERVERPROPERTY('IsIntegratedSecurityOnly') AS windows_auth_only;
If this returns 1 and you need SQL logins, the instance must be switched to Mixed Mode, which requires a service restart. That is a change-window job, not a quick fix.
Windows Login, SQL Auth Connection String
Nothing is wrong with the server. The connection string is using a username and password for an account that is a Windows login. Switch it to integrated security.
The One That Is Not a Login Problem at All
If the error log shows no 18456 entry at the time of the failure, the connection never reached the authentication stage. That is a network, TLS or protocol problem wearing a login error’s clothing, and you should be looking at the pre-login handshake instead.
Stopping It Recurring
- Lockouts that keep coming back are almost always a service, scheduled task or connection pool holding a stale password. Find it before resetting.
- State 38 in bulk usually means an application is connecting to a database that was renamed or dropped, and nobody updated the connection string.
- Grant server access through groups, not individual logins. State 11 and 12 mostly happen when a person’s group membership changes and their individual grant was never there to begin with.
Common Questions
Why does the client only ever show State 1?
The password is definitely right, so why State 8?
BadPasswordCount with a recent password change usually means an application is still retrying an old password, which is also what locks the account.There is no 18456 in the error log at all. What now?
Related Scripts
- SELECT Permission Denied (229), the last door in the corridor this error opens
- Duplicate Key Errors (2627 / 2601), the write-side denial with the value in the message
- Login Failed: Untrusted Domain (18452), the Windows-auth sibling of this error
- Login Disabled (18470), when the login exists but is switched off
- Kerberos vs NTLM, the mechanics behind most login-failure investigations
- Get Permissions and Role Membership, one login’s effective access across the instance
- Get Error Log Patterns, spot repeated login failures before someone reports them
- Get Sysadmin Members, audit who holds the keys
Leave a Reply