I recently came across a requirement to implement something like Overlay FS with NGINX when deploying MediaWiki. Specifically, I was using the Git version of MediaWiki and needed to add some custom files to the directory (such as logos, favicon.ico, various verification files, etc.). I wanted to keep these files separate from the source code in order not to pollute Git and to reduce the hassle when upgrading.

To abstract the problem, NGINX needed to serve from /srv/foo/orig, and now it needs to additionally try to search for files from /srv/foo/overlay in addition to the original path (note that unlike Overlay FS, the original path is used first). And since MediaWiki relies on PHP, it also needs to be able to handle both dynamic and static files.

After several fumbling sessions, I obtained the following configuration.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
root /srv/foo/orig;

location @overlay {
  root /srv/foo/overlay;
  try_files $uri $uri/ @orig;
}

location @orig {
  rewrite ^/([^?]*)(?:\?(.*))? /index.php?title=$1&$2 last;
}

location / {
  index index.php;
  try_files $uri $uri/ @overlay;
}

With these configurations, NGINX will try the following in order when it encounters a path shaped like /baz.

  1. match /: try /srv/foo/orig/baz and /srv/foo/orig/baz/.
  2. go to @overlay: try /srv/foo/overlay/baz and /srv/foo/orig/baz/.
  3. go to @orig: rewrite the query string as /index.php and have FastCGI process the request. This part is suitable for cases where no static file is matched in either location, and any underhanded operation can be performed, such as leaving it to a dynamic program, redirecting it (rewrite), or returning an error (error_page). Note that this section cannot be merged into @overlay because the root of the two is different.

The @overlay and @orig above are named locations for NGINX, described in the official documentation as follows.

The “@” prefix defines a named location. Such a location is not used for a regular request processing, but instead used for request redirection. They cannot be nested, and cannot contain nested locations.

One of the advantages of using named location is that the jump does not affect the actual URL and avoids all kinds of complicated matching processing. Note that you must use root in @overlay to switch directories, because you cannot use alias in named location. Also, due to some mechanism I haven’t figured out yet (related to the NGINX context), the configuration must cascade try_files for chaining, not try_files $uri $uri/ @overlay @orig.

If there are only static files, then just keep @overlay. If you want to search the overlay directory first (same usage as Overlay FS), you need to write it differently.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
root /srv/foo/orig;

location @overlay {
  root /srv/foo/overlay;
  try_files $uri $uri/ @orig;
}

location @orig {
  try_files $uri $uri/ @orig_dyn;
}

location @orig_dyn {
  rewrite ^/([^?]*)(?:\?(.*))? /index.php?title=$1&$2 last;
}

location / {
  index index.php;
  try_files @overlay;

Since root is the same, here @orig_dyn is merged directly into @orig.