
A 401 Unauthorized error means the server refused the request because it carried no valid credentials: the token, API key, session cookie or password was missing, malformed, expired or sent in the wrong scheme. The fix is to send a fresh credential in the format the WWW-Authenticate response header asks for. Refreshing the page or clearing cache alone rarely helps.
This guide goes past the definition: how to read the challenge the server sends back, the exact places a valid token gets lost (Postman variables, redirects, nginx and Apache proxies, CORS preflights), how 401 differs from 403 and 407, and the checks that pin down the cause in a couple of minutes.
What does 401 Unauthorized mean?
Status 401 Unauthorized belongs to the 4xx class of client errors. Per RFC 9110, §15.5.2, it means the request "has not been applied because it lacks valid authentication credentials for the target resource." The name is misleading: despite the word "Unauthorized", this is about authentication — the server could not establish your identity. British sources often spell it "401 unauthorised"; it is the same status code.
The standard requires that a 401 response must include a WWW-Authenticate header with at least one challenge applicable to the resource. This header tells the client which authentication scheme to use. If the request already contained credentials, the 401 means they were rejected.
What an HTTP/1.1 401 Unauthorized response looks like
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api", error="invalid_token", error_description="The access token expired"
Content-Type: application/json
{ "error": "token_expired" }
Read the whole response, not just the status line. The scheme (Bearer), the realm, and the error parameter usually name the cause outright. Many APIs repeat it in the JSON body (token_expired, invalid_api_key, missing_authorization).
The role of the WWW-Authenticate and Authorization headers
HTTP authentication is a dialogue between two headers. First the server replies with 401 and a WWW-Authenticate challenge ("identify yourself this way"). Then the client repeats the request with an Authorization header carrying the credentials in the required scheme.
GET /api/profile HTTP/1.1
Host: example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
If Authorization is missing, wrong, or the token inside it has expired, the server returns 401 again. Browsers react to one scheme on their own: a WWW-Authenticate: Basic challenge makes Chrome, Edge, Firefox and Safari show a native username/password dialog. That is why single-page apps usually answer their own API calls with a Bearer challenge or a custom scheme, so a failed fetch does not pop up a login box.
Bearer error codes: 400, 401 or 403
For OAuth 2.0 bearer tokens, RFC 6750, §3.1 defines three error codes and the status each should use. They tell you whether to refresh the token or fix something else:
| error= value | Status | What it means | What to do |
|---|---|---|---|
invalid_request | 400 | Malformed request: missing parameter, token sent twice | Fix how the request is built |
invalid_token | 401 | Token expired, revoked, malformed or issued for another audience | Refresh or re-issue the token |
insufficient_scope | 403 | Token is valid but lacks the scope the call needs | Request a token with the right scope |
| (no error parameter) | 401 | No credentials were sent at all | Add the Authorization header |
What's the difference between 401 Unauthorized and 403 Forbidden?
These two codes are the most commonly confused. In short: 401 means "I don't know who you are" (an authentication problem), while 403 means "I know who you are, but you can't come in" (an authorization problem). The table below breaks down the differences and adds 407, which looks similar but comes from a proxy, not the site.
| Aspect | 401 Unauthorized | 403 Forbidden | 407 Proxy Authentication Required |
|---|---|---|---|
| Meaning | Authentication failed | Authorization failed | The proxy between you and the site wants credentials |
| Who you are to the server | Identity not established | Identity known (or irrelevant) | Unknown to the proxy |
| When it occurs | Missing/wrong/expired credentials | Insufficient rights for the resource, IP or WAF rule | Corporate or ISP proxy requires login |
| Required header | WWW-Authenticate (mandatory) | Not required | Proxy-Authenticate (mandatory) |
| Credentials go in | Authorization | — | Proxy-Authorization |
| Will logging in help | Yes, re-authenticating may help | No, logging in again won't change rights | Yes, but to the proxy, not the site |
| What to do | Log in again, refresh the token | Request access from an admin | Configure proxy credentials in the OS or client |
A practical rule for API design: return 401 when the caller must authenticate (again), and 403 when authenticating again cannot change the outcome. Some sites deliberately return 404 instead of 403 to hide that a resource exists — that is allowed by the standard.
Authentication schemes: Basic, Bearer, Digest
The WWW-Authenticate header names the scheme. The most common are:
- Basic — username and password encoded in Base64 (HTTPS only!).
Authorization: Basic dXNlcjpwYXNz. Defined in RFC 7617. - Bearer — an access token (usually JWT or OAuth 2.0).
Authorization: Bearer <token> - Digest — a hashed response to the server's challenge; never sends the password in the clear.
- Negotiate / NTLM — Windows integrated authentication on IIS. Here the first one or two 401 responses are a normal part of the handshake, not an error; only a 401 at the end of the exchange is a failure.
- Vendor schemes — some APIs define their own prefix, for example Discord bots send
Authorization: Bot <token>. Sending the right token with the wrong prefix is still a 401.
Basic authentication example
curl -v https://example.com/private \
-H "Authorization: Basic dXNlcjpwYXNzd29yZA=="
# the same, letting curl build the header
curl -v -u user:password https://example.com/private
How do I fix a 401 Unauthorized error?
Work from the cheapest check to the most involved. Stop as soon as one step explains the error.
- Read the challenge. Run the request with
curl -i(or open DevTools → Network) and look atWWW-Authenticateand the response body. The scheme anderrorparameter narrow the cause. - Confirm the header is actually sent. In curl use
-v: lines starting with>are what went out. If there is noAuthorizationline, the problem is on the client, not the server. - Check the scheme and format.
Bearerwith one space, no quotes, no duplicated prefix (Bearer Bearer eyJ...is a classic copy-paste bug), no trailing newline in a token read from a file. - Check expiry. Decode the JWT and compare
expwith the current UTC time. If it has passed, use the refresh token or log in again. - Check that the credential belongs to this environment. A staging key sent to production, a token issued for another
aud, or a revoked or rotated API key all look identical from the client: 401. - Check what sits in between. Redirects, reverse proxies and CDNs can strip the header before the application sees it (see the sections below).
- Check the server's log. The auth layer usually logs the precise reason even when the response is deliberately vague.
How to clear error 401 as a website visitor
If you are not the developer and simply see "401 Unauthorized" in the browser:
- Check the URL — a mistyped path can land in a password-protected area.
- Log out and log in again, so the site issues a new session cookie.
- Clear cookies for that one site (in Chrome: the site-information icon in the address bar → cookies and site data). Clearing the whole browser cache is rarely needed.
- If the site uses a Basic Auth dialog, the browser remembers the credentials you typed for the session. After a password change, close all browser windows or open a private window to get a fresh prompt.
- If it persists, the site owner has restricted the page. Only they can grant access.
Causes of a 401 error and how to fix it
Causes split into client-side and server-side.
- Expired token or session — the most common cause with APIs. Fix: refresh the access token via the refresh token, or log in again.
- Wrong credentials — a typo in the login/password, or a wrong API key. Fix: double-check the credentials.
- Missing Authorization header — the client sent no credentials at all. Fix: add the header.
- Wrong scheme — the server expects Bearer but the client sends Basic. Fix: match the WWW-Authenticate header.
- Server configuration — for example,
auth_basicis enabled in nginx or an API key is required. - API key in the wrong place — the API expects
x-api-keyas a header and you put it in the query string or body (or the other way round). Fix: follow the provider's docs to the letter; "missing API key" responses usually mean this.
Server-side Basic Auth in nginx
location /admin/ {
auth_basic "Restricted Area";
auth_basic_user_file /etc/nginx/.htpasswd;
}
With this configuration, any request to /admin/ without a valid Authorization header gets a 401 and a Basic challenge. The password file is created with htpasswd -c /etc/nginx/.htpasswd admin (from the apache2-utils or httpd-tools package; drop -c when adding a second user, or it overwrites the file). The nginx error log then states the exact reason: no user/password was provided for basic authentication, user "admin" was not found in "/etc/nginx/.htpasswd" or user "admin": password mismatch.
401 behind nginx and Apache: the header that never arrives
A token can be perfectly valid and still produce a 401 because the application never receives it:
- Underscores in header names. nginx silently drops request headers that contain an underscore (
api_key,X_Auth_Token) becauseunderscores_in_headersisoffby default. Rename the header to use hyphens, or enable the directive in theserverblock. - Apache with PHP-FPM or CGI. Apache does not pass
Authorizationto CGI and FastCGI back ends by default, so$_SERVER['HTTP_AUTHORIZATION']is empty and the app answers 401. On Apache 2.4.13+ addCGIPassAuth On; on older setups the usual workaround isRewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]. - A proxy that replaces the header. An nginx block with its own
proxy_set_header Authorization ..., or an upstream protected with Basic Auth, overwrites the client's token. - Redirects. curl drops
Authorizationwhen a redirect leads to a different host (unless you pass--location-trusted), and most HTTP libraries do the same for safety. An API that redirectshttp://tohttps://orapi.example.comtowww.example.comtherefore turns a valid request into a 401. Call the final URL directly.
CORS preflight returning 401
Before a cross-origin request with an Authorization header, the browser sends an OPTIONS preflight — and the preflight never carries credentials. If the server enforces authentication on OPTIONS too, the preflight gets a 401 and the browser reports a CORS error instead of the real call. Let OPTIONS through the auth layer and answer it with the CORS headers; see how CORS works for the full header set.
401 Unauthorized in Postman
Postman hides the headers it sends behind the Authorization tab, which is exactly why 401s there are confusing. Check in this order:
- Auth type on the request. "Inherit auth from parent" uses the collection's or folder's settings; if the parent has "No Auth", nothing is sent.
- Unresolved variables. If
{{token}}is not defined in the selected environment, Postman sends the literal text. Check the environment selector in the top-right corner. - Duplicated prefix. With Auth type "Bearer Token", paste only the token — Postman adds
Beareritself. - A manual header fighting the Authorization tab. Remove one of them.
- The Postman Console (opened from the status bar at the bottom of the window) shows the raw request that actually went out, including headers — the Postman equivalent of
curl -v. - Redirects. If the endpoint redirects to another hostname, the request setting that follows the Authorization header on redirect decides whether the token survives.
Tokens and sessions: why a 401 hits logged-in users
The paradox "I logged in but I get a 401" is explained by the token lifecycle. In modern APIs the access token is deliberately short-lived — from a few minutes to an hour. This limits the damage if the token leaks. When it expires, the server responds with 401, and the client must exchange a long-lived refresh token for a new access token.
POST /oauth/token HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=def502...&client_id=app
A well-built client intercepts the first 401, refreshes the token automatically, and retries the original request — the user never notices the pause. Without this logic, the user sees a sudden logout. That is why handling 401 is a mandatory part of any API client. Two details matter in practice: refresh once and retry once (a second 401 after a refresh means the problem is not expiry, and looping will lock the account or hit rate limits), and serialize refreshes so ten parallel requests do not each burn the refresh token. The same logic applies to mobile apps: "401 unauthorized" on Android or iOS is almost always an expired or revoked token that the app failed to refresh. (The "unauthorized" state in adb devices is unrelated — it means the phone has not yet approved the computer's USB debugging key.)
Common server-side causes of a 401 in APIs
- Key rotation — the old API key was revoked, but the app still sends it.
- Clock skew — a JWT with an
expclaim is seen as expired due to wrong server time. Check withtimedatectlon Linux that the system clock is synchronized; thenbf("not before") claim fails the same way when the issuer's clock runs ahead. - Wrong
realmor audience — the token was issued for a different service. - Revoked session — an administrator forcibly ended the user's session.
- Signing key mismatch — after rotating JWT signing keys, tokens signed with the old key fail verification until the verifier fetches the new key set.
To look inside a token, decode its payload and read exp, nbf, iss and aud; the structure and the verification steps are covered in JWT explained. Never paste production tokens into third-party websites to decode them.
401: Unauthorized on Discord and other platforms
For a Discord bot, "401: Unauthorized" from the API almost always means the bot token was reset in the Developer Portal or is sent without the Bot prefix. For end users of a desktop or web app, the same message usually clears after logging out and back in. On IIS, 401 has substatus codes in the server logs — for example 401.1 (logon failed) and 401.2 (logon failed due to server configuration) — which point at the credential or at the enabled authentication methods respectively.
How to check headers and authentication
To see the response code and the WWW-Authenticate header, use the free HTTP header and response code checker on enterno.io — it instantly shows the status and every response header. To assess the endpoint's overall security posture and the correctness of authentication schemes, our security scanner helps. Checking response headers is especially useful when you need to learn which scheme (Basic, Bearer, Digest) the server expects — that information always lives in the WWW-Authenticate challenge. If a valid token still ends in 401, run the URL through the redirect checker: a hop to another host or from HTTP to HTTPS is a common place where the Authorization header is dropped.
From a terminal, the same checks look like this:
# status line and all response headers, without the body
curl -sI https://api.example.com/v1/me
# full exchange: request headers (>) and response headers (<)
curl -v https://api.example.com/v1/me -H "Authorization: Bearer $TOKEN"
# just the status code, handy in scripts
curl -s -o /dev/null -w "%{http_code}\n" https://api.example.com/v1/me
Note that -I sends a HEAD request; a few APIs handle HEAD differently from GET, so confirm with -v before drawing conclusions.
Related reading
To master response codes, see the HTTP status code reference and the breakdown of the 403 Forbidden error, which is often confused with 401. It also helps to understand how HTTP headers work, since they carry the credentials. For the status code's reference definition, see MDN: 401 Unauthorized.
FAQ
What's the difference between 401 Unauthorized and 403 Forbidden?
401 Unauthorized means authentication failed: the server could not establish your identity because of missing, wrong, or expired credentials. 403 Forbidden means authorization failed: the server knows who you are but you lack sufficient rights. Re-authenticating helps with 401; with 403 only an administrator granting more rights will help.
Why do I get a 401 even though I am clearly logged in?
Most likely your access token or server session has expired. Access tokens are short-lived (minutes), and once they expire the server responds with 401. Refresh the token via the refresh token or log in again. Also verify that the Authorization header is actually being sent and holds a current value — proxies, redirects and CORS preflights can drop it on the way.
Is the WWW-Authenticate header mandatory in a 401 response?
Yes. Per RFC 9110, a server returning 401 must include a WWW-Authenticate header with at least one challenge applicable to the resource. It tells the client which authentication scheme to use — Basic, Bearer, Digest, and so on. A 401 without this header violates the standard.
How do I fix a 401 when calling an API?
Check in order: whether the Authorization header is sent, whether the scheme is correct (Bearer vs Basic), whether the token has expired, and whether the API key is valid. Run the request through curl -v to see the sent headers and the server response, including WWW-Authenticate with the error description.
How to clear error 401 in a browser?
Log out and back in, then clear cookies for that single site. For a Basic Auth prompt, close every browser window or use a private window so the browser forgets the credentials it cached. If the error remains, the page is restricted and only the site owner can grant access.
Is Basic authentication secure?
Basic sends the username and password merely Base64-encoded — that is reversible encoding, not encryption. It is acceptable only over HTTPS, where all traffic is encrypted by TLS. Without HTTPS the credentials are effectively sent in the clear. For APIs, the Bearer scheme with short-lived tokens is preferable.