Keep API 401 Responses with oauth2-proxy and Nginx

Symptoms and Exact Error

In an oauth2-proxy and Nginx auth_request setup, an unauthenticated API request can receive a 302 sign-in redirect or HTML instead of a plain 401. That behavior is useful for browser pages, but JSON clients may surface it as Unexpected token '<', a CORS failure, or an endless sign-in retry.

auth_request allows the main request to pass if the specified subrequest returns a 2xx status, but denies access if it returns a 401 or 403. The oauth2-proxy /oauth2/auth endpoint is also designed to return 202 on successful authentication and 401 on failure. The issue lies in the fact that Nginx’s common error_page or login redirection rules are applied after this 401, converting API requests into browser flows.

Another distinction to make is between a 401 from the authentication subrequest and a 401 returned by the actual API upstream due to business logic. The former comes from oauth2-proxy, while the latter comes from the application after authentication has passed. If you treat them as having the same cause based solely on the status code, you may intercept legitimate API responses and redirect them to a login page.

Values to Check First

CheckpointsCommand or ConfigurationCriteria
Unauthenticated API Responsecurl -i https://example.com/api/healthIf 401 and JSON are required, but 302, Location, and HTML are returned, the login processing rules are being applied to the API.
Authentication Subrequestcurl -i https://example.com/oauth2/authVerify that it returns 401 when no session exists and 202 when a valid session cookie is present.
Final Nginx Configurationnginx -TCheck which location /api/ matches and which error_page it inherits.
Subrequest Bodyproxy_pass_request_body offVerify that body forwarding is disabled and Content-Length is cleared in the /oauth2/auth location.
upstream 401API Logs for Authenticated RequestsIf oauth2-proxy returns 202 but the API returns 401, it is an application permission issue rather than an authentication gateway issue.

If the configuration does not directly expose /oauth2/auth to the outside, call the corresponding oauth2-proxy endpoint from Nginx or within the same network. Do not leave test cookies or tokens in command history or shared logs. Since the nginx -T output may contain sensitive headers or upstream information, verify it locally only.

Cause Branches

oauth2-proxy Nginx auth_request 401 paths

  • /oauth2/auth returns 401 without a session: This is a normal authentication failure contract. The next check point is whether the API location maintains this response as a 401.
  • /oauth2/auth returns 404 or 5xx: Fix the oauth2-proxy upstream address, proxy_pass, and path before the redirection policy. auth_request does not correctly handle responses other than 401 or 403 as authentication errors.
  • Location header exists in the API response: Check if rules of the error_page 401 and auth-signin nature from the parent server or common include are also applied to the API location.
  • Unauthenticated requests return 401, but authenticated requests also return 401: This occurs if the oauth2-proxy session cookie is not transmitted, or if authentication passed with a 202 but the API’s own permission check returned a 401. Match the request ID or timestamp between the oauth2-proxy logs and the API logs.
  • 401 body is only HTML: The status code may be preserved, but the default Nginx error page might be served. If JSON is required by the API contract, create a minimum JSON response in an API-specific named location.

Fix Sequence

  1. Backup and expand the current configuration. Store the configuration where includes are merged using umask 077; nginx -T > /PRIVATE_BACKUP_DIR/nginx-before-auth-split.txt. Record the oauth2-proxy version and execution options, but mask the client secret and cookie secret.
  2. Align the authentication sub-request location with the official pattern. In location = /oauth2/auth, set the headers to identify the original request and forward to oauth2-proxy with proxy_pass_request_body off; and proxy_set_header Content-Length "";. The purpose of this location is not to render the login screen but to return 202 or 401.
  3. Separate the browser and API locations. While you can maintain the rule that sends 401 to login in the existing UI location, place a separate auth_request /oauth2/auth; and API-specific 401 handling within location /api/. Ensure that the more specific /api/ matches before the common location.
  4. Specify the API 401 destination. Use error_page 401 =401 @api_unauthorized; with its named location in the same server, as tested below, and verify actual responses.
  5. Verify upstream error interception. If the API must pass through the 401 body it returns itself, avoid using broad proxy_intercept_errors on; in the API location, and distinguish between 401s generated from authentication sub-requests and upstream 401s in the logs.
  6. Reload after syntax check. Only after nginx -t succeeds, execute nginx -s reload or the standard reload command for the deployment environment. If using containers, follow the execution method and PID 1 configuration provided by the image.

Check syntax and actual responses separately

Do not use the previously suggested error_page 401 =401; as a 401-preservation example. An independent reviewer supplied an Nginx 1.26.3 experiment in which syntax validation succeeded but requests returned 302 with Location: =401. A separate development-host test with Nginx 1.28.3 on 2026-09-13 reproduced the same result. Both are isolated comparisons, not incidents on the production site.

The official syntax, error_page code ... [=[response]] uri;, separates the response override from the destination URI. Explicitly name the handler for the API JSON 401. This fragment does not replace the existing authentication, cookies, or backend configuration.

# Inside the existing API location:
error_page 401 =401 @api_unauthorized;

# Separate named location in the same server:
location @api_unauthorized {
    default_type application/json;
    return 401 '{"error":"unauthorized"}';
}

The following results used a mock authentication endpoint. Actual OAuth providers, expired cookies, session refresh, split cookies, and WWW-Authenticate preservation were not tested. Never copy the mock allow/deny headers into production authentication logic.

CaseStatusLocationBody
bad example302=401HTML
named location401noneJSON
auth 401401noneJSON
browser auth 401302/loginHTML
auth allow, backend 200200noneJSON
auth 403403noneHTML
auth 500500noneHTML
auth allow, backend 401401nonebackend JSON

Converting every 403 or authentication-server error to 401 hides the cause. With proxy_intercept_errors off;, this configuration preserved the backend 401 body. That does not establish JSON responses for 403/500 or a real login round trip.

Nginx error_page · auth_request

Verification and Rollback

Unauthenticated API calls must return a 401 at curl -i https://example.com/api/health and must not contain the Location header or HTML login documentation. When the same user opens a browser screen path, the existing login flow must be maintained. Location separation is correctly implemented only if both tests pass.

  • Verify that API requests using a valid session cookie reach the actual upstream response behind oauth2-proxy 202.
  • Compare whether expired or missing sessions result in a 401 in the API and the intended login redirect in the browser screen.
  • Log both the auth subrequest status and the final request status in the Nginx access log to identify the layer where the 401 occurs.
  • Test with an account lacking permissions to ensure that 401/403 bodies generated natively by the API are not unnecessarily replaced by a common login response.

If authenticated requests are blocked or browser login breaks after changes, check the differences between nginx-before-auth-split.txt and the actual configuration file to roll back only the API location changes. Pass nginx -t again, reload, and verify that the oauth2-proxy session store or cookie secret was not changed simultaneously.

Official Documentation and Verification Criteria

Earlier verification notes preserved from this article

Written by: Nole IT Operator

Verification level: Checked against official documentation

This article was reviewed against the documented endpoint and Nginx module behavior available on the publication date. Test browser and API routes separately before reloading a production proxy.

Editorial and revision policy · Report an error