Handling OPTIONS request in nginx - http

We're using HAProxy as a load balancer at the moment, and it regularly makes requests to the downstream boxes to make sure they're alive using an OPTIONS request:
OPTIONS /index.html HTTP/1.0
I'm working with getting nginx set up as a reverse proxy with caching (using ncache). For some reason, nginx is returning a 405 when an OPTIONS request comes in:
192.168.1.10 - - [22/Oct/2008:16:36:21 -0700] "OPTIONS /index.html HTTP/1.0" 405 325 "-" "-" 192.168.1.10
When hitting the downstream webserver directly, I get a proper 200 response. My question is: how to you make nginx pass that response along to HAProxy, or, how can I set the response in the nginx.conf?

I'm probably late, but I had the same problem, and found two solutions to it.
First is tricking Nginx that a 405 status is actually a 200 OK and then proxy_pass it to your HAProxy like this:
error_page 405 =200 #405;
location #405 {
root /;
proxy_pass http://yourproxy:8080;
}
The second solution is just to catch the OPTIONS request and build a response for those requests:
location / {
if ($request_method = OPTIONS ) {
add_header Content-Length 0;
add_header Content-Type text/plain;
return 200;
}
}
Just choose which one suits you better.
I wrote this in a blog post where you can find more details.

In the httpchk option, you can specify the HTTP method like this:
httpchk GET http://example.com/check.php
You can also use POST, or a plain URI like /. I have it check PHP, since PHP runs external to Nginx.

Related

How does Nginx handle `If-None-Match` and `If-Modified-Since` headers when configured as a reverse proxy cache?

I have a PHP CMS as an upstream/origin server that serves content. It is placed behind a Nginx web server configured as a reverse proxy cache with proxy_pass and proxy_cache.
While implementing 304 Not Modified response on the upstream/origin server, I noticed that I sometimes get 304 Not Modified response from the proxy without my upstream/origin server being called.
My proxy configuration looks like this (notice that I don't cache 304 responses):
http {
#...
proxy_cache_path /usr/share/nginx/cache/ levels=1:2 keys_zone=microcache:10m max_size=1024m inactive=1h;
}
server {
server_name _;
#...
location / {
proxy_cache microcache;
proxy_cache_revalidate on;
add_header X-Cache-Status $upstream_cache_status;
#...
proxy_cache_valid 200 301 302 1m;
proxy_pass http://upstream;
}
}
When hitting a URL for the first time, I get a 200 with a X-Cache-Status: MISS and the ETag and Last-Modified headers returned by the origin.
The second time I hit the URL, I get a X-Cache-Status: HIT and my origin doesn't get called, so all is great!
However, if I hit the URL again, this time with the If-Modified-Since and If-None-Match headers with the values received with the first call, I get a 304 Not Modified, with X-Cache-Status: HIT and still no call on my origin server!
I seems then that Nginx has the content in its cache, and figures with the incoming headers that it should return a 304. Meaning my origin never gets to return the 304 itself if the content is in the Nginx cache.
Am I right? I cannot find any documentation on this behavior. I, however found a module in the Nginx source code that seems the be handling something similar: https://github.com/nginx/nginx/blob/master/src/http/modules/ngx_http_not_modified_filter_module.c#L78
Could you please provide me with insights on this behavior, how it works, and what enables it in my configuration?
Thank you very much!
proxy_cache_path /usr/share/nginx/cache/ levels=1:2 keys_zone=microcache:10m max_size=1024m inactive=1h;
proxy_cache microcache;
Meaning that nginx server stores cache data to /usr/share/nginx/cache/
and
proxy_cache_valid 200 301 302 1m;
This means any HTTP API calls that have HTTP statuses 200, 301, or 302 use nginx local cache (which is stored in /usr/shre/nginx/cache) for one minute.

How to escape "?" in nginx rewrite rule output

I am trying to reverse proxy in nginx, rewriting a front end page like "/a/b?page=2" into a backend request to "/a/b%3fpage=2"
I cannot figure out how to get nginx to make reverse proxy requests which include "%3f".
With the following config:
rewrite ^/one$ /a%3fb;
rewrite ^/two$ /a?b;
rewrite ^/three$ /a\?b;
/one makes a backend request like GET /a%253fb HTTP/1.0
/two makes a backend request like GET /a?b HTTP/1.0
/three makes a backend request like GET /a\?b HTTP/1.0
How can I get a backend request like GET /a%3fb HTTP/1.0?
Thanks to #Richard Smith's comment, I was able to fix this for my specific case with the following code:
location / {
set $backend_uri $request_uri;
if ($args ~* "page=(\d+)") {
set $page $1;
set $backend_uri $uri%3fpage=$1;
}
proxy_pass http://example.com$backend_uri;
}
I think that I might also have been able to do something more general with the lua rewrite directive, but I was unable to install mod-lua on an Amazon Linux 2 machine, see https://serverfault.com/questions/961337/how-to-install-nginx-mod-lua-on-amazon-linux-2

Redirect post request using nginx

I am currently getting post request like this:
POST /api/x/y HTTP/1.1
With request body: a=x&b=y etc.
I want to redirect the request to another server in any of these two ways:
1. GET x.x.x.x:8888/xy/abc?a=x&b=y
2. POST x.x.x.x:8888/xy/abc with body a=x&b=y
I am trying these two redirect options:
1.rewrite ^(.*) http://server/api$request_body redirect;
//this is not sending body params
2. return 307 http://server/api?$request_body;
//this is giving me 400
If you do this:
location /api/ {
proxy_pass http://x.x.x.x:8888;
}
Then a request to example.com/api/x/y/ will be proxied to http://x.x.x.x:8888/api/x/y/.
If you do this:
location /api/x/y/ {
proxy_pass http://x.x.x.x:8888/xy/abc/;
}
Then a request to example.com/api/x/y/ will be proxied to http://x.x.x.x:8888/xy/abc/.
Request method will be unchanged, unless you tell Nginx to change it. Some headers will not be passed, unless you tell Nginx to pass them.

Custom 404 in Nginx

I'm trying to specify a custom 404 page and preserve the URL, however using the below gives me the error nginx: [emerg] "proxy_pass" cannot have URI part in location given by regular expression, or inside named location, or inside "if" statement, or inside "limit_except" block in...
location / {
error_page 404 #404;
}
location #404 {
proxy_pass https://example.com/404.html;
}
Does anyone know a solution to this?
I have tested your config on nginx 1.6.2. The error message complains about the URL in proxy_pass having path specified. Specifying only https://server would be OK, but doesn't solve your issue.
Named locations (like #fallback) have been introduced also to avoid having to use non-existing locations (which could later become existing and cause issues). But we can also use a location which should never exist on the server itself, for example:
location / { error_page 404 /xx-404-xx; }
location /xx-404-xx { proxy_pass https://example.com/404.html; }
Redirecting to relative URL causes only internal nginx redirect, which does not change the URL as browser sees it.
EDIT: Here is the test result:
kenny:/etc/nginx/sites-enabled:# wget -S server.com/abc/abc
HTTP request sent, awaiting response...
HTTP/1.1 404 Not Found
Server: nginx/1.6.2
Date: Thu, 16 Jul 2015 07:01:38 GMT
Content-Type: text/html
Content-Length: 5
Connection: keep-alive
Last-Modified: Thu, 16 Jul 2015 07:00:59 GMT
ETag: "5037762-5-51af8a241434b"
Accept-Ranges: bytes
2015-07-16 09:01:38 ERROR 404: Not Found.
From apache access log:
1.2.3.4 - - [16/Jul/2015:09:01:38 +0200] "GET /test.html HTTP/1.0" 200 5 "-" "Wget/1.13.4 (linux-gnu)"
In nginx I have proxy_pass to a html file on Apache webserver with just "test\n" in it. As you can see, nginx fetched that, added headers from Apache (Last-Mod, ETag) and also Content-Length: 5, so it received the html file from Apache, but wget doesn't save the content of 404 errors. Also many browsers don't display 404 errors by default if they are smaller than 1 kB (they show their own error page instead). So either make your 404 page bigger or you can configure nginx to serve it as normal html with "200" result code (error_page 404 =200 /xx).
I have no idea why you are receiving external redirects with the same config. Try it with wget to see which headers exactly nginx sent. Also mixing http and https for proxy should be no issue. Try to remove everything from your config and test only this error page, maybe some other directive is causing this (like other location is used instead).

How to handle "OPTIONS *" request in nginx?

In my environment, I use perlbal to redirect request to nginx. If verify_backend is on. perbal will send a "OPTIONS *" request to nginx, but the nginx response it as a bad request.
According to RFC2616:
If the Request-URI is an asterisk (""), the OPTIONS request is intended to apply to the ?server in general rather than to a specific resource. Since a server's communication options typically depend on the resource, the "" request is only useful as a "ping" or "no-op" type of method; it does nothing beyond allowing the client to test the capabilities of the server. For example, this can be used to test a proxy for HTTP/1.1 compliance (or lack thereof).
I think perlbal is trying to send this kind of request, but nginx can't handle this by default.
When I try to send a request "OPTIONS * HTTP/1.0", I always get "HTTP 400 bad request":
127.0.0.1 - - [18/Feb/2013:03:55:47 +0000] "OPTIONS * HTTP/1.0" 400 172 "-" "-" "-"
but it works on "OPTIONS / HTTP/1.0" option without asterisk requests :
127.0.0.1 - - [18/Feb/2013:04:03:56 +0000] "OPTIONS / HTTP/1.0" 200 0 "-" "-" "-"
How can I configure nginx to let it respond with http return 200 rather than HTTP return 400 ?
I know it's an overkill but one solution is to put HAProxy in front of it to just capture that OPTIONS request and then build your own response in HAProxy:
location * {
if ($request_method = OPTIONS ) {
add_header Content-Length 0;
add_header Content-Type text/plain;
return 200;
}
}
The only way I found to modify the behaviour in this case was to respond to 400 in general:
error_page 400 =200 /empty_reply.html;
You could just send empty responses to everything you cannot handle.
For whoever wants to try to solve this another way, you can simulate this requests with:
curl -X OPTIONS $yourserverip --request-target "*" --http1.1

Resources