How to Preserve POST Data When Redirecting Requests in Nginx


1 views

When dealing with HTTP POST requests, the standard Nginx rewrite rule using 301/302 redirects will cause the POST data to be lost. This happens because:

rewrite ^/(.*)$ http://newdomain.com/$1 permanent;

The browser converts POST to GET during the redirect - this is standard HTTP behavior. What we need is a true proxy pass that maintains the original request method and body.

Instead of rewrite, use Nginx's proxy module to forward the complete request:


location / {
    proxy_pass http://newdomain.com;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

For more complex scenarios with HTTPS and custom headers:


location /api/ {
    proxy_pass https://api.newdomain.com/;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_set_header Content-Type $content_type;
    proxy_set_header Authorization $http_authorization;
    proxy_pass_request_body on;
    proxy_pass_request_headers on;
}

To maintain the original request characteristics:


location / {
    proxy_method $request_method;
    proxy_pass_request_headers on;
    proxy_pass_request_body on;
    proxy_pass http://backend;
    proxy_set_header X-Original-URI $request_uri;
}

When troubleshooting POST data forwarding:

  1. Check Nginx error logs: tail -f /var/log/nginx/error.log
  2. Verify headers with curl -v -X POST -d "test=data" http://yourdomain.com
  3. Use tcpdump to inspect raw traffic: tcpdump -i any port 80 -A

For high-traffic POST endpoints, consider adding:


proxy_buffers 16 32k;
proxy_buffer_size 64k;
proxy_busy_buffers_size 64k;
proxy_temp_file_write_size 64k;
client_max_body_size 10M;

When attempting to forward POST requests between domains using Nginx's rewrite directive with the permanent flag, you'll encounter an important limitation:

rewrite ^/(.*)$ http://mydomain/$1 permanent;

This approach converts POST requests to GET requests during the redirect, causing the request body to be lost. This happens because:

  • HTTP 301/302 redirects typically don't preserve the request method
  • The client browser or application initiates a new GET request after the redirect

To properly forward POST requests with their payload intact, we need to use Nginx's proxy_pass directive instead of rewrite:

server {
    listen 80;
    server_name domainA.com;
    
    location / {
        proxy_pass http://domainB.com;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        
        # Optional: Handle large POST bodies
        client_max_body_size 100M;
        proxy_request_buffering off;
    }
}

The critical components in this solution:

  • proxy_pass: Forwards requests to the target domain while preserving the original HTTP method
  • Header settings: Ensure proper request handling and tracing
  • client_max_body_size: Adjust for large file uploads
  • proxy_request_buffering: Can be disabled for better performance with large requests

If you must use redirects (not proxy passing), consider this approach for POST requests:

map $request_method $redirect_method {
    default permanent;
    POST redirect;
}

server {
    # ...
    rewrite ^/(.*)$ http://domainB.com/$1 $redirect_method;
}

While this still converts POST to GET, it at least makes the behavior explicit and allows different handling for different HTTP methods.

Use curl to verify POST data preservation:

curl -X POST http://domainA.com/api -d '{"test":"data"}' -H "Content-Type: application/json"

Check your domainB.com access logs to confirm the POST data arrives intact.

When using proxy_pass:

  • Enable keepalive connections to upstream
  • Consider adding proxy buffers for better throughput
  • Monitor connection metrics between servers
proxy_http_version 1.1;
proxy_set_header Connection "";
keepalive_timeout 75s;
keepalive_requests 1000;