34

Is it possible/how can I configure an Nginx location block to proxy to different backends depending on the request method (ie. GET/POST)?

The reason is, I am currently handling the 2 methods at 2 different URLs (one via http proxy and the other via fcgi) and am trying to make it more "REST"ful so, would ideally like the GETting the resource to return the list, while POSTing to the same resource should add to the list.

1

6 Answers 6

45

I don't use this configuration, but based on the examples here:

location /service  {
  if ($request_method = POST ) {
    fastcgi_pass 127.0.0.1:1234;
  }

  if ($request_method = GET ) {
     alias /path/to/files;
  }
}

If your writing your own application, you can also consider checking GET/POST in it, and sending X-Accel-Redirect headers to hand off transport of the files to nginx.

5
  • The GET block is a proxy_pass in my case, but otherwise that works. At the moment I'm not using the second if block, nginx appears to be stopping "processing" when the fastcgi_pass directive is reached (ie. not falling through and running the proxy pass as well) because I want anything other than POST to revert to the proxy. Commented Jun 20, 2010 at 11:51
  • 4
    Note that if is generally discouraged by the Nginx documentation: nginx.com/resources/wiki/start/topics/depth/ifisevil
    – vog
    Commented Dec 28, 2016 at 17:07
  • 1
    So, what is the alternative?
    – W.M.
    Commented Sep 13, 2017 at 9:51
  • 1
    @W.M. See my answer: serverfault.com/a/823053/175421
    – vog
    Commented Dec 14, 2017 at 8:45
  • @vog, Interesting. Pretty smart way to do it. Thanks for sharing.
    – W.M.
    Commented Dec 14, 2017 at 12:53
32

Although you could achieve this with if, this is generally discouraged by the Nginx documentation, because if doesn't play well with other directives. For example, assume that GET should be open for everyone, while POST is only for authenticated users, using HTTP Basic Auth. That would require if to be combined with auth_basic, which doesn't work properly.

Here is an alternative that works without if. The trick is to use "GET" and "POST" as part of the upstream names, so these can be addressed by variable substitution:

http {
  upstream other_GET {
    server ...;
  }
  upstream other_POST {
    server ...;
  }
  server {
    location /service {
      proxy_pass http://other_$request_method;
    }
  }
}

To combine this with HTTP Basic Auth for everything but GET, just add a limit_except block:

  ...
    location /service {
      proxy_pass http://other_$request_method;
      limit_except GET {
        auth_basic ...;
      }
    }
  ...
3
  • 1
    The problem with this approach is now we will return 502 gateway error because of no resolver defined to resolve other_HEAD (or whatever your missing upstream is). It will be more semantic to return something like 405 method not allowed. Is there a way to accomplish this?
    – James
    Commented Nov 28, 2019 at 19:20
  • 1
    @James: This sould perhaps be phrased as a new question, referring to this one. I don't have an answer for this detail, but maybe others to.
    – vog
    Commented Dec 2, 2019 at 15:30
  • @James: Thinking more of it, repeating upstream other_GET {...} as upstream other_HEAD {...} should do the trick, even though this means some duplication in your nginx configuration.
    – vog
    Commented Jan 16, 2020 at 15:52
9

I couldn't get the answer from @timmmmmy to work, but it pointed me to the map documentation and this worked for me:

map $request_method $upstream_location {
   PUT     example.com:8081;
   POST    example.com:8081;
   PATCH   example.com:8081;
   default example.com:8082;
}
server {
   location / {
      proxy_pass https://$upstream_location;
   }
}
1

Slight change to vog's answer to include a default handler for other methods like OPTIONS, PUT, etc.

    upstream webdav_default {
            server example.com;
    }
    upstream webdav_upload {
            server example.com:8081;
    }
    upstream webdav_download {
            server example.com:8082;
    }
    server {
            map upstream_location $request_method {
                    GET     webdav_download;
                    HEAD    webdav_download;
                    PUT     webdav_upload;
                    LOCK    webdav_upload;
                    default webdav_default;
            }
            location / {
                    proxy_pass https://$upstream_location;
            }
    }
0

This is what i did to make things working for me

add_header Allow "GET, POST, HEAD" always;
if ( $request_method !~ ^(GET|POST|HEAD)$ ) {
    proxy_pass http://back-end;
}
1
  • Hoe exactly does that switch between two endpoints based on request method?
    – Basic
    Commented Feb 10, 2019 at 14:15
0

@timmmmmy 's answer with the fixed syntax errors (without edit because other person refers to the original answer)

        upstream webdav_default {
                server ...;
        }
        upstream webdav_upload {
                server ...;
        }
        upstream webdav_download {
                server ...;
        }
        map $request_method $upstream_location {
            GET     webdav_download;
            HEAD    webdav_download;
            PUT     webdav_upload;
            LOCK    webdav_upload;
            default webdav_default;
        }
        server {
            location / {
                proxy_pass https://$upstream_location;
            }
        }

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .