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)

Вернуться на верх