When setting up Nginx as a reverse proxy for containerized applications, a common issue occurs where POST requests to API endpoints like /graphql
get unexpectedly converted to 301 redirects. This breaks client-side applications making AJAX requests, particularly in modern JavaScript frameworks.
The 301 redirect occurs because of Nginx's default behavior with URI processing. When Nginx sees /graphql
without a trailing slash, it assumes this should be a directory and automatically redirects to /graphql/
. This behavior makes sense for static content but causes problems with API endpoints.
Here's the corrected Nginx configuration that solves this issue:
upstream graphql-upstream {
least_conn;
server graphql:3000;
keepalive 64;
}
server {
listen 80;
server_name domain.com www.domain.com;
location / {
proxy_pass http://frontend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
}
location /graphql {
proxy_pass http://graphql-upstream;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Important: Disable redirect behavior
proxy_redirect off;
# CORS headers
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
}
}
1. proxy_redirect off: This directive prevents Nginx from modifying the Location header in 3xx responses
2. Simplified proxy_pass: Using just the upstream name without the path segment
3. Consistent Host headers: Ensuring headers are properly passed through
After applying these changes, test with curl:
curl -X POST \
http://domain.com/graphql \
-H 'Content-Type: application/json' \
-d '{"query":"{ hello }"}'
You should now receive a direct response from your GraphQL service instead of a redirect.
For production environments, consider adding:
- SSL termination
- Request rate limiting
- Proper CORS configuration
- Health checks for the upstream services
When setting up Nginx as a reverse proxy for containerized Node.js applications, a common issue arises where POST requests to endpoints like /graphql
get unexpectedly redirected with 301 status codes. This behavior breaks API clients expecting direct responses.
# Bad behavior example:
POST http://domain.com/graphql → 301 → http://domain.com/graphql/
The issue occurs because Nginx automatically adds trailing slashes to locations when:
- The
proxy_pass
directive contains a URI path component - No exact match exists for the requested URL
- The location block doesn't explicitly handle trailing slash behavior
Here's the corrected configuration that prevents unwanted redirects:
location /graphql {
# Remove the path from proxy_pass target
proxy_pass http://graphql-upstream;
# Required headers for GraphQL
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# CORS headers
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
# Connection upgrades
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_cache_bypass $http_upgrade;
}
# Alternative exact match version
location = /graphql {
proxy_pass http://graphql-upstream;
# ... same headers as above
}
The working solution makes these critical changes:
- Removes the
/graphql
path from the upstream target - Uses either standard location or exact match (
=
) syntax - Maintains all necessary proxy headers and CORS support
Verify with curl commands:
# Test POST request (should return 200, not 301)
curl -X POST http://domain.com/graphql \
-H "Content-Type: application/json" \
-d '{"query":"{ test }"}'
For production environments, consider adding:
# Rate limiting
limit_req_zone $binary_remote_addr zone=graphql:10m rate=10r/s;
# Timeout settings
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;