The 404 error_page is using $errorsroot variable set by auth_request so that a different error page can be shown based on the user auth. For example if there’s no auth the 404 should be /html/noauth.html otherwise /html/yesauth.html.
It works for all pages that don’t exist except for when someone goes to /auths. It’s internal so I want to treat it like any other missing page if someone accesses it.
If someone goes to /nopage which doesn’t exist, auth_request is done first and the proper error_page will be loaded from root /html$errorsroot. If someone goes to /auths directly how can I do the same thing?
your sample configs seem incomplete; but when I try to adapt them and mock the missing parts, it looks like it all Works For Me? (At least when the /auths subrequest returns the already-auth-or-not status in the response header.)
I think that the important part is that in your samples, $errorsroot is only set for an incoming request that was handled in “location / {}”, but you want it to take effect for all incoming requests. So – have the auth_request and auth_request_set directives at server{} level, outside all location{}s.
then I get the content of html/yesauth.html or html/noauth.html for any 404 response, where the upstream auth response headers include something like
errorsroot: /noauth.html
in its http-200 response.
You will probably want to adjust the @noauth piece to not catch 404; I did not test that side of things.
If you make those two changes, do your tests show what you want to see?
(Note that with auth_request at server{} level, the url is likely to be called multiple times for each client request. That should hopefully be ok if it is a lightweight “is this request already authenticated?” check. It may be messy if it will lead to an authentication challenge to the client each time.)
server {
# Hoist auth to server scope so $errorsroot is
# populated for every request that reaches the ACCESS phase, including
# requests that later internal-redirect into a named error location.
auth_request /auths;
auth_request_set $errorsroot $upstream_http_errorsroot;
# Declare BOTH error_page mappings in the SAME block, since
# error_pages inheritance is whole-array replacement, not a merge.
# Splitting them across server{} and
# location{} silently discards the server-level entry.
#
# Pick ONE target per status code. If 401/403 need @noauth and 404
# needs @errors404, they must be declared together:
error_page 401 403 = @noauth;
error_page 404 @errors404; # NO "=" -- see note below
location / {
# NOTE: do NOT declare any error_page here, or the server-level
# list above is discarded for this location.
try_files $uri/index.html =404;
}
# try_files installs a real content handler and takes an
# explicit variable-expanded path. `root` alone would install no
# handler and would map the ORIGINAL request URI, not $errorsroot
# (ngx_http_core_content_phase() → "no handler found" → 404).
location @errors404 {
root /html;
try_files $errorsroot =404;
}
location @noauth {
root /html;
try_files /noauth.html =404;
}
location = /auths {
internal;
proxy_pass http://your_auth_service;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
}
}
On the /auths-direct-request question specifically: with the above, a direct client request to /auths returns a generic 404 — the error_page 404 = @errors404; mapping does fire, and @errors404is entered — but $errorsroot is empty, so try_files $errorsroot =404; falls through to the =404 fallback and the built-in error page is served. If a deterministic page is required for this case, add an explicit fallback rather than relying on the auth-derived variable:
location @errors404 {
root /html;
# $errorsroot is empty for requests that never reached the ACCESS
# phase (e.g. direct hits on `internal` locations). Fall back
# explicitly rather than to the built-in error page.
try_files $errorsroot /noauth.html =404;
}
This is the closest achievable behaviour to the reporter’s stated goal (“treat it like any other missing page”), and it fails closed to the unauthenticated page — the correct security default, since a request that never passed auth should not be shown the authenticated variant.