Nginx PHP Failing with Large File Uploads (Over 6 GB) Nginx PHP Failing with Large File Uploads (Over 6 GB) nginx nginx

Nginx PHP Failing with Large File Uploads (Over 6 GB)


A large file upload is an expensive and error prone operation. Nginx and backend should have correct timeout configured to handle slow disk IO if occur. Theoretically it is straightforward to manage file upload using multipart/form-data encoding RFC 1867.

According to developer.mozilla.org in a multipart/form-data body, the HTTP Content-Disposition general header is a header that can be used on the subpart of a multipart body to give information about the field it applies to. The subpart is delimited by the boundary defined in the Content-Type header. Used on the body itself, Content-Disposition has no effect.

Let's see what happens while file being uploaded:

1) client sends HTTP request with the file content to webserver

2) webserver accepts the request and initiates data transfer (or returns error 413 if the file size is exceed the limit)

3) webserver starts to populate buffers (depends on file and buffers size)

4) webserver sends file content via file/network socket to backend

5) backend authenticates initial request

6) backend reads the file and cuts headers (Content-Disposition, Content-Type)

7) backend dumps resulted file on to disk

8) any follow up procedures like database changes

client_body_in_file_only off;

During large files upload several problems occur:

  • the HTTP body request dumps on to disk and passes to backend which process and copy the file
  • not possible to authenticate request before HTTP request content is uploaded to server
  • while upload large files backend rarely requires a file content itself immediately

Let's start with Nginx configured with new location http://backend/upload to receive large file upload, back-end interaction is minimised (Content-Legth: 0), file is being stored just to disk. Using buffers Nginx dumps the file to disk (a file stored to the temporary directory with random name, it can not be changed) followed by POST request to backend to location http://backend/file with the file name in X-File-Name header.

To keep extra information you may use headers with initial POST request. For instance, having X-Original-File-Name headers from initial requests help you to match file and store necessary mapping information to the database.

client_body_in_file_only on;

Let's see how make it happen:

1) configure Nginx to dump HTTP body content to a file and keep it stored client_body_in_file_only on;

2) create new backend endpoint http://backend/file to handle mapping between temp file name and header X-File-Name

4) instrument AJAX query with header X-File-Name Nginx will use to send post upload request with

Configuration:

location /upload {  client_body_temp_path      /tmp/;  client_body_in_file_only   on;  client_body_buffer_size    1M;  client_max_body_size       7G;  proxy_pass_request_headers on;  proxy_set_header           X-File-Name $request_body_file;   proxy_set_body             off;  proxy_redirect             off;  proxy_pass                 http://backend/file;}

Nginx configuration option client_body_in_file_only is incompatible with multi-part data upload, but you can use it with AJAX i.e. XMLHttpRequest2 (binary data).

If you need to have back-end authentication, only way to handle is to use auth_request, for instance:

location = /upload {  auth_request               /upload/authenticate;  ...}location = /upload/authenticate {  internal;  proxy_set_body             off;  proxy_pass                 http://backend;}

client_body_in_file_only on; auth_request on;

Pre-upload authentication logic protects from unauthenticated requests regardless of the initial POST Content-Length size.