401 Unauthorized Error: Causes and How to Fix It
A 401 Unauthorized error means the server never accepted your credentials in the first place - it's not blocking you because of permissions, it's blocking you because it doesn't know who you are yet. That distinction matters, because the fix for a 401 is almost never the same as the fix for a 403. Here's how to tell which cause you're dealing with and clear it fast.
401 vs 403: get this right first
A lot of time gets wasted chasing file permissions when the real problem is authentication. Quick gut-check:
- 401 Unauthorized - the server needs credentials (a username/password, an API key, a token) and either got none or got ones it can't validate.
- 403 Forbidden - the server knows who you are and has decided you're not allowed in anyway. That's a permissions or rule problem, not a login problem.
If you're being asked to log in again, or an API call is rejecting a key that used to work, you're in 401 territory. Let's fix that.
Where 401 actually shows up
It rarely looks the same twice. In practice you'll hit one of these:
- A browser popup titled "Sign in" or "Authentication Required" that keeps rejecting the right password
- A REST API call (WooCommerce, a custom integration, Zapier, a mobile app) returning
{"code":"rest_not_logged_in"}or a plain401status - Webmail or a mail client (Outlook, Thunderbird) suddenly refusing a password that worked yesterday
- A WHM/cPanel API token call failing with
Unauthorizedfrom a script or backup job - A reverse-proxied app behind Nginx or Cloudflare that works locally but 401s once traffic passes through the proxy
Cause 1: Directory Privacy (.htpasswd) protecting a folder
If you password-protected a directory in cPanel's Directory Privacy tool, Apache serves an HTTP Basic Auth prompt for anything under that path. A 401 here usually means one of three things:
- The password was reset in cPanel but the browser is still sending a cached, stale credential
- The
.htpasswdfile path in.htaccessdoesn't match where cPanel actually wrote it - A CDN or caching layer in front of the site is stripping the
Authorizationheader before it reaches Apache
Fix: confirm the auth block in .htaccess points to the real, absolute path:
AuthType Basic
AuthName "Restricted Area"
AuthUserFile /home/username/.htpasswds/public_html/protected/passwd
Require valid-user
Clear the browser's cached credentials for the site (or test in a private window), and re-enter the password cPanel is currently showing under Directory Privacy → the protected folder.
Cause 2: Expired, revoked, or mistyped API keys
This is the most common 401 for anything automated - WooCommerce REST integrations, WordPress Application Passwords, third-party plugins calling an external service, or your own scripts hitting an API.
- WooCommerce REST API: keys are tied to a specific WordPress user. If that user's password changes or the account gets deactivated, the key stops working even though it still "exists" under WooCommerce → Settings → Advanced → REST API.
- WordPress Application Passwords: these get revoked automatically if the site URL changes, SSL is toggled, or the user account is deleted. Regenerate them under Users → Profile → Application Passwords rather than reusing an old one.
- WHM/cPanel API tokens: tokens are scoped to the account that created them and can be individually revoked from WHM → Manage API Tokens. A 401 from an automation script almost always means the token was revoked, expired, or copied with a trailing space/newline.
Fix: regenerate the key or token, copy it without surrounding whitespace, and test it directly with curl before touching the plugin or script again:
curl -u consumer_key:consumer_secret https://yourdomain.com/wp-json/wc/v3/products
If that curl call also 401s, the key itself is bad - stop debugging the plugin and fix the credential first.
Cause 3: A proxy or CDN is stripping the Authorization header
Basic Auth and Bearer tokens both travel in the Authorization HTTP header. Some reverse proxies and CDNs don't forward it by default, which means the origin server never even sees the credential - it just sees an empty request and returns 401.
If you're running Nginx as a reverse proxy in front of an app, check the proxy block for a stray line that clears headers:
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Authorization $http_authorization;
proxy_pass_header Authorization;
}
Without proxy_set_header Authorization $http_authorization;, some Nginx configs (particularly ones using fastcgi_pass with PHP-FPM) drop the header silently. On Apache/PHP-FPM setups, the same symptom shows up when mod_php vs php-fpm handling changes and PHP stops seeing HTTP_AUTHORIZATION in $_SERVER at all - add this to .htaccess as a workaround:
RewriteEngine On
RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule .* - [E=HTTP_AUTHORIZATION:%1]
Cause 4: Webmail or mail client rejecting a correct password
If Outlook, Thunderbird, or Roundcube suddenly throws a 401-style "authentication failed" for an email account, check these in order:
- Was the mailbox password changed recently in cPanel → Email Accounts, but the mail client still has the old one saved?
- Is the account locked out by cPHulk or a similar brute-force protection after several failed attempts from a stale saved password? Check WHM → cPHulk Brute Force Protection → History and whitelist your IP if needed.
- Does the client still use the deprecated username format (just
userinstead ofuser@domain.com)? Most modern mail servers require the full email address as the login.
Cause 5: Clock drift breaking token-based auth
JWTs, OAuth tokens, and some API signature schemes are time-sensitive - if the server's clock has drifted more than a minute or two from real time, tokens that should still be valid get rejected as expired or "not yet valid," which many APIs report as a plain 401.
On a VPS, check drift with:
timedatectl status
If System clock synchronized shows no, enable NTP:
sudo timedatectl set-ntp true
Symptom-to-cause quick reference
| Where you see the 401 | Most likely cause |
|---|---|
| Browser Basic Auth popup won't accept the right password | Cached credentials or wrong .htpasswd path |
| WooCommerce/WordPress REST API call fails | Revoked or mismatched API key/Application Password |
| Works locally, fails through Nginx/Cloudflare | Authorization header stripped by the proxy |
| Webmail or Outlook suddenly rejects a known-good password | Password changed, or cPHulk lockout |
| API token/JWT rejected as expired when it shouldn't be | Server clock drift |
Prevention checklist
- Store API keys and tokens in a password manager, not in scripts or chat history, so you're not guessing which one is current
- Set a reminder to rotate long-lived tokens (WHM API tokens, Application Passwords) instead of letting them run indefinitely
- When adding a CDN or reverse proxy in front of an authenticated app, test Basic Auth and Bearer token requests through it before going live
- Enable NTP on every VPS so token expiry checks aren't fighting clock drift
If none of the above matches what you're seeing, pull the exact response body - not just the status code - since many APIs (WooCommerce, WordPress REST, WHM) include a specific error message alongside the 401 that names the exact rejected credential.
Frequently asked questions
What's the difference between a 401 and a 403 error?
A 401 Unauthorized means the server doesn't recognize your credentials at all - no login, an expired token, or a bad API key. A 403 Forbidden means the server knows exactly who you are and has decided you're not allowed access anyway. Fixing a 401 means fixing authentication; fixing a 403 means fixing permissions or access rules.
Why does my WooCommerce REST API key suddenly return 401?
WooCommerce REST API keys are tied to a specific WordPress user account. If that user's password is changed, the account is deactivated, or the key was revoked under WooCommerce > Settings > Advanced > REST API, every request using that key will 401 even though the key still appears to exist in the list.
I know my webmail password is correct - why is it still rejected?
Check whether the mailbox is temporarily locked by cPHulk Brute Force Protection after repeated failed attempts from a stale saved password in your mail client. Also confirm you're using the full email address as the username, not just the part before the @ symbol.
Can a CDN or reverse proxy actually cause a 401 error?
Yes. Basic Auth and Bearer tokens travel in the Authorization HTTP header, and some reverse proxies and CDNs don't forward that header to the origin server by default. The origin then sees a request with no credentials at all and correctly returns 401, even though the client sent one.
Why would a valid API token get rejected as expired?
Token-based auth schemes like JWT and OAuth are time-sensitive. If your server's clock has drifted from real time, tokens can be rejected as expired or not-yet-valid. Run timedatectl status on a VPS to check for drift and enable NTP with sudo timedatectl set-ntp true if it's out of sync.