A 401 Unauthorized response means the server requires authentication and either did not receive credentials or received credentials that it rejected. The name is slightly misleading because the error is about authentication (who you are), not authorization (what you are allowed to do). A 403 Forbidden error means the server knows who you are and is deliberately refusing access. A 401 means the server does not know who you are because you have not authenticated or your credentials failed.
This error shows up in browsers, in WordPress admin, in API responses, and in automated tools. The fix depends on which layer is returning it and why.
What a 401 response actually means#
When a server returns HTTP 401, it must include a
WWW-Authenticate
header that tells the client what authentication method is required. The most common values are:
WWW-Authenticate: Basic realm="Restricted"
WWW-Authenticate: Bearer
Basic
means the server expects a username and password, typically Base64-encoded in the
Authorization
header. This is common with password-protected directories, staging sites behind HTTP authentication, and basic API endpoints.
Bearer
means the server expects a token, usually a JWT (JSON Web Token) or an API key, in the
Authorization: Bearer <token>
header. This is standard for REST APIs, OAuth flows, and modern web service authentication.
If you see a 401 without a
WWW-Authenticate
header, the server is technically violating the HTTP specification, but some applications do this anyway. Check the response body for additional context about what the server expects.
401 vs 403: the difference that matters#
These two status codes get confused constantly, but the distinction is important for diagnosing the problem.
401 Unauthorized: “I do not know who you are. Provide credentials.” The request did not include authentication, or the authentication it included was invalid (wrong password, expired token, malformed header). The fix is to provide valid credentials.
403 Forbidden: “I know who you are, and you are not allowed to do this.” The request authenticated successfully, but the authenticated user does not have permission to access the requested resource. The fix is to change the user’s permissions or access a different resource.
In practice: if you enter a wrong password, you get 401. If you enter the right password but your account does not have access to the admin panel, you get 403. If your API token is expired, you get 401. If your API token is valid but does not have the required scope, you get 403.
Some applications blur this line. WordPress does not always return proper HTTP status codes for authentication failures, sometimes redirecting to the login page instead of returning 401. Nginx and Apache are more standards-compliant when they handle authentication directly.
Common causes and fixes#
Wrong username or password
The most straightforward cause. You are sending credentials but they are incorrect.
In a browser: If a login dialog pops up (the browser’s native HTTP authentication dialog, not a styled login page), you have entered the wrong username or password for HTTP Basic authentication. This happens with:
- Password-protected directories configured in Nginx or Apache
- Staging sites behind HTTP authentication
- Hosting control panels (cPanel, Plesk, or custom panels)
Fix: Verify the credentials. For HTTP Basic authentication configured in Nginx:
location /staging {
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
}
Check the
.htpasswd
file to verify the username exists. Regenerate the password if needed:
htpasswd /etc/nginx/.htpasswd username
For Apache with
.htaccess
:
AuthType Basic
AuthName "Restricted"
AuthUserFile /path/to/.htpasswd
Require valid-user
Same fix, different syntax. Verify the
.htpasswd
file path is correct and the user exists in it.
Missing credentials entirely
The request did not include an
Authorization
header at all. The server expected authentication and received none.
In WordPress: This can happen when a REST API endpoint requires authentication and the request is made without a nonce or application password. WordPress REST API endpoints that modify data require a valid nonce for cookie-based authentication:
fetch('/wp-json/wp/v2/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': wpApiSettings.nonce
},
body: JSON.stringify({ title: 'New Post', status: 'draft' })
});
Without the
X-WP-Nonce
header, WordPress returns 401 for any write operation through the REST API.
In API calls: If you are calling an external API and getting 401, check whether you are including the API key or token. Common mistakes include:
- Forgetting the
Authorizationheader entirely - Using
Bearerwithout a space before the token (Bearer<token>instead ofBearer <token>) - Putting the API key in the wrong header (some APIs use
X-API-Keyinstead ofAuthorization) - Sending the key as a query parameter when the API expects a header, or vice versa
Expired token or session
Authentication was valid when it was issued but has since expired.
JWT tokens have an expiration time (
exp
claim). Once expired, the server rejects them with 401. The fix is to refresh the token using the refresh token flow, or re-authenticate entirely.
Session cookies expire based on the server’s session configuration. If you were logged in and suddenly get 401, your session may have timed out. Logging in again usually resolves this.
WordPress nonces expire after 24 hours (or 12 hours for half-life). If a WordPress admin tab has been open for a long time without activity, the nonce embedded in the page becomes stale. AJAX requests using that nonce will fail with 401 or the WordPress-specific “Cookie check failed” error. Refreshing the page generates a new nonce.
API key revoked or rotated
If your application was working and suddenly returns 401 without any code changes, someone may have rotated or revoked the API key. This is common after security incidents where keys are mass-rotated, after a team member leaves and their credentials are revoked, or when a trial or subscription period expires.
Fix: Check the API provider’s dashboard for the status of your key. Generate a new key if the old one was revoked. Update your application’s configuration with the new key.
HTTP vs HTTPS mismatch
Some authentication schemes fail silently when the protocol changes. A cookie set with the
Secure
flag is only sent over HTTPS. If your site is configured for HTTPS but something causes a request to go over HTTP (a hardcoded URL, a misconfigured proxy, a mixed content issue), the authentication cookie is not sent and the server returns 401.
Fix: Make sure all requests use HTTPS consistently. Check for hardcoded
http://
URLs in your application. Verify that your reverse proxy or load balancer is passing the correct protocol headers.
.htaccess or Nginx config blocking access
A misconfigured server block or
.htaccess
rule can accidentally require authentication on paths that should be public, or break authentication on paths that should be protected.
In Nginx, check for
auth_basic
directives that may be broader than intended:
# This protects the ENTIRE server block, including public pages
server {
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
# Everything under this server block now requires authentication
}
If you only want to protect a specific path:
server {
# Public by default
location /admin {
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
}
}
WordPress-specific 401 causes
REST API disabled or blocked. Some security plugins disable the WordPress REST API entirely, returning 401 for all API requests. Plugins like Disable REST API or overly aggressive security configurations can cause this. Check your active security plugins if REST API calls that should work are returning 401.
Application passwords not enabled. WordPress 5.6 introduced Application Passwords for REST API authentication. If you are trying to use application passwords and getting 401, verify that application passwords are enabled (some hosts or plugins disable them) and that you are encoding the credentials correctly as Basic authentication:
# Application password format: username:xxxx-xxxx-xxxx-xxxx-xxxx-xxxx
curl -u "username:xxxx xxxx xxxx xxxx xxxx xxxx" \
https://example.com/wp-json/wp/v2/posts
Note that application passwords use spaces between groups, not hyphens, when used as actual credentials.
Blocked by security plugin. Wordfence, Sucuri, and similar plugins can return 401 for requests they consider suspicious. Check the plugin’s activity log for blocked requests matching the pattern you are seeing.
If you are completely locked out of WordPress admin, it may be a 401 from HTTP authentication layered on top of WordPress, not a WordPress login issue.
401 in reverse proxy and load balancer setups#
When your application sits behind a reverse proxy (Nginx in front of Apache, or a CDN like Cloudflare in front of your origin server), 401 errors can originate from any layer in the chain. This makes diagnosis harder because the 401 you see in the browser may not be coming from the application at all.
Nginx as reverse proxy returning 401. If Nginx has
auth_basic
configured on the proxy server block, it will return 401 before the request ever reaches your application. The application never sees the request. Check the proxy server’s Nginx configuration, not just the upstream application’s configuration.
CDN or edge layer requiring authentication. Some CDN configurations add HTTP authentication at the edge for staging or preview environments. If your CDN is returning 401, check the CDN’s access control settings. The origin server may be perfectly accessible directly but blocked at the CDN level.
Proxy stripping the Authorization header. Some reverse proxy configurations strip the
Authorization
header before forwarding requests to the upstream application. This is a security measure in some setups but breaks authentication if the upstream application needs the header. In Nginx, verify the proxy configuration passes the header:
location / {
proxy_pass http://upstream;
proxy_set_header Authorization $http_authorization;
proxy_pass_header Authorization;
}
If
proxy_set_header
is overriding headers without including
Authorization
, the upstream application receives the request without credentials and returns 401.
Identifying which layer returns the 401. Use
curl -v
and look at the response headers carefully. Different layers typically include different headers in their responses. A 401 from Nginx includes
Server: nginx
. A 401 from a WordPress plugin usually includes
X-Powered-By: PHP
and WordPress-specific headers. A 401 from Cloudflare includes
cf-ray
headers. These tell you where to look for the configuration issue.
401 from cron jobs and automated requests#
Automated scripts and cron jobs that make HTTP requests to authenticated endpoints are a frequent source of 401 errors because credentials can expire between when the script was written and when it runs.
WordPress cron hitting authenticated endpoints. If you have a cron job that calls a WordPress REST API endpoint, and the entire site is behind HTTP Basic authentication (common on staging), the cron request fails with 401 because cron does not send HTTP auth credentials by default:
# This fails if the site requires HTTP Basic auth
curl https://staging.example.com/wp-json/custom/v1/sync
# Include credentials for sites behind HTTP auth
curl -u "staging_user:staging_pass" https://staging.example.com/wp-json/custom/v1/sync
WP-CLI on password-protected sites. WP-CLI runs PHP directly and bypasses the web server, so HTTP Basic authentication does not affect it. But if you are using WP-CLI’s
--http
flag to make requests through the web server, the same 401 issue applies.
Monitoring tools getting 401. Uptime monitors that check your site’s health may trigger 401 if they hit a protected path. Configure your monitoring to check a path that is not behind authentication, or configure the monitoring tool to send credentials. A monitor alerting you every time it gets 401 from a protected path is noise, not signal.
Diagnosing a 401#
Check the response headers
The most useful diagnostic step is reading the actual HTTP response:
curl -I https://example.com/protected-path
Look for:
- The
WWW-Authenticateheader, which tells you what authentication method is expected - The response body (use
curl -vfor full output), which often contains a more specific error message - Whether the 401 comes from the application or from a reverse proxy in front of it
- The
Serverheader, which tells you whether Nginx, Apache, or the application itself returned the response
Check the request headers
If you are sending credentials and still getting 401, verify what is actually being sent:
curl -v -u "username:password" https://example.com/protected-path
The
-v
flag shows the request headers, including the
Authorization
header. Verify it is present and formatted correctly. Pay attention to encoding. Basic authentication Base64-encodes the
username:password
string. If your password contains special characters like colons or at signs, they need to be encoded correctly. A password containing
:
will break the
username:password
parsing because the first colon is treated as the delimiter.
Test with a known-good request
Before digging into configuration, confirm that authentication works at all:
# Test HTTP Basic auth directly
curl -v -u "username:password" https://example.com/protected-path
# Test Bearer token auth
curl -v -H "Authorization: Bearer your_token_here" https://example.com/api/endpoint
If these work from the command line but not from your application, the issue is in how your application constructs the request, not in the server configuration.
Check server logs
Nginx access logs show the response code for every request:
grep " 401 " /var/log/nginx/access.log | tail -20
This shows which URLs are returning 401 and from which IPs. If the 401 is coming from a specific location block with
auth_basic
, you can trace it directly to the Nginx configuration.
Nginx error logs may provide additional context:
grep "auth" /var/log/nginx/error.log | tail -20
Common error log entries include “no user/password was provided for basic authentication” (credentials missing entirely) and “user not found” or “password mismatch” (wrong credentials).
For WordPress, check the debug log:
// wp-config.php
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
Then check
wp-content/debug.log
for authentication-related errors. WordPress plugins that block requests often log the reason in this file.
When you see 401 as a visitor#
If you are visiting a website and get a 401 error, it usually means you are trying to access a page that requires a login you do not have. This is not an error on your end. The site owner has restricted access to that page.
If you believe you should have access:
- Clear your browser cache and cookies, then try logging in again
- Try an incognito/private window to rule out cached credentials
- Check whether the URL is correct, you may be hitting a protected staging site instead of the production site
- Contact the site owner if you believe your access was removed in error
If you are seeing 401 on a site that should be public, the site owner may have accidentally enabled HTTP authentication on the wrong path, or a server misconfiguration is requiring authentication where none should be needed.
Quick reference#
| Symptom | Likely cause | Fix |
|---|---|---|
| Browser login dialog on a page that should be public |
auth_basic
in Nginx/Apache on wrong path | Fix server config scope |
| WordPress REST API returns 401 | Missing nonce or expired session | Include
X-WP-Nonce
header, refresh page |
| API call returns 401 after working previously | Token expired or key revoked | Refresh token or generate new key |
| 401 on staging site | Wrong HTTP Basic credentials | Verify
.htpasswd
credentials |
| 401 intermittently | Session timeout or nonce expiration | Re-authenticate, check session lifetime |
| 401 after moving to HTTPS | Secure cookie not sent over HTTP | Fix mixed content, enforce HTTPS |
If the server is rejecting your request based on the HTTP method rather than missing credentials, you may be looking at a 405 Method Not Allowed instead. For other common WordPress errors and their fixes, see How to fix the most common WordPress errors.