Migrating Apache Server-Side Includes to Nginx

Follow-up: Found a whole lot more variables

Lead-off caveat: this is written mid-2016 using Nginx 1.10.0 on Ubuntu.

I'm planning to migrate my website from Apache to Nginx, which is more lightweight, faster, and I work with it all the time at work. But I makes heavy use of SSIs, Server-Side Includes on my old-fashioned static website - not only that, they're embedded in ".html" files rather than the more official ".shtml" files. SSI is an old but still very useful technology that allow the web server to do substitutions as it serves the web pages. Turn it on for Nginx with a directive in the location section (or http or server):

location / {
    ssi on;
    # other directives here
}

Nginx's default behaviour is to parse all HTML files, so this works with my ".html" files without problems. The commonest use is the include statement, which allows you to say something like this:

<!--#include virtual="/footers/main.inc" -->

so all of the main.inc file from the /footers/ folder in the root of your webserver will be included into the current file. Notice that it's formatted like an HTML comment, so that if the SSI engine isn't working it's treated as an HTML comment.

Not surprisingly, there are some differences between the way Apache handles SSIs and the way Nginx handles them. The first I discovered was that Apache doesn't care what kind of file you include - its MIME type, it's extension, doesn't matter. But if you expect to nest SSI calls under Nginx, Nginx has to understand the filetype. For example, let's look at that line of code above again:

<!--#include virtual="/footers/main.inc" -->

The main.inc file includes SSI statements, mostly related to data about the file itself. Like this:

Last modified <!--#echo var="LAST_MODIFIED"-->

Under Nginx, this doesn't work: the line is included verbatim and the SSI code isn't executed. I initially thought this was because Nginx didn't support nesting of SSI calls, but that's not the case. It's because Nginx doesn't consider a ".inc" file to be of a type that it should execute code in (as mentioned, Apache doesn't care). I considered converting all calls to ".inc" files to calls to ".html" files. There are only about a dozen ".inc" files to rename, but there are literally thousands of calls to those ".inc" files. So the other possibility is to reconfigure Nginx to accept ".inc" files as valid HTML/SSI code. It seems that Nginx does this based on MIME type, and this did the trick:

# file: /etc/nginx/mime.types
types {
    #text/html              html htm shtml;
    text/html              html htm shtml inc;

The original line is commented out, the working replacement follows it. This made the SSI processor work as expected: nested calls, ".inc" files, whatever ... but Nginx is still different from Apache and didn't like a number of the previously valid calls I was making. Let's look at that last code again:

Last modified <!--#echo var="LAST_MODIFIED"-->

This displays on the page as "Last modified (none)" after processing by Nginx, where it had previously showed the date in a format I set in an earlier directive. This appears to be because Apache SSI supports a number of built-in variables that Nginx does not. I didn't fire up Apache to check the following list, but LAST_MODIFIED at least worked for me for years, and I have confirmed Nginx's lack of support for the following:

Unsupported Variables and Directives Supported Variables and Directives
DOCUMENT_NAME DATE_LOCAL
LAST_MODIFIED DATE_GMT
HTTP_REFERER HTTP_HOST
REMOTE_ADDRIP DOCUMENT_URI
#exec  
#flastmod  
#fsize  
#comment  

#exec is no loss at all: it's dangerous and a bad idea. And #comment I'd like, but it was only added to Apache around 2016-03, so I'm not surprised it's not available. I'm most frustrated by the loss of LAST_MODIFIED, #flastmod, and #fsize, all of which I used heavily.

Follow-up: See LAST_MODIFIED, SSIs, and sed for an exercise in working around Nginx's lack of LAST_MODIFIED.