You hit Save. WordPress answers with “Are you sure you want to do this?” and the work disappears.
That is a nonce error, a failed check on the one-time security token WordPress attaches to forms, admin URLs, and REST requests.
Frustrating, and almost never random. Expired tokens, cached pages, session mismatches, and low PHP limits cause nearly all of them.
What this guide covers:
- How nonce verification works, tick by tick
- What each error message actually means
- Fixes for caching plugins, CDNs, AJAX calls, and the WooCommerce Store API
- CSP and Ethereum nonce errors, which share the name and nothing else
By the end, you will diagnose the cause in minutes instead of deactivating plugins at random.
What Is a Nonce Error
A nonce error is a failed validation of a one-time security token that WordPress attaches to forms, admin URLs, and API requests. The server rejects the submission because the token is missing, expired, or was generated for a different user, action, or login session.
Nonce stands for “number used once”. WordPress breaks that definition twice over: its nonces are hashed strings of letters and numbers, and the same value keeps working until it expires.
What a nonce error blocks:
- Plugin and theme installs from the dashboard
- Media library uploads
- Settings saves inside wp-admin
- WooCommerce add-to-cart and checkout submissions
- Any AJAX handler wired to
checkajaxreferer()
The failure surfaces as a 403 Forbidden response, which is why people file it under permissions. It isn’t one.
Key difference: a permissions error means the user lacks the capability. A nonce error means the request could not be proven to originate from your own site.
Have you seen the latest WordPress statistics?
Discover the latest WordPress statistics: market share, security trends, performance data, and revenue insights that shape the web.
Check Them Out →Patchstack logged 7,966 new vulnerabilities across the WordPress ecosystem in 2024, up 34% on 2023, and CSRF appears in that count exactly because developers skip the token check.
How Does Nonce Verification Work
Nonce verification compares the token sent with a request against a hash the server rebuilds on the spot. WordPress hashes 4 inputs together: the action string, the user ID, the session token, and the current nonce tick. Matching hash, request proceeds.
Where the token travels: a hidden form field named wpnonce, a URL query parameter, or the X-WP-Nonce header on REST calls. It sits inside the page HTML in plain sight.
wpverifynonce() does not return a simple true or false. It returns the tick number, and that number tells you how old the token is.
| Return value | Meaning | Age of token |
|---|---|---|
| 2 | Matches the current tick | Under 12 hours |
| 1 | Matches the previous tick | 12–24 hours |
| false | No tick matches | Nonce error |
The session token is the part that catches people out. Because wpgetsessiontoken() feeds the hash, a nonce minted during one login dies the second that session ends.
WordPress core itself has been caught out here. CVE-2017-9063 covered a CSRF hole in the filesystem credentials dialog before 4.7.5, where no nonce was required at all.
What Causes a Nonce Error
5 causes account for nearly every nonce error: an expired tick, a session or cookie mismatch, a cached page serving a stale token, a mismatched action string, and PHP limits that print the same message. The first three fill most support queues.
Expired Nonce Tick
Default lifetime is 86,400 seconds, split into two ticks of 43,200 seconds each.
Real validity runs between 12 hours plus one second and a full 24 hours, depending on where in the tick the token was created (WordPress developer documentation).
Classic trigger: a draft tab left open overnight, then submitted with coffee in hand the next morning.
Session and Cookie Mismatch
Logout in another tab: kills the session token, invalidates every nonce already rendered.
Cookie expiry: the browser drops wordpressloggedin, so the user ID feeding the hash drops to 0.
Regenerated salts: new security keys in wp-config.php void every nonce and auth cookie site-wide, instantly.
Mismatched WordPress Address and Site Address settings (one with www, one without) produce the same symptom and often get filed as a login problem instead.
Cached Pages Serving Stale Tokens
SQ Magazine’s 2025 market data puts caching plugin adoption at 82.4% of WordPress sites, which explains why this cause dominates on live sites.
Full-page caching stores the token along with the markup. Thirteen hours later the HTML is still fresh enough to serve and the nonce inside it is already dead.
Wrong Action String
A nonce created for one action never validates against another. The strings have to match character for character.
wpcreatenonce('savesettings') checked with checkadminreferer('save-settings') fails silently. One hyphen. That’s the whole bug.
Shield Security research notes a single character error in validation logic can break CSRF protection outright.
Server Limits Misread as Nonce Errors
PHP kills oversized POST requests before WordPress ever runs the token check, and the browser lands on the same “expired” screen.
postmaxsizelower than the uploaded ZIPuploadmaxfilesizebelow the theme file sizemaxinputvarscapped on forms with hundreds of fields
Related territory: a 413 request entity too large response and failures where images refuse to upload trace back to the same three settings.
What Do the Common Nonce Error Messages Mean
WordPress prints 5 different messages for what is often the same underlying failure. The wording rarely describes the actual cause, so map the message to its real trigger before touching any settings.
| Message | Usual real cause | Where it appears |
|---|---|---|
| The link you followed has expired | PHP upload or POST size limit | Theme and plugin uploads |
| Are you sure you want to do this? | check_admin_referer() failure | wp-admin forms and action URLs |
| Cookies are blocked due to unexpected output | Session token unavailable when the page renders | Login screen |
rest_cookie_invalid_nonce (403) | Stale nonce in cached HTML | Browser console and REST API calls |
| Invalid nonce | Cached cart fragments or stale checkout data | WooCommerce cart and checkout |
“The link you followed has expired” is the biggest liar of the group. It reads like a timing problem and is almost always a server limit, which is why it shows up when plugin installs refuse to run.
Where to confirm the cause: the WordPress error log for PHP notices, the server error log for limit rejections, and the browser Network tab for the raw 403 response body.
Anything failing mid-update belongs in the same bucket as other plugin update errors, since both stall on a failed token check before any files move.
How to Fix a Nonce Error in WordPress
Work from fastest to most invasive. A page refresh regenerates the token against the current tick and clears roughly half of all reported cases, so start there before editing a single file.
- Refresh the page and resubmit the action
- Hard refresh, then clear cookies for the site domain
- Log out, log back in, retry
- Purge cache in order: plugin cache, object cache, CDN, browser
- Raise PHP limits if the error only fires on uploads
- Deactivate plugins in batches to isolate a conflict
- Switch to a default theme to rule out theme-level form handling
Raising PHP Limits
Set postmaxsize higher than uploadmaxfilesize, never equal. Setting them to the same value still fails, and plenty of tutorials get that wrong.
Common working pair: uploadmaxfilesize = 64M and postmaxsize = 128M, applied through php.ini, .htaccess, or your host’s PHP selector.
Low limits also produce the memory exhausted error, so raise memorylimit in the same pass.
Isolating a Plugin or Theme Conflict
Batch deactivation beats one-at-a-time on sites carrying 40 plugins. Halve the list, test, halve again.
Tools worth having open: Query Monitor for hook-level inspection, the Health Check and Troubleshooting plugin for a safe per-user test mode, and WP-CLI for wp cache flush.
If the error survives every plugin being off, the theme is next. A broken form handler in a child theme sits in the same family as other theme errors, and switching to Twenty Twenty-Four confirms it in under a minute.
Adjusting Nonce Lifetime
The noncelife filter takes a value in seconds and applies globally, including wp-admin, AJAX, and REST authentication.
Drop it into a snippet plugin or functions.php: addfilter('noncelife', fn() => 4 HOURINSECONDS);
Extending the lifetime to paper over a caching problem is a bad trade. Longer tokens mean a wider CSRF window, and the cache is still the thing that’s broken.
How Do Caching Plugins and CDNs Break Nonce Validation
Full-page caching and per-user tokens conflict by design. The cache stores one copy of the HTML for everyone, nonces are bound to a single user and session, so every visitor after the first receives a token that was never theirs.
WordPress core contributor Milana Cap puts the rule plainly: keep the cache lifespan shorter than the nonce lifespan.
| Layer | What to exclude | Verification |
|---|---|---|
| Page cache plugin | Logged-in users, wp-admin, cart, checkout | Response shows a cache MISS |
| CDN edge | ?wc-ajax=, /wp-json/, /wp-admin/, /my-account/, cart, checkout, authenticated requests | cf-cache-status: **BYPASS** |
| Object cache | Nothing by default, but flush after Redis restarts | wp cache flush returns success |
Configuration notes by tool:
- WP Rocket: cache lifespan defaults have caused invalid nonces at 24 hours; the team’s own issue thread settled on a 10 hour lifespan
- LiteSpeed Cache: ESI needs LiteSpeed Enterprise, OpenLiteSpeed will not hold nonces correctly
- W3 Total Cache: exclude admin URLs before enabling any logged-in caching
- Cloudflare APO and Varnish: bypass on
wordpressloggedinandwoocommercecarthashcookies
Real case: Meow Lightbox 5.5.1 fired restcookieinvalidnonce for every visitor on LiteSpeed-cached pages after 12 to 24 hours, because the token was printed inline and cached with the page.
WooCommerce carries extra exposure since the 8.3 release in November 2023 made Cart and Checkout blocks the default. Cart fragments and ?wc-ajax= endpoints belong on the never-cache list, permanently.
How to Fix Nonce Errors in AJAX and REST API Requests
Custom code fails for 3 reasons: the token never reaches the request, the action strings disagree between creation and verification, or the token is stale before the user clicks anything. Each has a different fix.
Passing the Token to AJAX Calls
Hand the nonce to JavaScript with wplocalizescript() or wpaddinlinescript(), then post it back under ajaxnonce.
Matching pair: wpcreatenonce('myaction') on the PHP side, checkajaxreferer('myaction') in the handler. Same string, both ends.
An Ajax request missing the token returns -1 with a 403, which lands in the console as a failed resource load rather than anything mentioning nonces.
REST API Requests and X-WP-Nonce
Cookie-authenticated REST calls need the X-WP-Nonce header carrying a nonce created for the wprest action. No header, no authentication, regardless of a valid login cookie.
In the block editor, wp.apiFetch handles this through createNonceMiddleware, which appends the header to every request without repeating it per call.
The package also retries: when a response comes back as restcookieinvalidnonce, api-fetch pulls a fresh token from the nonce endpoint and repeats the request. Custom clients built against the REST API get no such safety net.
The Logged-Out Edge Case
Guests all share one nonce, because the user ID feeding the hash is 0 for everyone.
A token generated while logged out then verified while logged in fails, which is exactly what happens when a visitor logs in at checkout on a cached page.
WooCommerce’s own tracker flagged this in the Store API: the fix is fetching the nonce from response headers at runtime rather than serializing it into the page at render time.
How Long Does a Nonce Stay Valid and How Is the Lifetime Changed
A WordPress nonce carries a default lifetime of 86,400 seconds. Real validity lands somewhere between 12 hours plus one second and a full 24 hours, depending on where in the tick the token was minted.
The noncelife filter changes that number globally, and it hits everything: wp-admin, AJAX handlers, REST authentication, plugin forms.
| Value | Seconds | What it controls |
|---|---|---|
| Nonce life (default) | 86,400 | Maximum nonce lifetime |
| Single tick | 43,200 | Half the nonce lifetime; one 12-hour validation window |
| Auth cookie default | 172,800 | Authentication session length without “Remember Me” |
The Grace Period Between Ticks
Two ticks, not one. WordPress validates tokens from the current tick and the previous one, which is what creates the variable window.
A nonce created at 11:59 in the morning dies at midday. One created at 12:01 survives until midday the following day.
WordPress developer documentation confirms wpverifynonce() returns the tick number rather than a boolean, so the return value doubles as an age check.
Changing Lifetime With the noncelife Filter
The filter takes seconds and returns seconds. Nothing else.
addfilter('noncelife', function() { return 4 HOURINSECONDS; });
WordPress 6.1 added an $action argument to wpnoncetick(), so lifetimes can now target one action instead of the whole install.
Shorter values tighten the CSRF window. They also mean users on slow forms hit failures more often, so 4 hours is roughly the floor for anything public-facing.
Why Extending Lifetime Backfires
Stretching noncelife to 72 hours to stop cache-related failures treats the symptom and widens the attack window at the same time.
The WP Rocket team ran into this directly. Their own issue thread on invalid nonces at a 24-hour cache lifespan settled on dropping the cache to 10 hours rather than inflating the token lifetime.
Donation Platform for WooCommerce documents the same rule of thumb for its checkout forms: cache around 4 hours, leave the nonce alone.
How Do Login Sessions and Salt Keys Trigger Nonce Failures
Every nonce is bound to a session token and a user ID. Anything that changes either one, a logout, an expired cookie, a rotated key, or a role change, invalidates every token already sitting in a rendered page.
Session Token Binding
wpgetsessiontoken() feeds the hash directly, so tokens are per-session rather than per-user.
“Log out of all other sessions” in the profile screen kills every nonce generated under those sessions, instantly and site-wide.
Two-factor setups that force a logout every 48 hours produce the same effect mid-edit, which is exactly the complaint logged in WordPress Trac ticket 29312.
Regenerating Security Keys and Salts
WordPress stores 8 secret values in wp-config.php: AUTHKEY, SECUREAUTHKEY, LOGGEDINKEY, NONCEKEY, and their four matching salts.
NONCEKEY and NONCESALT sign the tokens. Replace them and every existing nonce dies with every auth cookie.
Useful after a breach, and worth knowing before you go editing config files for unrelated reasons. The Salt Shaker plugin handles rotation without FTP.
Multisite and Cookie Scope
COOKIEDOMAINset to a single subdomain on a multi-domain networkSITECOOKIEPATHpointing at the wrong install path- Mismatched www and non-www between Site Address and WordPress Address
Each one breaks the login cookie before the token check runs, which is a familiar headache for anyone running several WordPress sites off one network.
Reverse Proxies and Role Changes
Proxies: nginx or Varnish stripping cookies before PHP sees them leaves the user ID at 0 during hash generation.
User switching plugins: the session token changes on every switch, so any page left open under the previous identity fails.
Role changes: capability changes do not invalidate nonces on their own, though the follow-up capability check will still reject the request.
What Is a Content Security Policy Nonce Error
A CSP nonce error is a browser-side block, not a server rejection. The browser refuses to run an inline script or style because its nonce attribute does not match the value in the Content-Security-Policy response header.
Same word, completely different mechanism from the WordPress token. Nothing about PHP is involved here.
The Console Message
Chrome prints: refused to execute inline script because it violates the following Content Security Policy directive.
The message then lists the fix options, per the OWASP cheat sheet: the unsafe-inline keyword, a sha256 hash, or a matching nonce.
Inline CSS throws a parallel message about refusing to apply inline style, usually falling back to default-src when style-src was never set.
Why a CSP Nonce Cannot Be Cached
One nonce per response. The header and the markup ship together, so a cached page pairs an old header value with old script tags and the browser blocks everything.
Reusing a fixed nonce across responses (the pattern in plenty of Apache config snippets floating around) removes the protection entirely. An attacker just reads the value and reuses it.
OWASP recommends a random value generated per request, which rules out full-page caching of the HTML carrying it.
strict-dynamic and Fallbacks
strict-dynamic tells the browser to trust scripts created by an already-trusted script, so nonces do not have to be propagated by hand.
You get one or the other. Browsers treat unsafe-inline as ignored the moment a nonce or hash appears in the same directive, and combining both is not the workaround people expect.
W3C’s own spec discussion flags nonce exfiltration through content attributes, and Dropbox published a widely referenced deployment writeup on this exact tension.
Reporting Violations
Run report-uri or report-to before enforcing anything.
Report-only mode logs every violation without breaking the page, which catches third-party tags nobody remembered installing.
On WordPress specifically, blocked inline scripts overlap with warnings that scripts are loading from unauthenticated sources, since both trace back to what the page is allowed to execute.
What Is an Ethereum Transaction Nonce Error
An Ethereum nonce is a sequential counter of transactions sent from an address, starting at 0. A nonce error means the number attached to your transaction does not match what the network expects next, so the transaction is rejected or queued indefinitely.
| Message | Cause | Fix |
|---|---|---|
| Nonce too low | The nonce has already been used on-chain | Sync with the account’s current pending/latest nonce and use the next available nonce |
| Nonce too high | There is a gap because an earlier nonce is still missing | Submit the missing transaction first, or use the correct pending nonce |
| Replacement transaction underpriced | A transaction with the same nonce already exists, but the replacement fee is not high enough | Resubmit with a sufficiently higher fee; the exact required bump depends on the network/client |
Stuck Pending Transactions
One stalled transaction blocks every later one from the same address. Nonce 13 and nonce 14 cannot confirm while nonce 12 sits in the mempool.
Fix the oldest nonce first. Everything else is wasted effort until that one clears.
Two routes: speed up (same nonce, higher fee) if you still want the transaction, or cancel with a zero-value transfer to your own address using that same nonce.
The 10% Gas Rule
Geth only accepts a replacement if the gas price beats the pending one by at least 10%. It is spam protection, not a protocol rule, and different clients apply it differently.
Thirdweb’s error documentation gives the working example: a first transaction at 30 gwei maxFeePerGas needs roughly 40 gwei on the retry.
Resetting Local Nonce Data
MetaMask renamed this in extension version 10.28.1, from “reset account” to “clear activity and nonce data”. Mobile still calls it reset account.
It wipes local transaction history and nonce tracking only. On-chain assets stay untouched, and MetaMask advises using it only when the transaction is not visible on a block explorer.
Check the real count first with ethgetTransactionCount or the nonce field on Etherscan before touching anything. Hardhat and ethers.js both manage nonces automatically unless you override them, and overriding them is where most scripted failures start.
How Are Nonce Errors Prevented
Prevention is configuration, not troubleshooting. Cache exclusions written once at the server level, PHP limits set above realistic file sizes, and token refresh logic on long forms remove nearly every recurring failure before a user ever sees it.
| Layer | Standing rule | Check |
|---|---|---|
| Cache | Never cache admin, cart, checkout, or account pages | Cache lifespan is shorter than the relevant token/nonce lifetime |
| PHP | Set upload and POST limits above the largest plugin/theme ZIP | Site Health → Info → Server |
| Custom code | Use unique action strings for each security-sensitive handler | Perform a capability check alongside every nonce/token check |
Refreshing Tokens on Long Forms
WordPress core already solves this for the post editor. The Heartbeat API calls wprefreshpostnonces() and wprefreshheartbeatnonces(), which returns a fresh restnonce and heartbeat token on each tick.
Borrow the pattern: hook wprefreshnonces for custom screens, or expose a small endpoint your frontend can call before submitting.
Multi-step checkouts and long application forms are the obvious candidates. Anything a user might leave open through a lunch break qualifies.
Code Review Checklist for Custom Plugins
- One unique action string per handler, matched exactly at both ends
checkadminreferer()for admin forms,checkajaxreferer()for AJAXcurrentusercan()on every request, not the token check alone- No token verification skipped on GET actions that change data
Shield Security research calls out the second-to-last item as the frequent miss: plenty of vulnerable plugins validate the token and then never check what the user is allowed to do.
Monitoring Instead of Waiting for Tickets
The wpverifynoncefailed hook fires on every failed check, which makes silent logging trivial.
Log the action string, user ID, and referring URL. A cluster of failures on one endpoint points straight at a cache rule or a mismatched string.
WP Statistics shipped a CSRF hole through version 13.1.1 from missing token validation on a single function, the kind of gap that shows up in logs long before it shows up in a vulnerability database. Worth reading alongside broader questions about why a WordPress site gets flagged as not secure.
FAQ on Nonce Error
What does a nonce error actually mean?
The server rejected your request because its security token failed verification. WordPress could not confirm the submission came from your own site, so it stopped the action before anything was saved, uploaded, or installed.
How do I fix “The link you followed has expired”?
Raise your PHP limits. That message usually points at uploadmaxfilesize and postmaxsize being smaller than the theme or plugin ZIP, not at an expired token. Set postmaxsize higher than uploadmaxfilesize.
Why does my nonce expire so quickly?
It probably did not. A cached page serving old markup, a logout in another tab, or regenerated salt keys all invalidate tokens well before the 24-hour lifetime runs out.
Does clearing the cache fix a nonce error?
Often, yes. Purge in order: caching plugin, object cache, CDN, then browser. If the error returns hours later, the real fix is a cache exclusion rule rather than another manual purge.
Can a plugin cause a nonce error?
Yes, and it is common. Custom code that creates a token for one action string and verifies it against another fails every time. Deactivate in batches to find the culprit.
How long is a WordPress nonce valid?
Between 12 hours plus one second and a full 24 hours. WordPress splits the 86,400 second lifetime into two ticks and accepts tokens from the current tick and the previous one.
Why does WooCommerce show “invalid nonce” at checkout?
Cached cart fragments, almost always. Exclude cart, checkout, my-account, and ?wc-ajax= URLs from every cache layer, including your CDN, then clear everything and retest in incognito.
What does restcookieinvalidnonce mean?
Your REST request carried a stale or missing X-WP-Nonce header. The response is a 403. Cookie-authenticated calls need a fresh token created for the wprest action on every page load.
Is a nonce error a sign my site was hacked?
Rarely. Nonces block CSRF attempts, so a failure can mean the system worked, but expired tokens and cache conflicts explain the overwhelming majority of reports.
How do I fix “nonce too low” in MetaMask?
Clear activity and nonce data in Settings, Advanced. Check the real count on Etherscan first. If a transaction is stuck pending, resend it with the same nonce and at least 10% more gas.
Conclusion
A nonce error is a diagnosis problem, not a mystery. The message on screen rarely names the real trigger, so work from the failing layer down.
Check the age of the page first, then the login session, then every cache sitting in front of it.
Three habits kill most repeat failures:
- Cache exclusion rules covering admin, cart, and ?wc-ajax=
paths
- Matching action strings on both sides of every verification call
- A capability check sitting beside the token check, always
Keep Query Monitor open and the wpverifynoncefailed` hook logging on staging.
Log the action string alongside the user ID. Patterns surface there long before anyone opens a support ticke


