Quick answer
A 401 Unauthorized error means the server refused the request because it carried no valid authentication credentials: no login at all, a wrong password, or an expired or invalid token. The server must say how to authenticate in a WWW-Authenticate header. Visitors usually fix it by signing in again or clearing the site's cookies. Developers should check the token, and whether a proxy, redirect or cache is dropping the Authorization header or replaying an old 401.
01What does a 401 error mean?
RFC 9110 defines 401 as a request that "has not been applied because it lacks valid authentication credentials for the target resource". Despite the name, it is about authentication, who you are, not authorisation, what you may do. If the request did include credentials, the 401 means they were refused.
The same section adds a hard rule: the server generating a 401 MUST send a WWW-Authenticate header with at least one challenge, which names the scheme the client should use, such as Basic or Bearer. A 401 without that header breaks the specification and leaves clients guessing, so it is the first thing to check on an API that returns one.
A sudden wave of 401s on a login form or API from many addresses has a different meaning: it is the typical footprint of credential stuffing, where stolen username and password pairs are tried in bulk.
02401 vs 403 vs 407: which code applies?
The three codes look alike but ask different parties for different things. For OAuth bearer tokens, RFC 6750 maps its error values onto them: invalid_token goes with 401, insufficient_scope with 403, and a malformed request (invalid_request) with 400 Bad Request.
| Situation | Status | Required header | What the client should do |
|---|---|---|---|
| No credentials sent to a protected resource | 401 Unauthorized | WWW-Authenticate | Send credentials in the Authorization header |
| Credentials sent but wrong, expired or revoked | 401 Unauthorized | WWW-Authenticate, e.g. error="invalid_token" | Sign in again or refresh the token |
| Valid credentials, but not allowed this resource | 403 Forbidden | None (Bearer may add error="insufficient_scope") | Do not repeat with the same credentials |
| Refused for reasons unrelated to credentials (IP, rule, region) | 403 Forbidden | None | Read the page; contact the site |
| A forward proxy on your network wants a login | 407 Proxy Authentication Required | Proxy-Authenticate | Send Proxy-Authorization to that proxy |
| The server wants to hide that the resource exists | 404 Not Found | None | Nothing to fix from the client side |
03What does the WWW-Authenticate header tell you?
The challenge names the scheme, and the scheme tells you what kind of credential the server expects. A Basic challenge makes browsers show their own sign-in prompt; a Bearer challenge is addressed to software, so a browser just displays the page.
Basic is encoding, not encryption: the header in the trace below, dXNlcjpwYXNzd2Q=, is simply user:passwd in base64. RFC 7617 states plainly that Basic is not a secure method unless the connection is protected by TLS.
| Scheme | Defined in | Example challenge | Test with curl |
|---|---|---|---|
| Basic | RFC 7617 | Basic realm="Staging" | curl -u user:password URL |
| Bearer (OAuth 2.0) | RFC 6750 | Bearer realm="api", error="invalid_token" | curl --oauth2-bearer TOKEN URL |
| Digest | RFC 7616 | Digest realm=..., qop="auth", algorithm=SHA-256, nonce=... | curl --digest -u user:password URL |
| Negotiate (Kerberos, SPNEGO) | RFC 4559 | Negotiate | curl --negotiate -u : URL (needs a Kerberos ticket) |
> GET /basic-auth/user/passwd HTTP/1.1
< HTTP/1.1 401 UNAUTHORIZED
< WWW-Authenticate: Basic realm="Fake Realm"
> Authorization: Basic dXNlcjp3cm9uZw==
< HTTP/1.1 401 UNAUTHORIZED
< WWW-Authenticate: Basic realm="Fake Realm"
> Authorization: Basic dXNlcjpwYXNzd2Q=
< HTTP/1.1 200 OK04Which layer returned the 401?
Three layers commonly send a 401: the application (a login session or API token check), the web server (password protection such as nginx auth_basic or an Apache .htpasswd), or an authentication proxy or CDN feature in front of both. The page usually gives it away. nginx's built-in page is titled "401 Authorization Required" and arrives with a Basic challenge, while an API answers in JSON with a Bearer challenge.
If the error appears only through the public hostname, compare it with a request straight to the origin using curl --resolve, as described in the status code reference. A 401 that disappears when the request goes straight to the origin points at a reverse proxy or CDN that changed the request on the way.
05Why a 401 appears only behind a proxy or after a redirect
If the credentials are right and the server still says 401, check whether the Authorization header reaches it at all. Three common places lose it.
- The CDN strips it: Amazon CloudFront removes the Authorization header from GET and HEAD requests before forwarding them, unless the distribution is configured to forward it
- The web server hides it from the application: Apache keeps Authorization away from CGI and FastCGI scripts, including PHP-FPM behind mod_proxy_fcgi, unless CGIPassAuth On is set (Apache 2.4.13 and later)
- A redirect drops it: under the Fetch Standard, browsers remove Authorization as soon as a redirect leads to another origin, and curl sends credentials only to the host you first asked unless you use --location-trusted
> GET /redirect-to?url=https%3A%2F%2Fexample.com%2F HTTP/1.1
> Host: httpbin.org
> Authorization: Basic dXNlcjpwYXNzd2Q=
< HTTP/1.1 302 FOUND
< Location: https://example.com/
> GET / HTTP/1.1
> Host: example.com
(no Authorization header on the second request)06Can a CDN or proxy cache a 401?
Only when its response headers or a cache rule allow it. A 401 is not cacheable by default, so a cache that follows the standard does not store one on its own. Separately, RFC 9111 says a shared cache must not reuse a response to a request that carried an Authorization header for any later request, unless that response explicitly allows shared caching with public, s-maxage or must-revalidate.
Such rules exist. nginx's proxy cache does not consider the Authorization header unless told to, and proxy_cache_valid any caches every status code. In a test on nginx 1.22.1, a 401 cached from an anonymous request was then served to a request with a valid token, and a private 200 cached from an authenticated request was served to an anonymous one. The configuration below fixed both.
# create /var/cache/nginx first if it does not exist; nginx makes only the last level
proxy_cache_path /var/cache/nginx/edge keys_zone=edge:10m;
server {
listen 80;
server_name app.example.com;
location /api/ {
proxy_pass http://127.0.0.1:8090;
proxy_cache edge;
# risky: "proxy_cache_valid any 1m;" would also cache 401, 403 and 500
proxy_cache_valid 200 301 302 1m;
proxy_cache_bypass $http_authorization; # never answer these from cache
proxy_no_cache $http_authorization; # never store their responses
}
}07401 troubleshooting matrix
| Symptom | Who sent it | Likely cause | Test | Fix |
|---|---|---|---|---|
| Browser keeps showing a password prompt | Web server | Wrong user or hash in the password file, or wrong realm | curl -u user:password URL | Recreate the entry with htpasswd |
| API answers 401 with error="invalid_token" | Application | Token expired or revoked | Decode the token and read its exp claim | Refresh or reissue the token; check server clocks |
| 401 only through the CDN | Origin, without the header | Edge strips Authorization | Same request with curl --resolve to the origin | Forward Authorization in the CDN's origin request settings |
| 401 only for PHP on Apache | Application | Authorization hidden from scripts | Log the received headers | CGIPassAuth On |
| 401 after a redirect | Application on the new host | Header dropped on a cross-origin redirect | curl -v -L and compare requests | Call the final URL directly |
| Signed-in users see 401, or strangers see private data | Cache | 401 or private response cached | Age or cache-status headers | Bypass and skip the cache for Authorization |
08How to fix a 401 error as a visitor
Most 401s that people meet in a browser are expired sessions. Work through these in order:
- Check the URL: an old bookmark can point at an admin or staging area that needs separate credentials
- Sign out and in again, so the site issues a fresh session cookie
- Clear the cookies for that one site, or try a private window to see whether stored data is the cause
- If a browser password prompt appears, the browser remembers what you typed for the rest of the session; after a password change, close the browser completely before trying again
- If the site keeps refusing a password you know is right, ask its operator; repeated attempts can trigger lockouts or rate limits
Questions
Frequently asked questions
Sources
Sources and verification
nginx 1.22.1 configurations (Basic authentication, a Bearer stand-in, and a caching proxy with and without the Authorization bypass) were run in an isolated network namespace and exercised with curl 7.88.1; the scheme and redirect traces were captured with curl 7.88.1 against httpbin.org. Debian 12, October 2026.
- 01RFC 9110, section 15.5.2: 401 Unauthorized · RFC Editor
- 02RFC 9110, section 11.6.1: WWW-Authenticate · RFC Editor
- 03RFC 9110, section 15.5.4: 403 Forbidden, and 15.5.8: 407 · RFC Editor
- 04RFC 7617, The Basic HTTP Authentication Scheme · RFC Editor
- 05RFC 6750, OAuth 2.0 Bearer Token Usage, section 3.1: Error Codes · RFC Editor
- 06RFC 7616, HTTP Digest Access Authentication · RFC Editor
- 07RFC 4559, SPNEGO-based Kerberos and NTLM HTTP Authentication · RFC Editor
- 08RFC 9111, section 3.5: Storing Responses to Authenticated Requests · RFC Editor
- 09Fetch Standard, section 4.5: HTTP-redirect fetch · WHATWG
- 10curl man page: --location and --location-trusted · curl
- 11Apache core: CGIPassAuth directive · Apache HTTP Server
- 12Request and response behaviour for custom origins (Authorization header) · AWS
- 13Module ngx_http_proxy_module: proxy_cache_valid, proxy_cache_bypass, proxy_no_cache · nginx.org
- 14nginx development guide: HTTP phases · nginx.org
Last updated