Nginx Reverse Proxy Routing by HTTP Method (GET/POST) to Different Backends


1 views

When building RESTful services, we often need to route requests to different backend processors based on HTTP methods. A common scenario involves:

  • GET requests going to a caching layer or read-only API
  • POST requests routed to write-processing services
  • Other methods (PUT/DELETE) handled by specific microservices

Here's how to implement method-based routing in Nginx:


server {
    listen 80;
    server_name api.example.com;

    location /resources {
        # Handle GET requests
        if ($request_method = GET) {
            proxy_pass http://read_backend;
            break;
        }

        # Handle POST requests
        if ($request_method = POST) {
            proxy_pass http://write_backend;
            break;
        }

        # Default fallback
        proxy_pass http://default_backend;
    }
}

For more complex routing logic, use the map directive:


map $request_method $backend {
    default     http://default_backend;
    GET         http://read_backend;
    POST        http://write_backend;
    PUT         http://update_backend;
    DELETE      http://delete_backend;
}

server {
    location /api {
        proxy_pass $backend;
    }
}

When implementing method-based routing:

  • Nginx evaluates if conditions sequentially - place most frequent methods first
  • Map directives are evaluated during configuration load - better for performance
  • Consider connection pooling between Nginx and backends

Combining proxy and FastCGI routing:


location /data {
    if ($request_method = GET) {
        proxy_pass http://cache_server;
    }
    
    if ($request_method = POST) {
        fastcgi_pass unix:/var/run/php-fpm.sock;
        include fastcgi_params;
    }
    
    # Return 405 for unsupported methods
    return 405;
}

Always include proper error responses:


location /api {
    # ... method routing logic ...

    # Method not allowed
    error_page 405 = @method_not_allowed;
    location @method_not_allowed {
        add_header Allow "GET, POST";
        return 405 '{"error": "Method not allowed"}';
    }
}

Use curl to verify routing:


# Test GET routing
curl -X GET http://api.example.com/resources

# Test POST routing 
curl -X POST http://api.example.com/resources -d '{"data":"value"}'

In RESTful API design, it's common practice to use the same URI with different HTTP methods for different operations. A typical scenario is using:

  • GET /resource - for retrieving data
  • POST /resource - for creating data

However, these operations might need to be handled by different backend services due to technical requirements (like using HTTP proxy for GETs and FastCGI for POSTs). Nginx provides elegant solutions for this routing requirement.

Here's the simplest way to handle different methods in Nginx:

location /api/resource {
    if ($request_method = GET) {
        proxy_pass http://get_backend;
    }
    
    if ($request_method = POST) {
        proxy_pass http://post_backend;
    }
    
    # Handle other methods
    if ($request_method !~ ^(GET|POST)$ ) {
        return 405;
    }
}

For more complex scenarios with multiple methods, use the map directive:

map $request_method $backend {
    default        http://default_backend;
    GET           http://get_backend;
    POST          http://post_backend;
    PUT           http://put_backend;
    DELETE        http://delete_backend;
}

server {
    location /api/ {
        proxy_pass $backend;
    }
}

When you need to mix proxy and FastCGI based on methods:

location /api/resource {
    if ($request_method = GET) {
        proxy_pass http://backend_server;
        break;
    }
    
    if ($request_method = POST) {
        fastcgi_pass unix:/var/run/php-fpm.sock;
        include fastcgi_params;
        break;
    }
}

Always include proper error handling:

location /api/resource {
    limit_except GET POST {
        deny all;
    }
    
    if ($request_method = GET) {
        proxy_pass http://get_backend;
        proxy_intercept_errors on;
        error_page 502 503 504 = @fallback;
    }
    
    if ($request_method = POST) {
        fastcgi_pass unix:/var/run/php-fpm.sock;
        fastcgi_intercept_errors on;
        error_page 502 503 504 = @fallback;
    }
}

location @fallback {
    proxy_pass http://fallback_server;
}

When implementing method-based routing:

  • Use map directives for better performance with many rules
  • Minimize if blocks when possible - they have performance overhead
  • Consider using separate location blocks with limit_except when appropriate

Here's a complete example handling a RESTful user resource:

map $request_method $user_backend {
    default        http://default_api;
    GET           http://user_service:8001;
    POST          http://auth_service:8002;
    PUT           http://user_service:8001;
    DELETE        http://admin_service:8003;
}

server {
    listen 80;
    
    location /users/ {
        limit_except GET POST PUT DELETE {
            deny all;
        }
        
        proxy_pass $user_backend;
        proxy_set_header X-Real-IP $remote_addr;
        
        # Common proxy settings
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}