Some Differences Between Apache and Nginx SSIs

See also:

While I noticed that Nginx's SSI support fails to include some variables that Apache includes (notably LAST_MODIFIED and fsize, see my previous blog posts), something I failed to absorb in my first run-in with this is that Nginx seems to support nearly all of its own "Embedded Variables" in SSI. This appears to include: BINARY_REMOTE_ADDR, CONNECTION, CONNECTION_REQUESTS, DOCUMENT_ROOT, HOST, HOSTNAME, IS_ARGS, LIMIT_RATE, MSEC, NGINX_VERSION, PID, PIPE, PROXY_PROTOCOL_ADDR, PROXY_PROTOCOL_PORT, QUERY_STRING, REALPATH_ROOT, REMOTE_ADDR, REMOTE_PORT, REMOTE_USER, REQUEST, REQUEST_COMPLETION, REQUEST_FILENAME, REQUEST_ID, REQUEST_LENGTH, REQUEST_METHOD, REQUEST_TIME, REQUEST_URI, SCHEME, SERVER_ADDR, SERVER_NAME, SERVER_PORT, SERVER_PROTOCOL, STATUS, TIME_ISO8601, TIME_LOCAL, URI. The following four are available dependent on the configuration of your server: TCPINFO_RTT, TCPINFO_RTTVAR, TCPINFO_SND_CWND, TCPINFO_RCV_SPACE.

[2019-07-11: I've posted a thorough list of variables across both servers here, with better explanations.]

Note that I've included pretty much everything that didn't return "(none)" (indicating the variable wasn't defined). This includes a couple variables that returned an empty string - I'm taking this to indicate that under the right circumstances, they would be filled. I also left in a couple that returned "(none)" as they (QUERY_STRING, REMOTE_USER) fall into the same category. But it's up to you to experiment further. I'll try to update this entry if I have further discoveries. And I'm not saying you want to use all of these: publishing the real paths to your files on your current server could be argued to be a security risk.

I've also included the test (HTML) code for all their variables at the end of this post.

Let's get an example associated with my last blog post. In my last blog post I showed one way to distinguish between Apache and Nginx SSI, and based on that decided on the footer file to include. Here's the old file:

<hr style="clear:both;" />
<span class="footer">
<!--#set var="hstart" value="https://" -->
<!--#echo var="hstart"--><!--#echo var="HTTP_HOST"--><!--#echo var="DOCUMENT_URI" -->
(<!--#fsize virtual="$DOCUMENT_URI"-->b)
<br />
<!--#config timefmt="%Y-%m-%d" -->
Last modified <!--#echo var="LAST_MODIFIED" -->
by <a href="/giles/contact.html">giles</a>&nbsp;

Creating something similar (but not the same!) in Nginx:

<hr style="clear:both;" />
<span class="footer">

<!-- set scheme -->
<!--#if expr="$HTTPS" -->
    <!--#set var="hstart" value="https://" -->
<!--#else -->
    <!--#set var="hstart" value="http://" -->
<!--#endif -->

<!--#echo var="hstart" --><!--#echo var="HTTP_HOST" --><!--#echo var="REQUEST_URI" -->&nbsp;
<br />

<!--#set var="lastmodtext" value="Last modified: " -->

<!--#if expr="$GO_LAST_MODIFIED" -->
    <!--#echo var="lastmodtext" --><!--#echo var="GO_LAST_MODIFIED" -->
<!--#endif -->

by <a href="/giles/contact.html">giles</a>&nbsp;

The Apache include has a hard-coded value of "https://" - this is because my best solution to the problem for Apache goes like this:

<!--#if expr='v("SERVER_PORT") = "81"' -->
    <!--#set var="hstart" value="https://" -->
<!--#else -->
    <!--#set var="hstart" value="http://" -->
<!--#endif -->

This works on my local Apache 2.4.x, but not on my hosting site. Even if it did work, I'm not happy with using port numbers to assume whether or not encryption is in play: it's not too hard to change Apache to any non-standard port, and then how do you detect if it's encrypted or not? Since my sites are encrypted hard-coding the value ... should be okay. Since I'm headed to Nginx, I'm not going to spend more time to sort out this minor Apache problem.

I'm assuming that my problem with my hosting site's Apache is that they've set SSILegacyExprParser to "on". All they're doing is making the problem worse, delaying a changeover of SSI parser behaviour for all their hosted sites ... And why doesn't the setting have a "both" instead of just "on" and "off"?

Likewise, this worked locally:

<!--#if expr="-z v('GO_LAST_MODIFIED')" -->
    Last modified <!--#echo var="LAST_MODIFIED" -->
<!--#else -->
    Last modified <!--#echo var="GO_LAST_MODIFIED" -->
<!--#endif -->

But not remotely, presumably for the same reason. It uses GO_LAST_MODIFIED if it's defined, and only uses LAST_MODIFIED if it can't find GO_LAST_MODIFIED.

The Nginx version doesn't have the file size (one of the things Nginx doesn't support), and also has to use my own variable for the last-modified date. See LAST_MODIFIED, SSIs, and sed for how I'm hoping to make that maintainable. Notice that I didn't include a timefmt string either - because that only applies to the built-in LAST_MODIFIED, I don't know how to make it apply to GO_LAST_MODIFIED or if that's even possible.

Another annoying difference is the behaviour of $DOCUMENT_URI: in Apache, the resulting URI is for the parent document. Nginx is much more literal-minded: the resulting URI is for the current include file, not the parent document that called the include. Thus my use with Nginx of $REQUEST_URI (the value given by the user when they typed in or click on a link).

Test HTML Code

Paste this code into an HTML page on an Nginx server with SSI turned on to see for yourself which of the Nginx Embedded Variables are available - and useful - to you:

    <li>ARGS: <!--#echo var="ARGS" --></li>
    <li>BINARY_REMOTE_ADDR: <!--#echo var="BINARY_REMOTE_ADDR" --></li>
    <li>BODY_BYTES_SENT: <!--#echo var="BODY_BYTES_SENT" --></li>
    <li>BYTES_SENT: <!--#echo var="BYTES_SENT" --></li>
    <li>CONNECTION: <!--#echo var="CONNECTION" --></li>
    <li>CONNECTION_REQUESTS: <!--#echo var="CONNECTION_REQUESTS" --></li>
    <li>CONTENT_LENGTH: <!--#echo var="CONTENT_LENGTH" --></li>
    <li>CONTENT_TYPE: <!--#echo var="CONTENT_TYPE" --></li>
    <li>DOCUMENT_ROOT: <!--#echo var="DOCUMENT_ROOT" --></li>
    <li>HOST: <!--#echo var="HOST" --></li>
    <li>HOSTNAME: <!--#echo var="HOSTNAME" --></li>
    <li>IS_ARGS: <!--#echo var="IS_ARGS" --></li>
    <li>LIMIT_RATE: <!--#echo var="LIMIT_RATE" --></li>
    <li>MSEC: <!--#echo var="MSEC" --></li>
    <li>NGINX_VERSION: <!--#echo var="NGINX_VERSION" --></li>
    <li>PID: <!--#echo var="PID" --></li>
    <li>PIPE: <!--#echo var="PIPE" --></li>
    <li>PROXY_PROTOCOL_ADDR: <!--#echo var="PROXY_PROTOCOL_ADDR" --></li>
    <li>PROXY_PROTOCOL_PORT: <!--#echo var="PROXY_PROTOCOL_PORT" --></li>
    <li>QUERY_STRING: <!--#echo var="QUERY_STRING" --></li>
    <li>REALPATH_ROOT: <!--#echo var="REALPATH_ROOT" --></li>
    <li>REMOTE_ADDR: <!--#echo var="REMOTE_ADDR" --></li>
    <li>REMOTE_PORT: <!--#echo var="REMOTE_PORT" --></li>
    <li>REMOTE_USER: <!--#echo var="REMOTE_USER" --></li>
    <li>REQUEST: <!--#echo var="REQUEST" --></li>
    <li>REQUEST_BODY: <!--#echo var="REQUEST_BODY" --></li>
    <li>REQUEST_BODY_FILE: <!--#echo var="REQUEST_BODY_FILE" --></li>
    <li>REQUEST_COMPLETION: <!--#echo var="REQUEST_COMPLETION" --></li>
    <li>REQUEST_FILENAME: <!--#echo var="REQUEST_FILENAME" --></li>
    <li>REQUEST_ID: <!--#echo var="REQUEST_ID" --></li>
    <li>REQUEST_LENGTH: <!--#echo var="REQUEST_LENGTH" --></li>
    <li>REQUEST_METHOD: <!--#echo var="REQUEST_METHOD" --></li>
    <li>REQUEST_TIME: <!--#echo var="REQUEST_TIME" --></li>
    <li>REQUEST_URI: <!--#echo var="REQUEST_URI" --></li>
    <li>SCHEME: <!--#echo var="SCHEME" --></li>
    <li>SERVER_ADDR: <!--#echo var="SERVER_ADDR" --></li>
    <li>SERVER_NAME: <!--#echo var="SERVER_NAME" --></li>
    <li>SERVER_PORT: <!--#echo var="SERVER_PORT" --></li>
    <li>SERVER_PROTOCOL: <!--#echo var="SERVER_PROTOCOL" --></li>
    <li>STATUS: <!--#echo var="STATUS" --></li>
    <li>TCPINFO_RTT: <!--#echo var="TCPINFO_RTT" --></li>
    <li>TCPINFO_RTTVAR: <!--#echo var="TCPINFO_RTTVAR" --></li>
    <li>TCPINFO_SND_CWND: <!--#echo var="TCPINFO_SND_CWND" --></li>
    <li>TCPINFO_RCV_SPACE: <!--#echo var="TCPINFO_RCV_SPACE" --></li>
    <li>TIME_ISO8601: <!--#echo var="TIME_ISO8601" --></li>
    <li>TIME_LOCAL: <!--#echo var="TIME_LOCAL" --></li>
    <li>URI: <!--#echo var="URI" --></li>