Fix django/nginx flacky 502 error: upstream prematurely closed
I have a django + nginx application. I am making http request. The request contains a heavy database request (takes about 10 seconds)
Sometimes I see an error in nginx:
2026/03/31 14:47:12 [error] 39#39: *77449 upstream prematurely closed connection while reading response header from upstream, client: <MY HOST>, server: 127.0.0.1, request: "GET /filter_xmlstock/?film_name=%D0%A0%D0%B5%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5%20%D0%BF%D0%B0%D1%86%D0%B0%D0%BD%D1%8B&datetime_from=2026-03-25T00:00:00Z&datetime_to=2026-03-31T23:59:00Z HTTP/1.1", upstream: "http://MY_HOST:8000/filter_xmlstock/?film_name=%D0%A0%D0%B5%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5%20%D0%BF%D0%B0%D1%86%D0%B0%D0%BD%D1%8B&datetime_from=2026-03-25T00:00:00Z&datetime_to=2026-03-31T23:59:00Z", host: "HOST"
On the client side I see
<html>
<head>
<title>502 Bad Gateway</title>
</head>
<body>
<center>
<h1>502 Bad Gateway</h1>
</center>
<hr>
<center>nginx/1.24.0</center>
</body>
</html>
I have read that the error deals with timeout, but my nginx config timeout is 300 seconds (greater than response time)
upstream app {
server django:8000;
}
server {
listen 443 ssl;
server_name 127.0.0.1 content-protect-premier3.gpmdi.ru content-protect.mgns-tech.ru www.content-protect.mgns-tech.ru;
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,Keep-Alive,X-Requested-With,If-Modified-Since,X-token' always;
ssl_certificate /etc/nginx/ssl/content-protect.crt;
ssl_certificate_key /etc/nginx/ssl/content-protect.key;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
ssl_session_tickets off;
# intermediate configuration
ssl_protocols TLSv1.2;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers on;
# HSTS (ngx_http_headers_module is required) (63072000 seconds)
#add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
# Timeout settings
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
send_timeout 300s;
location / {
proxy_pass http://app;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Protocol $scheme;
# Additional timeout settings for this location
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
}
location /static/ {
alias /var/www/html/static/;
}
location /media/ {
alias /var/www/html/media/;
}
}
Django endpoint code
@api_view(['GET'])
def filter_xmlstock(request):
"""
Filter XmlStockResult objects by film_name, datetime_from, datetime_to
@ExampleRequest:
{
"film_name": "Some film name",
"datetime_from": "2025-08-21T00:00:00Z",
"datetime_to": "2025-08-27T23:59:59Z",
"output_filename": "optional_custom_filename.csv"
}
"""
serializer = FilterXmlStockSerializer(data=request.query_params)
if not serializer.is_valid():
return Response({
"status": "Error",
"message": "Invalid request data",
"errors": serializer.errors
}, status=400)
validated_data = serializer.validated_data
film_name = validated_data['film_name']
datetime_from = validated_data['datetime_from']
datetime_to = validated_data['datetime_to']
output_filename = validated_data.get('output_filename')
# Convert datetime objects back to ISO strings for the function
datetime_from_str = datetime_from.isoformat().replace('+00:00', 'Z')
datetime_to_str = datetime_to.isoformat().replace('+00:00', 'Z')
try:
file_path = filter_xmlstock_results(film_name, datetime_from_str, datetime_to_str, output_filename)
# Ensure leading slash for media path
if not file_path.startswith('/'):
file_url_path = f"/{file_path}"
else:
file_url_path = file_path
public_url = f"https://{os.getenv('EXTERNAL_HOST')}{file_url_path}"
return Response({
"status": "Success",
"file_url": public_url
})
except Exception as e:
return Response({
"status": "Error",
"message": f"Failed to filter and export data: {str(traceback.format_exc())}"
}, status=500)