How to Properly Restrict HTTP Methods in Nginx Using limit_except (Allow Only GET, POST, HEAD)


2 views

Many developers use if statements in Nginx to restrict HTTP methods, like this:

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

While this works, it's problematic because:

  • Nginx documentation specifically warns against using if in certain contexts
  • It can lead to unexpected behavior in complex configurations
  • It's not the most performant solution

Nginx provides a dedicated directive for this purpose:

location / {
    limit_except GET POST HEAD {
        deny all;
    }
    
    # Your regular location configuration
    try_files $uri $uri/ /index.php?$args;
}

Here's how this fits into a complete server block with HTTPS and www redirection:

server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://www.example.com$request_uri;
}

server {
    listen 443 ssl;
    server_name example.com;
    
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
    
    return 301 https://www.example.com$request_uri;
}

server {
    listen 443 ssl;
    server_name www.example.com;
    
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
    
    root /var/www/html;
    index index.php;
    
    location / {
        limit_except GET POST HEAD {
            deny all;
        }
        
        try_files $uri $uri/ /index.php?$args;
    }
    
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.1-fpm.sock;
    }
}

You can customize the error response:

error_page 405 = @method_not_allowed;

location @method_not_allowed {
    add_header Allow "GET, POST, HEAD" always;
    return 405 "Method Not Allowed";
}
  • The limit_except block only applies to the enclosing location
  • It doesn't affect proxy_pass or fastcgi_pass directives
  • Make sure to test your API endpoints if your site has any
  • Consider adding proper CORS headers if you need to support AJAX requests

For WordPress specifically, you might need to adjust this as some plugins use additional methods. Here's a WordPress-compatible version:

location / {
    limit_except GET POST HEAD PUT DELETE {
        deny all;
    }
    
    try_files $uri $uri/ /index.php?$args;
}

Many developers resort to using if statements with regex patterns to filter HTTP methods in Nginx:

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

While functional, this approach violates Nginx's documented "if is evil" principle and may cause unpredictable behavior in complex configurations.

The correct solution uses Nginx's native limit_except directive within location blocks:

location / {
    limit_except GET POST HEAD {
        deny all;
    }
    
    # Your regular configuration
    try_files $uri $uri/ /index.php?$args;
}

Here's a full production-ready configuration including HTTPS and www redirection:

server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://www.example.com$request_uri;
}

server {
    listen 443 ssl http2;
    server_name example.com;
    
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
    
    return 301 https://www.example.com$request_uri;
}

server {
    listen 443 ssl http2;
    server_name www.example.com;
    
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
    
    root /var/www/html;
    index index.php;
    
    location / {
        limit_except GET POST HEAD {
            deny all;
        }
        
        try_files $uri $uri/ /index.php?$args;
    }
    
    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}

To customize the error response for disallowed methods:

error_page 405 = @method_not_allowed;

location @method_not_allowed {
    add_header Allow "GET, POST, HEAD";
    return 405 "Method Not Allowed";
}

While limit_except improves security by restricting methods, remember to:

  • Combine with other security headers
  • Implement proper CORS policies if needed
  • Consider rate limiting for POST requests