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 enclosinglocation
- 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