1

Using nginx 1.8.1 on an Amazon Linux EC2 instance. Using as a reverse proxy to support https for Apache running on a different instance. All is working fine, except for this issue.

I want to serve a static page from nginx in case I want to take the Apache server instance down. So I did this:

    location   / {
        try_files /site-down.html $uri @backend;
    }

So before I shut down the backend server, I create a symbolic link in the nginx server's root directory to a static html file that looks like this:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

  <title>example.com</title>
  <style type="text/css">
  body {
    background-image:url('/images/back-soon-background.jpg');
    font-family: arial,verdana,sans-serif;
    text-align: center;
  }
  #msg {
    background-image: url('/images/back-soon-oval.png');
    background-repeat: no-repeat;
    width: 600px;
    padding-top: 140px;
    padding-left: 50px;
    padding-right: 50px;
    padding-bottom: 150px;
  }
  </style>
</head>

<body>
<div id="msg">
<h1>Sorry, the site is currently unavailable.</h1>
<h2>We expect to be back within 2 hours.</h2>
</div>
</body>
</html>

The problem is that when I do that, the page gets displayed immediately as just the text content of the div, without the images specified by the <style> in the <head> section. Using Chrome developer tools Network tab, I can see requests for the image urls going out, and getting 200 status codes. But if I click on those requests, there is no preview available, and the body length is too short. Reloading the page doesn't help right away. But if I let it sit there for a while, eventually the correct output with the images appears. If I point my browser directly at /site-down.html, the page displays correctly right away.

Both Chrome 52.0.2743.116 m and Firefox 48.0.2 behave the same way. I'm new to nginx, but I can't imagine why the first uri in a try_files should behave any differently from going directly to that uri in the case where the file exists. What am I missing?

sootsnoot
  • 405
  • 1
  • 5
  • 12

3 Answers3

1

Comments by @Michael-Hampton provided the answer to the question as I asked it. I suggested about 6 weeks ago that if he posted that information as an answer, I'd mark it accepted. But since he didn't do that, I'm constructing an answer here to accept. Seems a shame to leave this unanswered.

The answer to the question as I asked it is just that the nginx configuration I specified forced all requests to first attempt to serve the static site-down.html page. And since the images were specified as urls to the same site, those image requests also got handled by the / location directive, so the try_files got applied and changed them also to serve the site-down.html page.

I don't know why the images eventually showed up at all after some reloads and waiting, something must have timed out.

The most direct way to address the problem as I was seeing it was to change the background-image url's into data url's with the image content itself embedded in base64 strings. By doing that, the site-down.html page does not generate any additional requests for resources, and it works as I originally intended.

But he also noted that what I'm trying to do, when working, gives a 200 status code even though the site is basically down. I explained that the site is intended solely for interactive users, not servers, and this is for short-term intentional outages, not errors like a backend server crash. So I don't see that as a big problem. But the truth is, it's always best to give a meaningful status code. So I think the "right" solution for me is have two different "available" nginx config files, one of which simply hard-codes a 503 response using site-down.html as a custom error page. Then instead of creating/removing a symlink named site-down.html in the root, and relying on try_files to give the desired behavior, I should just change the symlink in the sites-enabled directory to select the right configuration and do a sudo service nginx reload to smoothly switch behaviors.

The advantage of using try_files is that the behavior switches immediately with one command, while the disadvantage is that the status code indicates the site is working fine even though it isn't. The advantage of switching config files is that it gives meaningful status, while the disadvantage is the extra step of reloading nginx is something that could be forgotten. However, in the end the switch will be done by a script, and the script won't forget to reload the configuration.

sootsnoot
  • 405
  • 1
  • 5
  • 12
0

try_files tries files in the order you specify. Change the order to something like this

try_files $uri @backend /site-down.html;

I assume since the page is being served now you have everything else set up ok. Give this a go and comment on the answer if you need further help. Be sure to include logs if applicable.

Tim
  • 31,888
  • 7
  • 52
  • 78
  • Yes, it does try in the order specified. Your change completely defeats the purpose I described, which is to display the /site-down.html page if that page exists, and only if that page does *not* exist should it attempt to display the $uri (and only if the $uri does not identify a static file below should it send the request to the backend). The behavior at that level is completely correct, the problem is just that when site-down.html does exist, it displays without the resources from the css until quite a few seconds later. – sootsnoot Sep 09 '16 at 21:24
  • The idea was to change the approach slightly. My suggestion should try the first two and only display site-down if the site is down. However I'm not sure if try_files will move on to the next option if it gets an error code. I think it's worth exploring, but it doesn't approach things in the way you were initially thinking. It's similar to Michael's response. – Tim Sep 09 '16 at 21:37
0

Use the error_page directive to handle the situation when your backend is down. This will also let you return a proper HTTP response code.

For example:

server {
    try_files $uri @backend;

    error_page 500 502 503 504 =503 /site-down.html;

    #....

In this case, we serve static files first, then go to the backend. If the backend returns one of the listed errors, then nginx will serve /site-down.html with a HTTP 503 response.

The benefit of this method is that you don't have to manually add or remove the site-down.html file. You leave it in place, and nginx will automatically serve it if the backend is actually down.

Michael Hampton
  • 244,070
  • 43
  • 506
  • 972
  • Yes, this is sensible in general, but the point is that I want to be able to display a page saying the backend is down, and preventing normal access to it, without actually taking the backend down. For example, the backend instance is in a security group that allows http access only from the nginx server and from my laptop. So I create the symlink on the nginx server, point my etc/hosts file at the backend's public IP address, and then exercise/test the web app while all other users see the site-down page. That's the functionality I want, what you propose here does something quite different. – sootsnoot Sep 09 '16 at 21:35
  • 1
    That's simple, then: `try_files $uri /site-down.html @backend;` But as a rule, you should do sensible things. – Michael Hampton Sep 09 '16 at 21:36
  • One of us is not understanding the other. If $uri specifies a static file, then that file will be displayed instead of the site-down page, that's not the desired functionality. And even if it were acceptable, why would putting the site-down page second in the list instead of first cause the images in its style to display immediately instead of being suppressed? – sootsnoot Sep 09 '16 at 21:41
  • 1
    If you want to suppress even the serving of static files, then you'll also suppress the serving of the linked stylesheets from site-down.html! You really need to think some more about what you are trying to accomplish. – Michael Hampton Sep 09 '16 at 21:43
  • Ahaa! Thank you Michael, that explains it! I know what I wanted to accomplish, I just didn't appreciate the significance of the additional requests cause by the url's within the styles. I guess I can make do without the images, although if there's a way to fully embed images within the single html file, like there is with embedded images in email messages, I'd be happy to do that. I guess what happens is the image requests give a 200 status and response body of the site-down.html page itself, which is not an image. Don't know why they eventually appear, but the initial display is correct. – sootsnoot Sep 10 '16 at 03:25
  • If you want to massage your comment into an answer, I'll accept it as the answer: I asked how to get the resources to load in a timely way, and the answer is that the try_files itself is preventing the resources from loading! I'll ask a different question about embedding images without generating additional requests if I can't find an answer to that. – sootsnoot Sep 10 '16 at 03:30
  • FYI, I found that it's quite trivial to embed the images, I just had never done it before. In the Style section, just replace url(/foo.jpg) by url(data:image/jpeg;base64,long-string), where long-string is the output from base64 -w0 foo.jpg. The two images here are not very big, larger one has a base64 encoding of about 22K characters, but html doesn't care. So I did that, and now the site behaves exactly as I wanted. This is for short temporary intentional outages. The site is strictly for interactive users, no need to give error status code. If the backend is broken, that gives 502 status. – sootsnoot Sep 10 '16 at 04:32