Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

nginx; return response code if a file exists by using try_files only

Tags:

nginx

Since IfIsEvil I've been trying to set up a configuration using the directive try_files only so that a maintenance page is displayed together with the response code 503, for any URI without exception, i.e. including php pages, if a maintenance file exist.

There are two problems with my configuration:

  1. The maintenance page does not display for php URIs.
  2. The response code 503 is not returned if the maintenance.html file exists.

I've seen similar questions [1],[2] but none with a solution that uses try_files only (as opposed the using the if directive) and that unconditionally serves a maintenance page with the response code 503, if the corresponding file is present. Is such a solution possible?

Below is my current non-working conf file. It doesn't contain a 503 response code setting because I don't understand where it's supposed to go in order for it to work as described above.

worker_processes            1;

error_log                   /var/log/nginx/error.log debug;

events {
    worker_connections      1024;
}

http {
    include                 mime.types;
    default_type            application/octet-stream;

    index                   index.php index.html index.htm;

    server {
        listen                  80;
        server_name             rpi;
        root                    /www;

        location / {
            try_files           /maintenance.html $uri $uri/ /index.php?$args;

            # pass the PHP scripts to FastCGI server
            location ~ \.php$ {
                try_files           $uri =404;
                fastcgi_pass        unix:/run/php-fpm/php-fpm.sock;
                fastcgi_index       index.php;
                include             fastcgi.conf;
            }
        }
    }
}

I guess that my question could alternatively be phrased like this: Can try_files be made to work as an if control structure? If not by itself, can it, with the above goal, be combined with other directives to act as such, excluding the if directive?

edit: Below is a solution using if that I'm currently using by including it in the server section:

error_page              503 @maintenance;

if (-f $document_root/maintenance.html) {
        return          503;
}

location @maintenance {
        try_files       /maintenance.html =404;
}
like image 714
ivavid Avatar asked May 22 '13 13:05

ivavid


People also ask

What is the use of try_files in nginx?

Using try_files means that you can test a sequence. If $uri doesn't exist, try $uri/ , if that doesn't exist try a fallback location.

What is $URI in nginx?

It is exactly the $uri/ part that makes nginx assuming an URI can be a directory name and looking for an index file presence inside it.

What is root Nginx?

The root directive specifies the root directory that will be used to search for a file. To obtain the path of a requested file, NGINX appends the request URI to the path specified by the root directive. The directive can be placed on any level within the http {} , server {} , or location {} contexts.

What is location in nginx?

The location directive within NGINX server block allows to route request to correct location within the file system. The directive is used to tell NGINX where to look for a resource by including files and folders while matching a location block against an URL.


1 Answers

Very good question! But it's not possible at all unless you call some kind of script that sets the correct response code.

A working solution only with nginx and no if

The try_files directive is only performing an internal redirect for the last statement. But we can combine it with the index directive and force an internal redirect.

# This will be the HTML file to display for the 503 error.
error_page 503 /maintenance/maintenance.html;

# Let nginx know that this particular file is only for internal redirects.
location = /maintenance/maintenance.html {
  internal;
}

# Any request that starts with the maintenance folder is 503!
location ^~ /maintenance/ {
  return 503;
}

# Instead of checking if a file exists and directly delivering it we check
# if a certain directory exists and trigger our index directive which will
# perform an internal redirect for us.
location / {
  expires epoch;
  try_files /maintenance/ $uri $uri/ /index.php?$args;
}

Any drawbacks with this method?

  • Actual 301 redirect to the directory instead of staying at the same URL (search engines).
  • Browser caching of 301 redirects might be an issue, that's why I added the expires epoch to the location block.

Other solutions?

Via nginx configuration file

Instead of creating an HTML file, why not create an nginx configuration file and simply reload the process?

  • Much better performance!
  • Easier to understand!
  • No side effects!

The nginx configuration could look like the following (note that this if isn't evil at all):

error_page 503 /maintenance.html;

location / {
  include maintenance.conf;
  if ($maintenance = 1) {
    return 503;
  }
  try_files $uri $uri/ /index.php?$args;
}

Content of the maintenance.conf file:

set $maintenance 0;

And if you want to activate the maintenance mode (in your shell):

echo set $maintenance 1;> maintenance.conf && service nginx reload

More advanced for shell friends You could even extend an init script with this, for instance my LSB compliant one by replacing the following block at the end of the file:

*)
  echo "Usage: ${NAME} {force-reload|reload|restart|start|status|stop}" >&2
  exit 1
;;

With the following block:

maintenance)
  echo "set $maintenance 1;" > /etc/nginx/maintenance.conf && service nginx reload;
;;

production)
  echo "set $maintenance 0;" > /etc/nginx/maintenance.conf && service nginx reload;
;;

*)
  echo "Usage: ${NAME} {force-reload|reload|restart|start|status|stop|maintenance|production}" >&2
  exit 1
;;

And now you can simply execute the following command (including auto-completion) to go into maintenance mode:

service nginx maintenance

Or the following to go back into production:

service nginx production

With a script / PHP file

Another extremely easy approach that would work like a charm is to use a PHP file that handles it.

location / {
    try_files /maintenance.php $uri $uri/ /index.php?$args;
}

Your PHP file would look exactly like your HTML file, you only have to add the following to it's beginning (assuming PHP 5.4+):

<?php http_response_code(503) ?><!doctype html>
<html>
<head>
<!-- ... more html ... -->
like image 119
Fleshgrinder Avatar answered Sep 22 '22 08:09

Fleshgrinder