Restrict allowed HTTP methods in Nginx

Security vulnerabilities are often exploits of software that fails when trying to deal with unexpected input. Other times they are exploits of a misconfiguration or a service that unintentionally was open to the public.

For the above reasons, we should limit as much as possible what services are exposed to the public and limit as much as possible what they do and accept from the visitors. To follow those security principles, we should only allow the HTTP methods for which we, in fact, provide services. Under all normal circumstances, that would be the methods GET, POST and HEAD.

Add the following lines to your configuration, either in your server block to make the restriction global to your website, or in a location block to restrict only that part of your website.


Update June 14, 2016:

Skip the rest of this article. The method described here is not the best way to implement this. As sayetan points out in a comment: There is a limit_except directive you should use instead.


Whitelist allowed HTTP methods

add_header Allow "GET, POST, HEAD" always;
if ( $request_method !~ ^(GET|POST|HEAD)$ ) {
	return 405;
}

This configuration will return the HTTP status 405 Method Not Allowed for all requests that try a method not specified in our whitelist.

The HTTP spec states we MUST include an Allow header when sending the 405 response code, so we do that for all requests. Technically, we only need to send the Allow header on the 405 responses, but if you know a way to use the add_header statement within an if, please let me know.


Update December 15, 2015:
Christian Bricart reached out to me on Twitter, with an approach which let you set the Allow header only on 405-responses:

error_page 405 @error405;
location @error405 {
	add_header Allow "GET, POST, HEAD" always;
}

Different allowed HTTP methods for different request URIs

Note that if you are providing an API that use other request methods – like the WordPress REST API – you need a much more advanced configuration to allow certain request methods, e.g. PUT and DELETE, to specific endpoints. If you’re not 100% sure what you are doing – or getting yourself into – you better drop it.

To have a match on specific request URIs and request methods without using location blocks, we have to overcome the limitation in Nginx configuration that it supports neither nested ifs nor any type of the OR or AND conditional operators. You can do that by using maps and a “clever hack” in the configuration, similar to what I show in the post Restrict access to the WordPress dashboard by IP address in Nginx. I strongly suggest you use maps for at least the request URIs.

13 Comments

  1. 1. Thank you for the article!
    2. I’ve just tried to add header inside the ‘if’ block and it works perfectly for me!

    if ( $request_method !~ ^POST$ ) {
    add_header Allow “POST” always;
    return 405;
    }

    nginx version: openresty/1.9.3.2

    1. Thanks a lot, sayetan! I wasn’t aware of the limit_except directive, which clearly is a cleaner way of achieving this.

    2. Using limit_except works, but it returns `403 Forbidden`. How can I make it return `405 Method Not Allowed`?

      1. if you wish `405 Method Not Allowed`,
        you’ve got just replace ‘deny all;’ by ‘return 405;’

        1. Return does not seem to work within limit_excerpt
          > “return” directive is not allowed here
          Besides, if one wants to return 444 no response, the first method is still required.

  2. Hello,

    i know that a long time but, just to clarify some points :

    nginx devs do not recommend to use directive “if” when used in “location” context.

    If we are in “server” context, it’s good to use directive “if” like this :
    if ($request_method !~ ^(GET|HEAD)$ ) {
    return 405;
    }

    But, if we are in “location” context, it’s better to use directive ” like this :
    location ^~ /members {
    limit_except GET {
    deny all;
    }
    }

    With “limit_except”, don’t forget that allowing the GET method makes the HEAD method also allowed.

    So, it’s depend where we are in our Nginx configuration.

    And if you wish `405 Method Not Allowed` (and not `403 Forbidden`), just replace ‘deny all;’ by ‘return 405;’

    Best regards and thank you for your great post (and blog).

  3. Hello,

    i know that a long time but, just to clarify some points :

    nginx devs do not recommend to use directive “if” when used in “location” context.

    If we are in “server” context, it’s good to use directive “if” like this :
    if ($request_method !~ ^(GET|HEAD)$ ) {
    return 405;
    }

    But, if we are in “location” context, it’s better to use directive ” like this :
    location ^~ /members {
    limit_except GET {
    deny all;
    }
    }

    With “limit_except”, don’t forget that allowing the GET method makes the HEAD method also allowed.

    So, it’s depend where we are in our Nginx configuration.

    Best regards and thank you for your great post (and blog).

  4. nginx devs do not recommend to use directive “if” when used in “location” context.

    That’s actually not true and shows you don’t understand the problem with it.
    When the “if” statement ends with a “return” directive, there is no problem and it’s safe to use.

    1. Humm, what ardour Roderick ;-)

      Just to clarify :
      I never writen do not use ‘if’ in location context but just answered by quoting the sentence of the author of the post above (“sayetan”): “nginx devs do not recommend to use directive “if” when used in “location” context.”

      if you had read my answer in context, it’s written both safe solution :
      – into location : with IF and RETURN so it’s a safe solution of course (in my original answer it’s written : it’s good to use directive “if” like this…),

      – into location context : it’s better to use ‘limit except’ than ‘if’ as explain by nginx dev :

      if we are in “location” context, it’s better to use directive ” like this :
      location ^~ /members {
      limit_except GET {
      deny all;
      }
      }

      with “limit_except”, don’t forget that allowing the GET method makes the HEAD method also allowed.

      In add, i answered to “Sander”, who wanted to use ‘limit_except’ but had an error, and how to fix it.

      Really sorry if my answer didn’t seem clear to you but i think it’s good now.

  5. I was trying to disable HEAD method and tried “limit_except” in location block
    but allowed GET method so I was getting HEAD too. So, I tried following method in location block by adding “add_header Allow “GET, POST, OPTIONS” always;”

  6. I am new to nginx, having mostly worked with IIS (and web.config) in the past. I appear to be in a situation where I have POSTs to my / URL returning a 502 Bad Gateway error.

    I’d like to allow these POSTs (they are the final step in a payment process between my site and PayPal, where PayPal is returning the user to my site)

    What should I put in my nginx.conf file to allow these? Currently, the entirety of my file is:

    user nginx;
    worker_processes auto;

    # send nginx error logs to stderr
    error_log /dev/stderr error;
    pid /var/run/nginx.pid;

    events {
    worker_connections 10000;
    }

    http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    log_format main ‘$remote_addr – $remote_user [$time_local] “$request” ‘
    ‘$status $body_bytes_sent “$http_referer” ‘
    ‘”$http_user_agent” “$http_x_forwarded_for”‘;

    access_log off;
    sendfile on;

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/modules-enabled/*.conf;

    #redirect www->nonwww
    server {
    server_name http://www.mysite.com;
    return 301 $scheme://mysite.com$request_uri;
    }
    }

Comments are closed.