Password Reset Link Says It Already Expired: Troubleshooting Guide
This guide is for customers who click a password reset email and immediately see that the link has expired or is invalid. You will learn how to tell whether the problem is an old email, a second reset request, email-link wrapping, server time drift, or a misconfigured reset lifetime, and how to fix each one.
TL;DR — If your password reset link says it has already expired, the most common cause is that you are opening an older reset email or a newer reset request has already invalidated the first link. Start by requesting one fresh reset email, open only the newest message, and click the link directly from that email in a private/incognito window. Reading time: ~6 min
The scenario
It is the middle of the afternoon, you are trying to sign in before a client call, and you click Forgot password because your saved password is not working. The reset email arrives quickly, but when you click the button, the page says the link has already expired. You try again, request another email, and now two or three reset messages are sitting in your inbox with nearly identical subject lines. It feels like the site is broken, but in practice this is usually one of a handful of very fixable issues.
Symptoms
- The reset page shows one of these messages:
This password reset link has expired.Invalid or expired token.Your reset link is no longer valid.400 Bad Request410 Gone
- You receive multiple password reset emails, and only one of them works.
- The link works for a few seconds, then fails after you request another reset.
- Clicking the button from your email app fails, but copying the full URL into a browser works.
- The link opens a branded email-security page first, then lands on the reset page with an expired-token message.
- The issue affects many users at once after a deploy or server move.
- In application logs, support or engineering may see lines like:
password reset token expired
invalid reset token
token signature invalid
jwt expired
reset token not found
Likely causes
| Cause | How common | Quick check |
|---|---|---|
| You opened an older reset email after requesting a newer one | Very common | In your inbox, sort by newest and open only the most recent password reset email |
| The email client or email-security scanner rewrote or broke the link | Common | Right-click the reset button and copy the full link address, then paste it into a private/incognito browser window |
| The reset link lifetime is set too short in the app | Common | In your provider dashboard or app settings, open the authentication/password reset settings and check the reset token expiry value |
| The app server clock is wrong (time drift) | Less common | Ask support to compare the server time to real UTC time with date -u |
| The reset token is single-use and was already consumed by a preview/scanner or a previous click | Less common | Request a fresh reset, then click it once from a different browser or device without opening the email preview first |
Step-by-step diagnosis
-
Check whether you are using the newest email
In your email inbox, sort by newest first, then search forpassword resetor the product name. Open only the latest message and ignore or delete the older ones.
This is your problem if: the newest email works, or the timestamps show you clicked an older message after requesting another reset.
Jump to:### You opened an older reset email after requesting a newer one -
Open the link in a private/incognito window
In your browser, open a private/incognito window. In the email, right-click the reset button and choose Copy link or Copy link address, then paste the full URL into the private/incognito window.
This is your problem if: the pasted link works in private browsing, or the email app button fails but the copied full URL works.
Jump to:### The email client or email-security scanner rewrote or broke the link -
Request exactly one new reset email and click it once
Go back to the sign-in page and use Forgot password one time only. Wait for the new email. Do not click older emails, and do not tap the button multiple times.
This is your problem if: the first click works, but any second request immediately makes the first email fail. That means the system invalidates older tokens by design.
Jump to:### You opened an older reset email after requesting a newer oneor### The reset token is single-use and was already consumed -
Check for link wrapping or security scanning
When you hover over the reset button, look at the destination. If it starts with your email provider or security gateway domain instead of the product's normal domain, the link may be wrapped for scanning. Common examples are links that first go through a mail-security service before redirecting.
This is your problem if: the copied link contains a long tracking URL, or support confirms the original token was already visited before you clicked it.
Jump to:### The email client or email-security scanner rewrote or broke the linkor### The reset token is single-use and was already consumed -
If you manage the app: check the reset expiry setting
In your provider dashboard or your app's admin/settings area, open the authentication or password reset configuration. Look for a setting named something likereset token expiry,password reset TTL(time to live), orpassword reset expiration.
This is your problem if: the value is extremely short, such as 1-5 minutes, or lower than your normal email delivery time.
Jump to:### The reset link lifetime is set too short in the app -
If the problem affects many users: check server time
Ask your support team or hosting provider to verify the application server time and timezone. Dashboard path varies by host, so the fastest generic check is a CLI command on the app host:
date -u
Compare that output to a trusted UTC clock.
This is your problem if: the server time is off by more than a minute or two, especially after a VM restore, container host issue, or manual time change.
Jump to: ### The app server clock is wrong (time drift)
Fixes
You opened an older reset email after requesting a newer one
Most password reset systems invalidate older links as soon as a newer reset is requested. That is normal and protects your account.
What to do
- Delete or archive all older reset emails.
- Request one new reset email.
- Wait for that single email to arrive.
- Open only the newest message and click the link once.
- If you have both phone and laptop mail apps open, use just one device for the reset.
If you are the site owner and users keep getting confused, update the reset email text to say older links stop working after a new request.
Verify it worked: request one fresh email, open only the newest message, and confirm the reset page loads instead of the expired-link error.
The email client or email-security scanner rewrote or broke the link
Some email apps and mail-security tools wrap links for tracking or scan them before you click. If your app treats the first visit as the only valid use, that scan can consume the token.
What to do as a user
- In the email, right-click the reset button and choose Copy link.
- Paste it into a private/incognito browser window.
- If that still fails, request a new reset email and open it in a different mail client, such as your webmail in a desktop browser instead of a mobile mail app.
What to do as the site owner
- Prefer reset links that are safe to open more than once until the password is actually changed.
- If your app uses one-time tokens, consume the token only after the user submits a new password, not on the first page load.
- If your email service rewrites links, use the plain reset URL in the email body as a fallback.
Example plain-text fallback in the email template:
If the button does not work, copy and paste this full link into your browser:
https://example.com/reset-password?token={{token}}
Verify it worked: a copied link pasted into a private/incognito window opens the reset form successfully.
The reset link lifetime is set too short in the app
If reset tokens expire in only a few minutes, normal email delays can make them useless by the time the user opens the message.
What to do
- In your app or identity provider settings, increase the password reset token lifetime to a practical value such as 30-60 minutes.
- If your framework uses an environment variable, update it and redeploy.
Common examples in app configuration:
PASSWORD_RESET_TOKEN_TTL_MINUTES=60
Or in JSON/YAML-style app config:
{
"auth": {
"passwordResetTokenTtlMinutes": 60
}
}
auth:
passwordResetTokenTtlMinutes: 60
After changing the setting, restart or redeploy the app using your normal hosting workflow.
Verify it worked: request a new reset email, wait 10-15 minutes, then confirm the link still opens the reset page.
The app server clock is wrong (time drift)
Password reset tokens often include an issued-at or expiry time. If the server clock is wrong, a brand-new token can look expired immediately.
What to do If you have a hosting dashboard, use its time-sync or reboot tools first. If your host exposes shell access, resync time on the server.
Check current UTC time:
date -u
On a system using timedatectl:
timedatectl status
sudo timedatectl set-ntp true
If chrony is installed:
chronyc tracking
sudo systemctl restart chronyd
If systemd-timesyncd is used:
sudo systemctl restart systemd-timesyncd
If this is a containerized app, fix the host clock; containers usually inherit time from the host.
Verify it worked: date -u matches real UTC time, and a newly generated reset link no longer shows as expired.
The reset token is single-use and was already consumed
Some apps invalidate the token on the first visit to the reset page. That can be triggered by an email preview, a link scanner, or your own first click from another device.
What to do
- Request one fresh reset email.
- Open the email in webmail on a desktop browser.
- Do not preview the message in multiple apps.
- Click the link once and complete the password change immediately.
What to do as the site owner Change the flow so the token is marked used only after the password is successfully changed.
Pseudo-logic for the safer flow:
GET /reset-password?token=... -> validate token, show form, do not consume token yet
POST /reset-password -> validate token again, change password, then consume token
Verify it worked: a fresh link remains valid long enough to load the form, and only becomes invalid after the password is changed.
Prevention
- Set a practical reset TTL and keep it in config, not hard-coded
Store the value in one place and review it during deploys.
PASSWORD_RESET_TOKEN_TTL_MINUTES=60
- Log reset-token failures with a reason code
Add structured logs so support can tellexpired,already_used, andsignature_invalidapart.
{
"event": "password_reset_failed",
"reason": "expired",
"user": "user@example.com",
"request_id": "abc123"
}
- Alert if server time drifts
Add a host-level monitor for NTP (network time protocol) sync and clock offset. Trigger an alert if offset exceeds 60 seconds.
Alert: host_clock_offset_seconds > 60 for 5m
- Test the full reset flow in CI after auth changes
Add an automated test that requests a reset, opens the latest email link, loads the form, and submits a new password.
Test steps: request reset -> capture email -> open reset URL -> submit new password -> sign in with new password
- Include the full plain URL in reset emails
This gives users a fallback when the button is wrapped or broken by the email client.
Reset your password: https://example.com/reset-password?token={{token}}
- Do not consume tokens on page load
If you own the app, change the implementation so scanners and previews do not burn the link before the human user reaches the form.
Consume token only after successful POST /reset-password
This article was written by an AI system and published pending human review. Verify anything you intend to act on.
Have a project in mind?
Get an instant AI price estimate for it, or talk directly to our team.
One email a month on what we learn building with AI