Custom 503 Error Page

I am trying to use a test for the existence of a file to return a error
page with a 503 Temporarily Unavailable response code. My configuration
is below. The problem is that it does not work. I can see the custom
error page, but the HTTP status code is 200, not the desired 503.

If I change the if directive to this:

if (-f $document_root/system/maintenance.html) {
  rewrite  ^(.*)$  /system/maintenance.html;  # No last
  return 503;
}

I start getting a 503 HTTP status code, but the content is not my custom
error page, but rather the default 503 response compiled into the nginx
server.

Am I doing something terribly wrong? I would really like to see my
custom page with a real 503 HTTP status code.

Thanks for any help,
Douglas A. Seifert

nginx.conf:

user and group to run as

#user www www;

number of nginx workers

worker_processes 6;

pid of nginx master process

pid /usr/local/www/nginx.pid;

Number of worker connections. 1024 is a good default

events {
worker_connections 1024;
}

start the http module where we config http access.

http {

pull in mime-types. You can break out your config

into as many include’s as you want to make it cleaner

include /usr/local/nginx/conf/mime.types;

set a default type for the rare situation that

nothing matches from the mimie-type include

default_type application/octet-stream;

configure log format

log_format main '$remote_addr - $remote_user [$time_local] ’
'"$request" $status $body_bytes_sent “$http_referer”

‘"$http_user_agent" “$http_x_forwarded_for”’;

main access log

access_log /usr/local/www/log/nginx_access.log main;

main error log

error_log /usr/local/www/log/nginx_error.log debug;

no sendfile on OSX

sendfile on;

These are good default values.

tcp_nopush on;
tcp_nodelay off;

output compression saves bandwidth

gzip on;
gzip_http_version 1.0;
gzip_comp_level 2;
gzip_proxied any;
gzip_types text/plain text/html text/css application/x-javascript
text/xml application/xml application/xml+rss text/javascript;

server {

# port to listen on. Can also be set to an IP:PORT
listen *:8080;

# Set the max size for file uploads to 50Mb
client_max_body_size 50M;

# sets the domain[s] that this vhost server requests for
server_name .foo.com *;

# doc root
root /usr/local/www/test;

# vhost specific access log
access_log  /usr/local/www/log/nginx.vhost.access.log  main;

# this rewrites all the requests to the maintenance.html
# page if it exists in the doc root. This is for capistrano's
# disable web task
if (-f $document_root/system/maintenance.html) {
  rewrite  ^(.*)$  /system/maintenance.html last;
  return 503;
}

location / {
  root /usr/local/www/test;
}

error_page   500 502 504  /500.html;
error_page   503 /503.html;

}

}

Hello!

On Sat, Feb 02, 2008 at 03:18:27PM -0800, Douglas A. Seifert wrote:

}

I start getting a 503 HTTP status code, but the content is not my custom
error page, but rather the default 503 response compiled into the nginx
server.

Am I doing something terribly wrong? I would really like to see my
custom page with a real 503 HTTP status code.

If you want to use custom response for 503 error, you should write

  error_page 503 /system/maintenance.html;

in your config.

Maxim D.

in your config.

Thanks for the response. Unfortunately, however, that has no effect. I
still see the compiled in 503 content.

-Doug Seifert

Hello!

On Sat, Feb 02, 2008 at 05:08:29PM -0800, Douglas A. Seifert wrote:

  error_page 503 /system/maintenance.html;

in your config.

Thanks for the response. Unfortunately, however, that has no effect. I still see the compiled in 503 content.

Probably because you have error_page 503 redefined later in your
config to /503.html.

Maxim D.

Try:

if (-f $document_root/system/maintenance.html) {
rewrite ^(.*)$ @maintenance last;
}

location = @maintenance {
error_page 503 /system/maintenance.html;
return 503;
}

On Sun, 2008-02-03 at 09:23 +0800, Eden Li wrote:

Try:

if (-f $document_root/system/maintenance.html) {
rewrite ^(.*)$ @maintenance last;
}

location = @maintenance {
error_page 503 /system/maintenance.html;
return 503;
}

Unfortunately, I get the same result: a 503 with the compiled in 503
content.

Thanks,
Doug Seifert

On Sun, 2008-02-03 at 04:20 +0300, Maxim D. wrote:

custom page with a real 503 HTTP status code.
Probably because you have error_page 503 redefined later in your
config to /503.html.

Maxim,
Thanks for trying, but it doesn’t matter where in the config the
error_page directive is placed, the result is the same: a 503 response
with the compiled in 503 content.

Thanks,
Doug Seifert

Hello!

On Sat, Feb 02, 2008 at 06:16:15PM -0800, Douglas A. Seifert wrote:

Thanks for the response. Unfortunately, however, that has no effect. I still see the compiled in 503 content.

Probably because you have error_page 503 redefined later in your
config to /503.html.

Maxim,
Thanks for trying, but it doesn’t matter where in the config the
error_page directive is placed, the result is the same: a 503 response
with the compiled in 503 content.

Just another quick note: due to some implementation wierdness of
ngx_http_rewrite_module, it may be required to define error_page
before if/return block. Try something like this:

 error_page 503 /system/maintenance.html;

 if (-f ...) {
     return 503;
 }

Maxim D.

Hmm… that doesn’t seem to work either. Is it possible that return xxx; always generates the internal response?

The only thing that would achieve the desired result in this case is
proxying to some blackhole which would cause the 503 to be caught and
rewritten according to the error_page directive.

Hello!

On Sun, Feb 03, 2008 at 10:47:39AM +0800, Eden Li wrote:

Hmm… that doesn’t seem to work either. Is it possible that return xxx;
always generates the internal response?

The only thing that would achieve the desired result in this case is
proxying to some blackhole which would cause the 503 to be caught and
rewritten according to the error_page directive.

Oops. Sorry, I missed. The real problem is that with suggested
configuration (i.e. always return 503 at server level) there
is no way to reach /system/maintenance.html file for nginx.

So, it tries to get /system/maintenance.html for error body, and
gets yet another 503. So it has to return hardcoded content.

The only solution is to allow nginx to access
/system/maintenance.html somehow. Something like this:

 error_page 503 /system/maintenance.html;

 location / {
     if (-f ...) {
         return 503;
     }
 }

 location /system/maintenance.html {
     # allow requests here - do not return 503
 }

The if{} block should be in all locations where access should be
disallowed, but not for /system/maintenance.html itself.

Maxim D.

         return 503;

Maxim D.

Doing it that way gets me the custom content, but with a 200 OK
response. :frowning: I’m afraid the only way I will be able to do this is to
recompile nginx with the default 503 content set to what I want it to be
(that’s my current workaround). Or dig up my extremely rusty C skills
and try to figure out the bug, if any.

Thanks,
Doug Seifert

On 2/2/08, Douglas A. Seifert [email protected] wrote:

         return 503;

Maxim D.

Hey Doug,

We do something like http://forum.engineyard.com/forums/3/topics/22 at
Engine Y… I did it earlier this week for a client. Is this
approach any different than the approaches you’ve tried thusfar?

On Sat, Feb 02, 2008 at 08:02:53PM -0800, Douglas A. Seifert wrote:

         return 503;

Maxim D.

Doing it that way gets me the custom content, but with a 200 OK
response. :frowning: I’m afraid the only way I will be able to do this is to
recompile nginx with the default 503 content set to what I want it to be
(that’s my current workaround). Or dig up my extremely rusty C skills
and try to figure out the bug, if any.

The way suggested by Maxim should work. Are you sure that yo do not use

 error_page 503 = /system/maintenance.html;

instead of

 error_page 503 /system/maintenance.html;

?

         return 503;

Maxim D.
error_page 503 = /system/maintenance.html;

instead of

 error_page 503 /system/maintenance.html;

I had forgotten to pull the rewrite directive out of the if:

I had

if (-f …) {
rewrite ^(.*)$ /system/maintenance.html last;
return 503;
}

Taking the rewrite directive out fixed it.

For those interested, the final config that does what I want is below.
I imagine a more complicated config will be harder to deal with because
I will have to make sure the check for the maintenance page stays out of
the top level of the config.

test.conf

user and group to run as

#user www www;

number of nginx workers

worker_processes 6;

pid of nginx master process

pid /usr/local/www/nginx.pid;

Number of worker connections. 1024 is a good default

events {
worker_connections 1024;
}

start the http module where we config http access.

http {

pull in mime-types. You can break out your config

into as many include’s as you want to make it cleaner

include /usr/local/nginx/conf/mime.types;

set a default type for the rare situation that

nothing matches from the mimie-type include

default_type application/octet-stream;

configure log format

log_format main '$remote_addr - $remote_user [$time_local] ’
'"$request" $status $body_bytes_sent “$http_referer”

‘"$http_user_agent" “$http_x_forwarded_for”’;

main access log

access_log /usr/local/www/log/nginx_access.log main;

main error log

error_log /usr/local/www/log/nginx_error.log debug;

no sendfile on OSX

sendfile on;

These are good default values.

tcp_nopush on;
tcp_nodelay off;

output compression saves bandwidth

gzip on;
gzip_http_version 1.0;
gzip_comp_level 2;
gzip_proxied any;
gzip_types text/plain text/html text/css application/x-javascript
text/xml application/xml application/xml+rss text/javascript;

server {

# port to listen on. Can also be set to an IP:PORT
listen *:8080;

# Set the max size for file uploads to 50Mb
client_max_body_size 50M;

# sets the domain[s] that this vhost server requests for
server_name .foo.com *;

# doc root
root /usr/local/www/test;

# vhost specific access log
access_log  /usr/local/www/log/nginx.vhost.access.log  main;

# this rewrites all the requests to the maintenance.html
# page if it exists in the doc root. This is for capistrano's
# disable web task
error_page   500 502 504  /500.html;
error_page   503 /system/maintenance.html;
location /system/maintenance.html {
  # Allow requests
}

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

}

}

         return 503;

Maxim D.

Hey Doug,

We do something like http://forum.engineyard.com/forums/3/topics/22 at
Engine Y… I did it earlier this week for a client. Is this
approach any different than the approaches you’ve tried thusfar?

Corey,

I tried to duplicate the config on the forum post you cited, but I must
be doing something wrong because I get into an infinite redirect loop.
Attached is what I tried …

test.conf

number of nginx workers

worker_processes 6;

pid of nginx master process

pid /usr/local/www/nginx.pid;

Number of worker connections. 1024 is a good default

events {
worker_connections 1024;
}

start the http module where we config http access.

http {

pull in mime-types. You can break out your config

into as many include’s as you want to make it cleaner

include /usr/local/nginx/conf/mime.types;

set a default type for the rare situation that

nothing matches from the mimie-type include

default_type application/octet-stream;

configure log format

log_format main '$remote_addr - $remote_user [$time_local] ’
'“$request” $status $body_bytes_sent “$http_referer”

‘“$http_user_agent” “$http_x_forwarded_for”’;

main access log

access_log /usr/local/www/log/nginx_access.log main;

main error log

error_log /usr/local/www/log/nginx_error.log debug;

no sendfile on OSX

sendfile on;

These are good default values.

tcp_nopush on;
tcp_nodelay off;

output compression saves bandwidth

gzip on;
gzip_http_version 1.0;
gzip_comp_level 2;
gzip_proxied any;
gzip_types text/plain text/html text/css application/x-javascript
text/xml application/xml application/xml+rss text/javascript;

server {

# port to listen on. Can also be set to an IP:PORT
listen *:8080;

# Set the max size for file uploads to 50Mb
client_max_body_size 50M;

# sets the domain[s] that this vhost server requests for
server_name .foo.com *;

# doc root
root /usr/local/www/test;

# vhost specific access log
access_log  /usr/local/www/log/nginx.vhost.access.log  main;

# this rewrites all the requests to the maintenance.html
# page if it exists in the doc root. This is for capistrano's
# disable web task
error_page   500 502 504  /500.html;
error_page   503 @503;
location @503 {
  rewrite ^(.*)$ /system/maintenance.html break;
}
location /system/maintenance.html {
  # pass
}

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

}

}

On 2/3/08, Douglas A. Seifert [email protected] wrote:

events {

nothing matches from the mimie-type include

gzip on;

access_log  /usr/local/www/log/nginx.vhost.access.log  main;
  # pass

}

Here’s my server block. My ordering is slightly different.
http://pastie.caboo.se/private/dmnepj3m2zxsnxhrqsuhcq

Here’s the shell output from curl before and after the maintenance
page goes up, and it appears to be delivering the content as expected.
Parked at Loopia

Here’s my server block. My ordering is slightly different.
Parked at Loopia

Here’s the shell output from curl before and after the maintenance
page goes up, and it appears to be delivering the content as expected.
Parked at Loopia

That did the trick. Thanks a million. I think Ezra should update his
default nginx configuration to match this. 503’s at maintenance time
are so much desired for SEO …

-Doug Seifert

I tried pretty much all of the approaches outlined above without success
on nginx 0.6.37 (posting this in June 2009. Get the version # using:
nginx -v)
The one approach I found did work was putting the following in the
server section of my config files:

error_page 503 @503;
location @503 {
rewrite ^(.*)$ /system/maintenance.html break;
}

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

I found it in the internet archive, posted by Lee Jensen and credited to
Ezra.

K.

Unfortunately this works great when nginx is directly giving a 503
code, but when used with return 503 :

error_page 503 /system/maintenance.html;
return 503;

The custom page isn’t taken into account…

Posted at Nginx Forum:

error_page 503 @503;
location @503 {
rewrite ^(.*)$ /system/maintenance.html break;
}

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

Great! Thanks to share it works great when I hardcode the return nbr. I
don’t understand why the following code doesn’t work…

    error_page 503 @503;
    location @503 {
            set $page 503;
            rewrite  ^(.*)$  /503.html break;

    }

    if ($page = 503) {
            return 503;
    }
    // or why not return $page;

Any tips?

As a sidenote, as I’ve never done any ruby dev, I had to find an online
interactive ruby shell to solve the registering captcha :smiley: :stuck_out_tongue: