Skip to content

Est. 2011ยทMicrosoft Partner 7033487ยทDelivery under 3 minยทSupport 7 days a week

Your vault is empty.

Free Fix 18487

Errors 18487 and 18488: Expired SQL Logins and Password Policy Blocks

11 min read Updated October 5, 2026 SQL Server

Fix it now

18487 means the password of the account has expired. 18488 means it must be changed. Neither is corruption, a network fault or a permission problem, and neither is fixed by recreating the login. The question they raise is whether age-based expiry belongs on this login at all.

Run on the instance. It answers which code you have and shows the next wave coming

SELECT name, is_expiration_checked, is_policy_checked,
       LOGINPROPERTY(name,'IsMustChange')        AS must_change,
       LOGINPROPERTY(name,'IsExpired')           AS expired,
       LOGINPROPERTY(name,'DaysUntilExpiration') AS days_left
FROM   sys.sql_logins
ORDER  BY days_left;
  1. For a person, connect once with SSMS using the old password and let it prompt for a replacement, or change it in T-SQL: ALTER LOGIN [jsmith] WITH PASSWORD = 'new' OLD_PASSWORD = 'old';
  2. For a service account, set a new password: ALTER LOGIN [svc_app] WITH PASSWORD = '<long random password>';
  3. Then decide whether expiry belongs on it: ALTER LOGIN [svc_app] WITH CHECK_EXPIRATION = OFF;
  4. For 18488, clear the flag by setting the password again without MUST_CHANGE, then confirm LOGINPROPERTY('svc_app','IsMustChange') returns 0.
  5. Restart the application or service so it stops presenting the old password, then confirm it connects.

Stop the application before you reset the password. A service reconnecting with the old one will keep failing, and on a policy that locks accounts it will lock the login again within seconds of each reset.

If the login connects without a change prompt, you are done. If the reset is itself being rejected, the next section covers the policy rules that produce that.

Why it happens

SQL Server does not maintain a password policy of its own. When a SQL login has CHECK_POLICY on, the instance asks Windows to validate the password against the policy in force on the machine hosting SQL Server: length, complexity, history, lockout and maximum age. On a domain member that is the domain policy; on a standalone server it is the local policy. That is the whole mechanism, and it explains most of the surprises.

CHECK_EXPIRATION is the separate switch that applies the maximum-age part, and it depends on CHECK_POLICY. Microsoft documents the T-SQL defaults as CHECK_POLICY on and CHECK_EXPIRATION off, and documents that CHECK_EXPIRATION cannot be turned on while CHECK_POLICY is off. So do not assume from a default what a given login has; read is_expiration_checked and is_policy_checked and find out.

MUST_CHANGE is the third option and it produces 18488. It marks the login so the password an administrator set must be replaced by the user before the login can do anything. Microsoft documents that MUST_CHANGE requires both CHECK_EXPIRATION and CHECK_POLICY to be on, otherwise the statement fails. It is a sensible setting for a person and a poor one for a service, because a service has no way to answer a prompt.

15128 is the engine enforcing exactly that relationship: CHECK_POLICY and CHECK_EXPIRATION cannot be turned off while MUST_CHANGE is on. It is not a fault, it is the order of operations. Clear MUST_CHANGE by setting the password, and only then change the policy switches.

A person’s password has expired

You have this one if 18487 for a named individual who connects with SSMS or another interactive tool.

  1. Connect with SSMS using the old password; it prompts for a replacement and sets it for you.
  2. Or change it in T-SQL by supplying the old one: ALTER LOGIN [jsmith] WITH PASSWORD = 'new' OLD_PASSWORD = 'old';
  3. Confirm afterwards with SELECT LOGINPROPERTY('jsmith','DaysUntilExpiration');

Supplying OLD_PASSWORD is the route that does not need ALTER ANY LOGIN. Microsoft documents that a principal can change the password for its own login, which is how a user resets themselves without an administrator.

A service account’s password has expired

You have this one if 18487 for an account used by an application, and the failure arrives for every connection at once rather than gradually.

  1. Stop the application or service so it stops presenting the old password.
  2. Set a new password: ALTER LOGIN [svc_app] WITH PASSWORD = '<long random password>';
  3. Decide whether age-based expiry belongs here. If not: ALTER LOGIN [svc_app] WITH CHECK_EXPIRATION = OFF;
  4. Start the service with the new password and confirm it connects.

Turning CHECK_EXPIRATION off is a deliberate trade, not a shortcut. Do it with a long random password, minimum permissions and a note of why, or you have simply moved the problem.

MUST_CHANGE is set on an account that cannot answer a prompt

You have this one if 18488 on a login used by an application, immediately after somebody reset it with the change-at-next-login option ticked.

  1. Set the password again without the flag: ALTER LOGIN [svc_app] WITH PASSWORD = '<new password>';
  2. Confirm it cleared: SELECT LOGINPROPERTY('svc_app','IsMustChange'); should return 0.
  3. Then apply the policy settings you actually want, in that order.

Try to switch the policy options off while MUST_CHANGE is still set and you get 15128, which is the engine telling you to clear the flag first.

The new password is rejected by policy

You have this one if You are trying to fix the expiry and get a validation failure instead: 15116 for a password that is too short, 15118 for one that is not complex enough.

  1. Read the effective policy: run secpol.msc on the SQL Server host and look at Account Policies, Password Policy, or gpresult /r on a domain member.
  2. Meet the length and complexity rules that policy sets. Password history is the one people forget: a recently used password is refused.
  3. If the account genuinely should not be under policy, set CHECK_POLICY = OFF, accepting that CHECK_EXPIRATION goes off with it.

The design is wrong rather than the password

You have this one if You are resetting the same service account every ninety days and it fails in production first, every time.

  1. Move the application to Windows authentication, so the account is managed once in Active Directory rather than twice.
  2. Where the application supports it, use a group managed service account so Windows rotates the password and nothing has to store it.
  3. If SQL authentication is unavoidable, keep expiry off on that login and rotate it on a schedule you control, in a window you choose.

Full reference

Telling the codes apart

Code What Microsoft publishes Who it usually hits
18487 The password of the account has expired Service accounts created years ago under a policy nobody re-read
18488 The password of the account must be changed A login just created or reset by an administrator
15116 Password validation failed: the password does not meet the operating system policy requirements because it is too short Anyone choosing a short password
15118 Password validation failed: the password does not meet the operating system policy requirements because it is not complex enough Anyone choosing a simple password
15128 The CHECK_POLICY and CHECK_EXPIRATION options cannot be turned off when MUST_CHANGE is on Anyone trying to disable policy before clearing the flag

15116 and 15118 are frequently treated as the same message and they are not. One is about length and the other about complexity, and knowing which you have saves a round of guessing at a password the policy will accept.

The login options and how they constrain each other

Option Documented behaviour
CHECK_POLICY Whether the Windows password policy of the host is enforced. Default ON in T-SQL
CHECK_EXPIRATION Whether the maximum-age part is enforced. Default OFF. Cannot be ON while CHECK_POLICY is OFF
MUST_CHANGE Prompts for a new password the first time the login is used. Requires both CHECK_EXPIRATION and CHECK_POLICY to be ON, or the statement fails
UNLOCK Unlocks a locked-out SQL login. A password option on ALTER LOGIN
OLD_PASSWORD Required when a principal changes its own password without ALTER ANY LOGIN

Turning CHECK_POLICY off has two documented side effects people do not expect: the password history is cleared, and the lockout time is reset. Turning it back on initialises the history with the current password hash. If you are toggling it to unlock an account, that is fine; if you are toggling it in a hardened environment, know what you are resetting.

Reading LOGINPROPERTY without guessing

Property What it returns
DaysUntilExpiration Days remaining. 0 if expired or expiring today, -1 if the Windows policy never expires passwords, NULL if CHECK_POLICY or CHECK_EXPIRATION is off
IsExpired 1 if the password has expired
IsMustChange 1 if the login must change its password on next connection
IsLocked 1 if the login is locked
LockoutTime When the login was locked after too many failed attempts
PasswordLastSetTime When the current password was set, which is what a maximum age is measured from
BadPasswordCount Consecutive attempts with an incorrect password, which identifies the service still retrying

A NULL from DaysUntilExpiration is the answer people misread most often. It does not mean the login is fine; it means expiry is not being checked on it at all, which may or may not be what you intended. -1 is different again, and means the policy in force never expires passwords.

Unlocking without changing the password

Microsoft documents toggling CHECK_POLICY off and back on as a way to unlock a login without setting a new password. It works because turning the policy off resets the lockout time. Use it when the password is known to be correct and an application simply hammered it until the account locked, and pair it with fixing whatever was hammering it, because otherwise the account locks again on the next retry.

Seeing the next wave before it lands

A maximum password age is measured from the day the password was set, so logins created together expire together, and nobody records the date at creation time. The query at the top of this article, ordered by days remaining, tells you which logins expire next. Run it on a schedule and this error stops being a surprise, which is a better outcome than getting quicker at fixing it.

Every code this article covers

Code What it points at Source
18487 Login failed because the password of the account has expired. Severity 14 Microsoft Learn
18488 Login failed because the password of the account must be changed. Severity 14 Microsoft Learn
15118 Password validation failed: the password does not meet the operating system policy requirements because it is not complex enough. Severity 16 Microsoft Learn
15128 The CHECK_POLICY and CHECK_EXPIRATION options cannot be turned off when MUST_CHANGE is on. Severity 16 Microsoft Learn
15116 Password validation failed: the password does not meet the operating system policy requirements because it is too short. Severity 16 Microsoft Learn

Confirm the fix worked

  1. Connect with the affected login and confirm it succeeds without a change prompt.
  2. SELECT LOGINPROPERTY('<login>','IsMustChange'); returns 0.
  3. SELECT LOGINPROPERTY('<login>','DaysUntilExpiration'); returns a value you understand: a number you are comfortable with, -1 for a policy that never expires, or NULL because expiry is deliberately off.
  4. SELECT LOGINPROPERTY('<login>','BadPasswordCount'); is not climbing, which proves nothing is still retrying with the old password.
  5. The application reconnects and runs its real workload, not just a test from SSMS.

Questions people ask about this

Is turning CHECK_EXPIRATION off a security failure?

Not on its own. It is a considered decision for an account that cannot answer a password prompt. The failure would be turning it off and leaving a short or shared password in place. Pair it with a long random secret, minimum permissions and a rotation schedule you actually run.

Why can I not turn the policy off on this login?

Because MUST_CHANGE is still set, and Microsoft documents that CHECK_POLICY and CHECK_EXPIRATION cannot be turned off while it is on. That is error 15128. Set the password again without the flag first, confirm IsMustChange returns 0, then change the policy switches.

Does this cost anything to fix?

No. Password policy behaviour is the same in every edition, including the free ones. This is a configuration change using T-SQL and Windows policy tools you already have.

Why did every application fail on the same morning?

Because a maximum password age is measured from the day the password was set. Logins created together expire together, and nobody writes the date down. Query DaysUntilExpiration across the instance now and you will see the next wave coming.

The reset itself is rejected. What is wrong?

Read which code you got. 15116 is a password that is too short and 15118 is one that is not complex enough, both judged against the Windows policy on the host. Password history also applies, so a recently used password is refused even when it meets the other rules.

Related error codes

Was this article helpful?

Your feedback helps us improve our documentation.

Related articles

License Error Error 40615 and 40613: Azure SQL Firewall Blocks and Unavailable Databases Free Fix Error 7391: The Linked Server Could Not Begin a Distributed Transaction License Error Error 8645: Memory Grant Timeouts Against the Standard Edition Memory Cap Free Fix Error 512: Subquery Returned More Than One Value, and Related Query Failures
โ† Back to Knowledge Base