nginx maintenance page with content issue
Asked Answered
S

6

22

To show maintenance page during deploy I've always used next config in nginx:

if (-f /home/shared/system/maintenance.html) {
    return 503;
}

error_page 503 @maintenance;

location @maintenance {
    root /home/shared/errors;
    rewrite ^(.*)$ /maintenance.html break;
}

And everything was ok until I needed to add static content to maintenance page (images, stylesheets, etc.)

No of static content work with such logs in error.log:

2011/05/05 02:47:20 [notice] 13760#0: *6 "^(.*)$" matches "/some.jpg", client: x.x.x.x, server: server.com, request: "GET /some.jpg HTTP/1.1", host: "server.com"
2011/05/05 02:47:20 [notice] 13760#0: *6 rewritten data: "/maintenance.html", args: "", client: x.x.x.x, server: server.com, request: "GET /some.jpg 2 HTTP/1.1", host: "server.com"

Which is logical - if I do rewrite everything to maintenance.html that means that static content is not exclusion.

But I cannot find any proper solution to make redirect to every file except that ones which are physically existed in root /home/shared/errors folder.

PS. /home/shared/errors does not share any resources with common project folder - this is completely separate folder (even without any symlinks to /current for the project.

Shortage answered 12/5, 2011 at 20:46 Comment(0)
S
12

I'm sorry, Frank Farmer, but this doesn't work.

What works but not so neat:

And how requests work ->

  1. I use rule #1

    if (-f /home/shared/system/maintenance.html) {
        return 503;
    }
    

    this rule has a support of named location location @maintenance and for the common redirect to /maintenance.html at root /home/shared/errors everything works.

  2. this page contains and image some.jpg - to receive this image browser starts new request and this new request again hits rule #1

    all of this was described in initial question BUT

    if I use some if magic like in Frank Farmer's answers I can point server to requested file BUT HTTP answer will be 503 and browsers (all, except Safari, in my test) throws errors in debug console and do show image but do not process CSS-files in the same situation.

    And this is critical.

  3. I tried to resolve this using location magic for my new content-requests - that means that I have to:

    1. do NOT return 503 for content requests and skip named location at all.

    2. do change root /home/shared/errors because maintenance content is still there.

  4. Finally, I've got next solution:

    1. create maintenance-static folder for all static content and change paths in my maintenance.html file and maintenance-static stylesheets

    2. next use these rules (I believe they are self-descriptive) which replace single if (-f /home/shared/system/maintenance.html) in initial question:

      set $can503 0;
      if (-f /home/shared/system/maintenance.html) {
          set $can503 1;
      }
      if ($uri ~* /maintenance-static/) {
          set $can503 0;
      }
      location /maintenance-static/ {
          root /home/shared/errors;
      }
      if ($can503 = 1) {
         return 503;
      }
      

This solution works without any errors in all browsers and for numerous of pages in shared/errors folder, for ex. maintenance.html, error.html, overload.html, etc.

This solution is not really clear - probably you can hint me how I can make it neater, but remembering we are dealing with separate requests (and separate nginx processes for each file/request in case of high load, etc.) for initial html and its content we cannot use same rule with 503 redirecting for all files.

Shortage answered 16/5, 2011 at 11:17 Comment(0)
D
10

I spent a good two hours looking for an answer to this problem, and finally found this post. Seems like it should be more common. My solution fell somewhere in between Frank's and Wile's. As stated by Wile, certain browsers (Chrome for instance) will choose to not render the CSS/JS of any files that came back 503, even if it fully and properly fetched them.

But there's a fix that's less hacky than what Wile did. Simply return 200!

My full solution was as follows:

error_page 503 @maintenance;

location @maintenance {
    root /path_to_static_root;
    if (!-f $request_filename) {
        rewrite ^(.*)$ /rest_of_path/maintenance.html break;
    }
    return 200;
}

Worked like a charm. :)

Dallas answered 5/11, 2011 at 8:5 Comment(2)
do you know if anything has changed with the way nginx handles errors? instead of returning 200 it now returns 502. it only does 502 if the file actually exists (this excludes a "maintenance.html" request)Sloop
Made an update, there is indeed a whole new issue when acting as a reverse proxy. Question here #9338630Sloop
C
4
location @maintenance {
    root /home/shared/errors;
    rewrite  (some\.jpg|some2\.gif)$ /$1 break;
    rewrite ^(.*)$ /maintenance.html break;
}

This might work without enumerating the whitelisted files:

location @maintenance {
    root /home/shared/errors;
    if (!-f $request_filename) {
        rewrite ^(.*)$ /maintenance.html break;
    }
}
Calicle answered 12/5, 2011 at 22:32 Comment(2)
Great! Thanks, but is there a solution not to list each file? It's not a problem - I have very few files, but this would be neater solution...Shortage
@FrankFarmer Funny how your answer would have spared me a good amount of fumbling around because I eventually came up with the exact same code (see my blog post if you are interested).Reiff
G
2

this solution works with only a single if statement, and is also much more readable and understandable:

upstream unicorn {
        server unix:/path/to/unicorn.sock;
}

server {
        listen 3000 default deferred;

        proxy_read_timeout 3600;
        client_max_body_size 4G;
        set_real_ip_from 0.0.0.0/0;

        root /path/to/current/public;
        try_files $uri/index.html $uri.html $uri @unicorn;

        error_page 404 /404.html;
        error_page 500 502 504 /500.html;
        error_page 503 /system/maintenance.html;

        location /404.html {
                internal;
        }

        location /500.html {
                internal;
        }

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

                proxy_set_header Host $http_host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
                proxy_redirect off;
                proxy_pass http://unicorn;
        }
}
Gym answered 11/5, 2012 at 8:46 Comment(6)
What is internal? And how this deals with static content on maintenance page? Cannot see a solution hereShortage
internal prevents locations from being accessed directly. see wiki.nginx.org/HttpCoreModule#internal for detailsGym
static assets are delivered correctly, since they don't even enter the @unicorn location. they are handled by try_files.Gym
So, this means that static for maintenance page is inside current/public? I don't think this meets my requirements, but may work for other cases. root trick is one of the main points.Shortage
you could add a simple location /maintenance-static { alias /path/to/statics; } to make it work outside the document_rootGym
I think the problem with this one is that if your upstream returns a real 503 (like Drupal is in maintenance mode) the maintenance page here gets on 503, and that happens even when the maintenance.html doesn't exist, so you get a 404Bickel
M
1
server {
    listen 80;
    server_name myserv.trunk;
    autoindex off;
    access_log /var/log/nginx/sitename-access.log;

    location ^~ /static/ {
       root /home/dev/myserv-site/;
    }

    location ~ /(?P<language>en)?/? {
        uwsgi_pass unix:///tmp/uwsgi.sock;
        include uwsgi_params;
        error_page 502 @fallback;
        error_page 503 @maintenance;
        if (-f /home/dev/myserv-site/maintenance.active) {
          return 503;
        }
    }

    location @fallback {
      root /home/dev/myserv-site/;
      if ($language) {
          rewrite ^(?!\/static\/)(.*)$ /static/html/502.$language.html break;
      }
      rewrite ^(?!\/static\/)(.*)$ /static/html/502.zh.html break;
    }

    location @maintenance {
      root /home/dev/myserv-site/;
      if ($language) {
          rewrite ^(?!\/static\/)(.*)$ /static/html/503.$language.html;
      }
      rewrite ^(?!\/static\/)(.*)$ /static/html/503.zh.html break;
   }
}

Here is a sample of translatable maintenance (503) or 502 error page screen. If user goes to http://site.com/en/ English page will be displayed(when maintenance.active file exists). static dir is inside myserv-site dir.

Mccue answered 29/3, 2012 at 8:39 Comment(0)
O
1

I tried all the suggestions above and unfortunately they don't work as expected. The only solution that just works for me is the following:

location / {
    if (-f /path/to/file/indicating/maintenance/mode) {
       rewrite ^(.+)$ /maintenance/$1;
    }

    #... the rest of the "normal" logic
}

location /maintenance {
    root /path/where/your/maintenance/root/is;
    rewrite ^/maintenance/(.*)$ /$1 break;
    try_files /$uri /index.html =404;
    return 200;
}

I know, it creates the unnecessary /maintenance location in the normal conditions, but you can imagine another location path, which your users never guess :).

Orbadiah answered 7/4, 2015 at 18:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.