What this error actually means
WordPress places a short-lived probe cookie called wordpress_test_cookie when it renders wp-login.php, and then checks for it on the POST back. If the probe cookie is not present when you submit the form, WordPress refuses to run wp_set_auth_cookie() and shows the error. It is a safety interlock: there is no point writing the three real authentication cookies (wordpress_[hash], wordpress_sec_[hash], wordpress_logged_in_[hash], documented in the WordPress handbook) if the browser is not going to accept them on the next request.
The message is therefore a symptom, not a diagnosis. In practice the browser is almost always willing to accept cookies. The question is what is stopping the test cookie from making the round trip.
Common causes, ordered by likelihood
- You are loading
wp-admininside an iframe or a third-party context. SaaS dashboards, site builders, and "preview in a modal" flows embed WordPress inside an iframe on a different domain. Browsers applySameSite=Laxby default, and MDN's SameSite reference is explicit: a Lax cookie is only sent on top-level safe navigations. A POST from inside an iframe on another domain is neither, so the test cookie is dropped. - A browser extension or hardened privacy mode is blocking the probe. Brave's strict Shields, uBlock Origin's cosmetic filters, the "delete cookies on exit" profiles in Firefox, and private/incognito windows with third-party extensions loaded all break the probe silently.
WP_HOME/WP_SITEURLdo not match the URL in your browser bar. The auth cookies are scoped to the domain WordPress thinks it is. If you reachhttps://www.example.com/wp-login.phpbutWP_HOMEishttps://example.com, the browser gets a cookie forexample.comand posts back onwww.example.com. From the browser's perspective the probe cookie is missing.- A cache or security plugin is stripping
Set-Cookieheaders onwp-login.php. Full-page cache plugins occasionally cache the login page for anonymous visitors, and security plugins sometimes rewrite responses. Either can silently remove theSet-Cookie: wordpress_test_cookie=...header. - Something is sending output before the headers. A stray newline, a UTF-8 BOM, or a
echo/var_dumpinwp-config.php, a must-use plugin, or an active theme'sfunctions.phpcan trip PHP's "headers already sent" state. WordPress detects this and explicitly returns the blocked-cookies error instead. This is the classic "unexpected output" variant.
Diagnose which cause applies
Open your browser's DevTools (F12), switch to the Network tab, reload wp-login.php, and look at the response for that request. Then submit the form and look at the POST.
- Does the GET response contain
Set-Cookie: wordpress_test_cookie=...? If not, something on your site is suppressing it. Jump to cause 4 or 5. - Does the Cookie tab on the POST request include
wordpress_test_cookie? If not, the browser received it but did not send it back. Jump to cause 1, 2, or 3. - Does
document.cookiein the Console show the probe after a simple page load? If it does and it still is not sent on POST, you are almost certainly in a cross-site iframe or SameSite context (cause 1).
If the POST response body literally contains the string "Cookies are blocked due to unexpected output", you are in cause 5. WordPress calls that variant out explicitly, so you do not need to guess.
Fix per cause
Cause 1: iframe or third-party context
Open wp-login.php in a new top-level tab instead of inside the embed. If the host application is a SaaS that always wraps admin screens in an iframe, this is a structural limitation of Lax cookies; the only clean fix is to log in once in a top-level tab and then return to the embed. You will know it worked when the URL bar shows wp-admin/ after login instead of bouncing back to wp-login.php.
Cause 2: browser extension or hardened mode
Try a different browser profile with no extensions, or a regular (non-private) window. If that works, the cause is in the current profile. In Brave, open Shields on the site and set cookies to "Allow all cookies" for example.com. In Firefox, "Enhanced Tracking Protection" for the site can be switched off per-site from the shield icon in the address bar. Verify by retrying the login in the modified profile: a successful login rules out the extension stack.
Cause 3: domain or HTTPS mismatch
Check wp-config.php for WP_HOME and WP_SITEURL. If they are absent, check the siteurl and home options in the database. Both values must match the exact URL you hit in the address bar, including the www or absence of it and the https:// scheme. If the browser also shows a broken padlock or console warnings about insecure subresources, the same incomplete migration may be causing mixed content warnings.
// wp-config.php, for a site served on https://www.example.com
define( 'WP_HOME', 'https://www.example.com' );
define( 'WP_SITEURL', 'https://www.example.com' );
After saving, clear any browser cookies scoped to the old hostname and reload wp-login.php. You will know it worked when you can complete the login without bouncing and DevTools shows the auth cookies set on the exact domain in the URL bar.
If you run a multisite with mapped domains, you may also need to stop forcing COOKIE_DOMAIN, which is documented in the wp-config reference as a single value. Multisite with domain mapping needs the default behavior, not a hard-coded domain.
Cause 4: cache or security plugin stripping the header
Rename the plugin you suspect under wp-content/plugins/ via SFTP or the hosting file manager. Start with full-page caches (wp-super-cache, w3-total-cache, wp-rocket, litespeed-cache) and WAF-style security plugins (wordfence, wp-cerber, all-in-one-wp-security-and-firewall). Renaming the directory forces WordPress to treat the plugin as missing, which deactivates it without needing dashboard access.
Retry the login after each rename. You will know you found the culprit when the login succeeds and DevTools shows the Set-Cookie: wordpress_test_cookie=... header returning on the GET. Restore the plugin directory afterwards and contact the plugin vendor: a correctly configured cache should never cache wp-login.php, and a correctly configured WAF should never rewrite Set-Cookie headers on it.
Cause 5: unexpected output (BOM, whitespace, stray echo)
Rename your active theme's directory so WordPress falls back to a core theme (twentytwentyfive or whichever is bundled with your version). Retry login. If that fixes it, the problem is in the theme's functions.php or an included file. If it does not, rename wp-content/mu-plugins next, then check wp-config.php for any line that starts before <?php or comes after a trailing ?> that is followed by whitespace or a newline.
The fastest way to find a BOM is to open the suspect file in a hex-aware editor and look for the bytes EF BB BF at position zero. Remove them and save as UTF-8 without BOM. You will know it worked when wp-login.php returns a clean response body without the "due to unexpected output" phrasing and the auth cookies round-trip successfully.
When to escalate
If you have worked through all five causes and the error is still there, collect the following before asking for help, so whoever looks at it next does not have to start from zero:
- WordPress version, PHP version, active theme, and an exact list of active plugins (from
wp plugin list --status=activeor the Plugins screen) - The full
wp-config.phpwith secrets redacted, showingWP_HOME,WP_SITEURL, and anyCOOKIE_*constants - A DevTools Network export (HAR) for one login attempt, covering both the GET
wp-login.phpand the POST - The response body of the failing POST, specifically whether it contains the words "due to unexpected output"
- Whether the site runs behind Cloudflare, a load balancer, or any reverse proxy, and which hosting tier
- The output of
curl -I https://www.example.com/wp-login.phpshowing the response headers
If you are stuck after that, read why you cannot log in to WordPress to confirm you are in the right article: the sibling WordPress login redirect loop covers the case where the probe cookie works fine but the authenticated session still does not stick, and no access to wp-admin without an error covers situations where you never see the WordPress login screen at all. If you land on the dashboard but then get "Sorry, you are not allowed to access this page", the login has already succeeded and your problem is a capability check, not a cookie one.
How to prevent it from happening again
- Keep
WP_HOMEandWP_SITEURLpinned inwp-config.phpso they cannot drift out of sync with the domain the browser actually hits. - Exclude
wp-login.phpandwp-admin/*from every caching layer you add, whether a plugin, a CDN page rule, or a reverse proxy. - Before committing edits to
wp-config.phporfunctions.php, save as UTF-8 without BOM and make sure there is no whitespace or newline before<?phpand no closing?>at all. - Avoid embedding
wp-admininside an iframe on a different domain; if the host app must do it, open login in a top-level tab and rely on the existing session afterwards.