nginxcustom-error-pagesssi

NGINX error page using SSI include returns incorrect value for variables


When using the include command in SSI in an NGINX config, the $status variable will always return 200 (and any maps using $status also return whatever value is mapped to it) instead of the actual response code.

Interestingly, this does not happen for other variables like $request_id or $remote_addr.

Relevant config

map $status $status_text {
    ...
    200 'OK';
    503 'Service Unavailable';
    ...
}
error_page 502 503 504 /errorpages/503.html;
error_page 401 403 /errorpages/403.html;

location /errorpages/ {
    ssi on;
    root /var/www/;
}
#### 403.html ####
<!--# include file="footer.html" -->

#### footer.html ####
HTTP details: <!--# echo var="status" default="" --> <!--# echo var="status_text" default="" -->

Response when going to a page

Expected response

I would've expected the error page to use the actual response code configured in error_page as well as what is actually returned.

I've attempted to use =502 and = in the error_page configuration, but neither make a difference.


Solution

  • When you use the include SSI instruction, nginx retrieves the contents of the included file using the subrequests internal mechanism.

    Subrequests are primarily used to insert output of one request into another, possibly mixed with other data. A subrequest looks like a normal request, but shares some data with its parent. In particular, all fields related to client input are shared because a subrequest does not receive any other input from the client.

    In your case, when requesting the footer.html file, nginx uses a subrequest with the URI /errorpages/footer.html. By default, subrequests are not logged in the access log. However, for debugging purposes, you can enable their logging using the log_subrequest directive.

    Nginx variables exist only within the request processing context. Some variables, like $request_id, $remote_addr, and even $request_uri, are shared between the main request and all subrequests. However, $uri variable, response-related variables (including the $status) and user-defined variables are unique to each subrequest.

    Sharing such variables between subrequests and the main request can be a non-trivial task. For example, the widely used auth_request directive, which utilizes subrequest results for access control, has a dedicated auth_request_set companion directive for this purpose.

    Fortunately, you have an option to do the same using SSI engine internal variables:

    #### 403.html ####
    <!--# set var="result" value="$status" --><!--# set var="result_text" value="$status_text" -->
    <!--# include file="footer.html" -->
    
    #### footer.html ####
    HTTP details: <!--# echo var="result" default="" --> <!--# echo var="result_text" default="" -->
    

    If you want to use the result SSI variable only when it is defined and fall back to the status variable otherwise, you can achieve this using SSI conditional expressions:

    #### footer.html ####
    <!--# if expr="$result" -->
      <!-- result variable is defined and not empty, do nothing -->
    <!--# else -->
      <!--# set var="result" value="$status" -->
      <!--# set var="result_text" value="$status_text" -->
    <!--# endif -->
    HTTP details: <!--# echo var="result" default="" --> <!--# echo var="result_text" default="" -->