Using error_page to a named location - possible?

Hi, as part of a larger problem I have a captive portal which grabs all
incoming URLs and after evaluating a bunch of logic redirects the caller
somewhere else. I use proxy_pass to pass the request to my cgi code.

However, I would like to customise the error message in the event that
the upstream daemon is unavailable. I can’t figure out how to do that
affecting part of the incoming URLs. Is it possible to do something
like “error_page @502_error” in conjunction with proxy_pass?

So, I have

server {
listen 8000 default_server;

root /var/www/cp/htdocs/public;

location / {
proxy_read_timeout 5;
proxy_pass http://127.0.0.1:3000;
error_page 502 /502.html;
proxy_set_header Host $http_host;
proxy_set_header X-Server-Addr $server_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-HTTPS 0;
proxy_set_header X-Captive-Portal-Redirect 1;
}
location /502.html {
internal;
}
}

However, if I visit “http://somewhere/502.html” I get a 404 response
(presumably matching the location /502.html and the 404 is due to the
“internal”?)

I can’t see that it’s possible to use something like:

location / {
proxy_pass http://127.0.0.1:3000;
error_page 502 @502;
}

location @502 {
index /502.html;
root /var/www;
internal;
}

Does someone have a suggestion on how to customise the error response
without affecting ANY incoming URLs (they must ALL be passed through the
proxy under normal working situation)

(Its occured to me that I could perhaps setup another “server { }” block
listening on another port, then use a different proxy_pass in the
“location @502 {}”? Seems ugly, but would that work?)

Thanks for any thoughts

Ed W

2012/3/6 Ed W [email protected]:

Does someone have a suggestion on how to customise the error response
without affecting ANY incoming URLs (they must ALL be passed through the
proxy under normal working situation)

Try location = /502.html { } instead.

Hello!

On Tue, Mar 06, 2012 at 04:37:41PM +0000, Ed W wrote:

Hi, as part of a larger problem I have a captive portal which grabs
all incoming URLs and after evaluating a bunch of logic redirects
the caller somewhere else. I use proxy_pass to pass the request to
my cgi code.

However, I would like to customise the error message in the event
that the upstream daemon is unavailable. I can’t figure out how to
do that affecting part of the incoming URLs. Is it possible to do
something like “error_page @502_error” in conjunction with
proxy_pass?

Yes, see below.

proxy_pass http://127.0.0.1:3000;

}

However, if I visit “http://somewhere/502.html” I get a 404 response
(presumably matching the location /502.html and the 404 is due to
the “internal”?)

Yes.

internal;
}

Try something like:

location @502 {
    internal;
    root /var/www;
    rewrite ^ /502.html break;
}

This should work at least for GET requests.

Does someone have a suggestion on how to customise the error
response without affecting ANY incoming URLs (they must ALL be
passed through the proxy under normal working situation)

(Its occured to me that I could perhaps setup another “server { }”
block listening on another port, then use a different proxy_pass in
the “location @502 {}”? Seems ugly, but would that work?)

This should work too.

Maxim D.

Hello!

On Tue, Mar 06, 2012 at 10:05:03PM +0400, Max wrote:

proxy_set_header X-Server-Addr $server_addr;

However, if I visit “http://somewhere/502.html” I get a 404 response
}

# all named locations are internal by default, hence no "internal;"
return 502 "Gateway error while servicing $request_uri!\n";

}

The proxy_intercept_errors isn’t needed to handle 502’s generated
by nginx, it’s only needed when backend returns the error in
question and one want to override it.

Maxim D.

06 марта 2012, 22:24 от Maxim D. [email protected]:

Hi, as part of a larger problem I have a captive portal which grabs
Yes, see below.
proxy_read_timeout 5;
}

error_page 502 @502;

}

location @502 {
# all named locations are internal by default, hence no “internal;”
return 502 “Gateway error while servicing $request_uri!\n”;
}

The proxy_intercept_errors isn’t needed to handle 502’s generated
by nginx, it’s only needed when backend returns the error in
question and one want to override it.

Perhaps I misunderstood the OP, but he mentioned passing the
request to his cgi code, but mentioned no fastcgi_pass directives,
so it seems he wants to intercept 502 errors from the backend
server.

Max

06 марта 2012, 21:47 от Maxim D. [email protected]:

that the upstream daemon is unavailable. I can’t figure out how to
listen 8000 default_server;
proxy_set_header X-Forwarded-HTTPS 0;
the “internal”?)

Yes.

I can’t see that it’s possible to use something like:

location / {
proxy_pass http://127.0.0.1:3000;
error_page 502 @502;
}

It is possible, you should use the proxy_intercept_errors directive:

location / {
proxy_pass http://127.0.0.1:3000;
proxy_intercept_errors on;
error_page 502 @502;
}

location @502 {
# all named locations are internal by default, hence no “internal;”
return 502 “Gateway error while servicing $request_uri!\n”;
}

Max

On 06/03/2012 17:47, Maxim D. wrote:

internal;
}
Try something like:

 location @502 {
     internal;
     root /var/www;
     rewrite ^ /502.html break;
 }

This should work at least for GET requests.

Perfect - thanks!

Now, I have thought about this a little while and I don’t quite
understand how it works…?

The key seems to be the rewrite clause - near as I can see, without this
the error request gets reprocessed by the “location /” section, but the
rewrite is causing it to terminate that?

Can you teach me to understand this please?

Many thanks!

Ed W

Hello!

On Tue, Mar 06, 2012 at 10:32:56PM +0000, Ed W wrote:

root /var/www;
This should work at least for GET requests.

Can you teach me to understand this please?

Under normal conditions

location @502 {
    root /var/www;
}

will behave much like normal static location, as described here:

http://nginx.org/en/docs/http/request_processing.html

That is, request to a “/file”, if it happens to hit his location,
will be processed as follows:

  1. It doesn’t ends with ‘/’, hence nginx will try to open a file
    for it, + . I.e. “/var/www/file” will be opened.

  2. The (1) will likely fail (as there is no such file) and will
    generate 404.

More importantly, request to a “/” will be processed as
follows:

  1. It ends with “/”, so nginx will try to use index module to
    handle it.

  2. Index module will test + + file, i.e.
    “/var/www/502.html” with your config. If it’s found - nginx will
    do internal redirect to “/502.html”.

  3. The internal redirect will end up in “location /”. This is
    obviously not what you want.

With “rewrite ^ /502.html break;” the following two important
things happen:

a. Every request will be a request to a file (i.e. no internal
redirects). Note that “break” is important, without “break” nginx
will re-do location matching again after rewrite.

b. Every request will be a request to the “/var/www/502.html”
file.

That is, every GET request which hits the @502 location will
return the /var/www/502.html.

Note well:

For POSTs things won’t be that easy, as error_page set to a named
location won’t change request method to GET (in contrast to
error_page set to a uri), and the above will generate 405 Method
Not Allowed. If you have to handle POSTs as well, I see the
following options as of now:

  1. Use proxy_pass to another server{} in location @502 and process
    it there. You may also use proxy_method/proxy_pass_request_body
    to simplify things.

  2. Use some normally impossible location, like

    location / {
    proxy_pass http://backend;
    error_page 502 //502.html;
    }

    location = //502.html {
    root /var/www;
    }

The (2) is more like a hack, but should work as //502.html isn’t
normally reachable via nginx due to merge_slashes being “on” by
default (see Module ngx_http_core_module).

Maxim D.

On 06/03/2012 23:28, Maxim D. wrote:

}
root /var/www;
The key seems to be the rewrite clause - near as I can see, without
will behave much like normal static location, as described here:
generate 404.

  1. The internal redirect will end up in “location /”. This is
    obviously not what you want.

With “rewrite ^ /502.html break;” the following two important
things happen:

Hi, Thanks for this REALLY clear post. I wonder if there is somewhere
sensible to record this on the wiki - it’s very incisive on a subtle
subject!

Just to confirm my understanding, the point 2) above seems to the key -
ie the “index” module is doing a “redirect” without a break, hence
adding the explicit redirect WITH a break is what we need to prevent the
cycle. This fits with my observations (of course) because when I added
a location matching “location /502.html” this also prevents the cycle

Thanks for investing quite some time answering this!

Note, the background to this is that I want to bounce any incoming url
to “http://portal/login?redirect_to=$encoded_incoming_url”. I have
tried to achieve this using only core nginx, but I can’t handle the
corner cases such as incoming url having multiple parameters. I have
worked around using my cgi to generate the redirect. IF you believe
that this should be possible using only nginx I would be interested to
re-ask that question - however, I think you are going to confirm that
it’s not currently possible?

Many thanks again!

Ed W

2012/3/7 Maxim D. [email protected]:

location @502 {
    internal;
    root /var/www;
    rewrite ^ /502.html break;
}

This seems to be working, too:

location @502 {
root /var/www;
try_files /502.html =404;
}