Aditya Sharma

WordPress Application Passwords Not Working: Fix the REST API 401

On this page, 8 sections

An application password that returns a 401 fails in a handful of ways. Four of them come with an error code you can read on REST. The code in the JSON body names the cause: invalid_username, invalid_email, application_passwords_disabled_for_user or incorrect_password. One more case leaves no application-password code, because WordPress never tried to check the password at all.

Start with one curl request that prints the full response. The body decides everything that follows.

If you want the security model behind these passwords first, I wrote that up as how WordPress application passwords work and what they can do. This article is the troubleshooting half.

The test that separates the causes

Run this from a terminal, with your own domain, username and password:

curl -i -u "username:abcd efgh ijkl mnop qrst uvwx" \
  https://example.com/wp-json/wp/v2/users/me

The -i flag prints the response headers and body. Use a real username or email, and the application password exactly as WordPress showed it. Spaces are fine.

Read two things: the HTTP status and the code field in the JSON. I read the WordPress source on 11 October 2026 to map those codes, and I did not run this against a live site for this article.

A 200 means authentication worked and the problem is in your application, not in WordPress. A 401 with one of the four codes tells you which check failed. A 401 with a generic “not logged in” style message and none of the four codes means WordPress did not check your password.

The likeliest reason is the silent case below, after you rule out two others the code allows: no application password exists anywhere on the site yet, or the request is not a REST API or XML-RPC request.

How WordPress checks an application password

The function is wp_authenticate_application_password() in wp-includes/user.php. Its checks happen in this order.

  1. It returns early if a user was already authenticated by another method.
  2. It returns early if no application password exists on the site at all (WP_Application_Passwords::is_in_use()).
  3. It only continues for a REST API or XML-RPC request. For any other kind of request it does nothing, and a filter named application_password_is_api_request can change that.
  4. It looks the user up by login name first, then by email address if the name looks like an email.
  5. It checks that application passwords are available, then that they are available for that user.
  6. It strips every character that is not a letter or digit from the password you sent, then checks it against each stored hash for that user.

If step 6 finds no match, it returns incorrect_password with the message “The provided password is an invalid application password.”

The REST layer then turns the error into a response. rest_application_password_check_errors() in rest-api.php sets the status to 401 when the error has no status of its own.

The named failures

invalid_username or invalid_email

The username you sent is not a login name, and it is not an email address on any account. The lookup uses the user’s login name, then the email. It does not use the display name or the “nicename” shown in author URLs. If your script sends “Aditya Sharma” and the login is aditya, you get invalid_username.

The profile screen shows the login name on the user’s edit page. Use that, or use the account’s email.

application_passwords_disabled

Application passwords are not available on this site. Core’s check is wp_is_application_passwords_supported(), and in the version I read it returns true when is_ssl() is true or the environment type is local.

On REST you will not see this code: when the feature is off, core returns early and the request gets a generic 401. The code itself appears only on XML-RPC. A site served over plain HTTP, outside a local environment, is the usual way to land here.

Two things can cause it on a site you believe is on HTTPS:

  • SSL ends at a proxy. is_ssl() looks at the HTTPS server variable or port 443. If a CDN or load balancer terminates SSL and forwards plain HTTP to the server, WordPress thinks the request is not secure. The redirect loop article shows the wp-config.php block that makes WordPress recognise HTTPS behind a proxy. Use it only if the proxy is the one setting the header, because a client can send that header too and you do not want a forged one to fool an authentication endpoint.
  • A plugin turned the feature off. The filter wp_is_application_passwords_available can return false. A security or hardening plugin may be applying it.

You can set the environment type to local for a development site. Do not do that on a production site, because it removes the HTTPS requirement.

application_passwords_disabled_for_user

Application passwords are on for the site, but off for this account. The filter wp_is_application_passwords_available_for_user decides this. The default is true for every user. A plugin or a line in a theme’s functions.php has changed it for this user or this role. The error message in core says to contact the site administrator.

incorrect_password

The user exists and the feature is on, but none of the user’s stored application passwords matched. This is the plain “wrong password” case, with a few specific versions of it:

  • The password was revoked, and the script uses an old one. Check the user’s profile screen for the list.
  • The password belongs to a different user than the one named in the request.
  • The password was copied with a typo. Spaces are handled for you, because core removes non-alphanumeric characters before checking, but the letters and their case still have to be right. The 2020 integration guide says generated passwords are 24 characters long, made of letters and digits only.
  • The password is for a different site. A staging site and a live site each have their own list.

The fix is to create a new application password for the exact user and site, copy it once, and update the script.

The silent failure: no Authorization header reaches PHP

Core reads the credentials from $_SERVER['PHP_AUTH_USER'] and $_SERVER['PHP_AUTH_PW']. In wp_validate_application_password(), if either one is missing, the function returns right away with the incoming value. It does not set an error. As far as WordPress knows, the request carried no credentials.

So if your web server or a layer in front of it does not pass the HTTP Authorization header through to PHP, WordPress never tries to check the password. The response is a plain 401 with no application-password error code.

I derived this from reading the code. Which server setups strip the header, and the exact rewrite rule or proxy setting that restores it, differ by host, and I did not reproduce any of them for this article. The way to check on your own server is empirical:

  1. Run the curl command above. Note that you get a 401 with none of the four codes.
  2. Ask your host, or look in the server config, whether the Authorization header is forwarded to PHP. On a managed host, ask support.
  3. After a change, rerun curl. A changed status or a named error code means PHP now sees the header.

Cloudflare Access, a reverse proxy or a basic-auth layer in front of the site can also consume or replace the Authorization header with its own. If the site has HTTP basic auth at the server level, that auth uses the same header, and the application password cannot share it.

REST requests from a logged-in browser use a cookie plus a nonce. In rest_cookie_check_errors(), a request with a cookie but no nonce is treated as unauthenticated, and one with a bad nonce returns rest_cookie_invalid_nonce with a 403 status.

That is a different error from the application-password ones. If you test from the browser console while logged in, you are testing cookie authentication, not your application password. Use curl for the application-password test.

Questions people ask

Do application passwords work over HTTP?

Not by default outside a local environment. In the code I read, they need is_ssl() to be true or the environment type to be local.

Can I use my normal login password instead?

In the REST path I read, only application passwords are checked for Basic authentication. I did not test sending the normal password, so create an application password.

Why does it work in Postman but not in my script?

A frequent difference is how credentials are sent. Check that your script sends the Authorization header and uses the login name or email, not the display name. Compare the two requests with -v in curl.

Do this today

Run the curl command against /wp-json/wp/v2/users/me with your username and application password, and write down the status and the code value. If there is no code from the list of four, suspect that the header is not reaching PHP.

Resources

Tell me where I am wrong

Your email is not published and I do not add it to any list. Corrections with a source are the ones I act on fastest.