- Error number
- 18487
- Severity
- 14 (user-correctable error)
- State
- Not documented by Microsoft (not reproduced here)
- Product
- Microsoft SQL Server Database Engine
- Versions
- All supported SQL Server versions (2016 to 2025) and Azure SQL Managed Instance, for SQL logins with CHECK_EXPIRATION = ON
- Category
- Logins & Permissions
- Last reviewed
- 24 October 2026
On this page
What it means
Error 18487 means the SQL login uses password expiration (CHECK_EXPIRATION = ON) and its password is older than the Windows maximum password age, so it must be changed before the login can connect.
SQL logins with CHECK_EXPIRATION = ON follow the Maximum password age of the Windows password policy on the server (or the domain policy that applies to it). Once the password is older than that, SQL Server refuses new connections with 18487 at severity 14 and writes the failure to the error log. Microsoft's password policy article describes it the same way: accounts with expired passwords are disabled until the password is changed.
Like error 18488, the reason is only given once the password itself has been accepted, so the message tells you the password was right but too old. It usually appears suddenly on a working system, on the day the password reaches its age limit, which is why it tends to break application logins that nobody has touched for weeks.
Common causes
- An application or service login was created with
CHECK_EXPIRATION = ON(the SQL Server Management Studio box "Enforce password expiration"), and nobody rotated its password. - The Windows or domain password policy lowered the maximum password age, so existing passwords expired earlier than planned.
- A person's SQL login was not used for a long time and the password aged past the limit.
Diagnose it
Read-only These queries only read metadata and dynamic management views. They do not change data, permissions or configuration, so they are safe to run first.
Has the password expired, and when was it set?
SELECT name, is_policy_checked, is_expiration_checked,
LOGINPROPERTY(name, 'IsExpired') AS is_expired,
LOGINPROPERTY(name, 'DaysUntilExpiration') AS days_until_expiration,
LOGINPROPERTY(name, 'PasswordLastSetTime') AS password_last_set
FROM sys.sql_logins
WHERE name = N'app_login'; is_expired = 1 confirms the cause. password_last_set plus the maximum password age tells you when it expired.
Which SQL logins will expire soon?
SELECT name,
LOGINPROPERTY(name, 'DaysUntilExpiration') AS days_until_expiration
FROM sys.sql_logins
WHERE is_expiration_checked = 1
ORDER BY CAST(LOGINPROPERTY(name, 'DaysUntilExpiration') AS int); Small numbers are the next 18487 failures waiting to happen. Rotate those passwords now.
Read the failures in the error log
Requires membership of securityadmin or sysadmin.
EXEC sp_readerrorlog 0, 1, N'has expired'; The login name and client address of each failed attempt.
How to fix it
Each fix is labelled with what it changes. Try read-only checks and the least invasive fix first, and test changes outside production.
Set a new password (administrator)
Changes permissionsSetting a new password resets its age. Update every application that uses the login in the same change.
ALTER LOGIN [app_login] WITH PASSWORD = N'Use-a-strong-unique-password-1';
Let the person change the password at login
Client or network sideA person with an expired password can choose a new one while connecting, for example with sqlcmd -z (we tested this option with a must-change login, error 18488, not with an expired one).
-- sqlcmd -S SQL01 -U app_login -z "New-strong-password-1" -Q "SELECT 1"
Rotate service passwords on a schedule
Changes permissionsFor application logins, keep CHECK_POLICY = ON and rotate the password from a secrets manager before DaysUntilExpiration reaches zero, using the second diagnostic as an alert. Whether an application login should use expiration at all is a policy decision for your security team; do not switch it off just to clear one failure.
Examples
A login whose password is older than the maximum password age
Shown as documented in sys.messages and Microsoft's password policy article, not reproduced: it needs a password older than the Windows maximum password age, and the lab policy was not changed. The client and error log layout follows the reproduced error 18488.
-- The login uses expiration, and the password is too old:
SELECT LOGINPROPERTY(N'app_login', 'IsExpired') AS is_expired; -- 1
-- A connection with the correct password then fails:
-- sqlcmd -S . -C -U app_login -Q "SELECT 1"
Login failed for user 'app_login'. Reason: The password of the account has expired.
How to prevent it
Monitor DaysUntilExpiration for every SQL login with expiration on, and rotate passwords well before the limit. Prefer Windows or Microsoft Entra authentication for services where you can, so password age is handled by the directory instead of connection strings.
Treat a change to the Windows maximum password age as a change that can break SQL logins, and check the expiry list after it. See also error 18456 for other login failures.
Common questions
Where does SQL Server get the expiry period from?
From the Windows password policy that applies to the server (local or domain), through the NetValidatePasswordPolicy API. SQL Server has no separate setting for it.
Does an expired password affect sessions that are already connected?
The message is raised at login, so it concerns new connections. Applications usually notice when their connection pool opens a new connection.
What is the difference between 18487 and 18488?
18487 means the password became too old under the expiration policy. 18488 means an administrator flagged the login with MUST_CHANGE. Both are fixed by setting a new password.
Learn more on CodeWithSQL
Official documentation
- Password policy (Microsoft Learn)
- LOGINPROPERTY (Transact-SQL)
- ALTER LOGIN (Transact-SQL)
- Database engine events and errors 18000 to 18999 (Microsoft Learn)
Last technically reviewed 24 October 2026; examples and diagnostics run on SQL Server 2025 (17.0.1145.4). Message text checked against the engine's sys.messages catalogue.