4

I'm serving my website using Nginx as web server. I offer upload functionality to my users (they are allowed to submit pictures up to 5Mb) so I have the directive: client_max_body_size 5M; in my server config. What I've noticed is that if I try to upload any file the webserver does not prevent the upload of bigger files. Just as example, suppose I try to upload a really big video (a movie) of 700Mb. The server does not reject the upload instantaneously but it buffers the whole data (taking so long and slowing down the server) and only at the end of the upload it returns a 413 Request entity too large error.

So the question is: is there any way to properly configure Nginx to block big files upload when the transmitted data starts to overcome my client_max_body_size limit?

I think it would be really insecure to go on production with my actual settings and I wasn't able to find anything helpful on google.

Edit:

I'm using php and Symfony2 as backend...

Edit (again):

This is what appears in my error.log:

2013/01/28 11:14:11 [error] 11328#0: *37 client intended to send too large body: 725207449 bytes, client: 33.33.33.1, server: www.local.example.com, request: "POST /app_dev.php/api/image/add/byuploader HTTP/1.1", host: "local.example.com", referrer: "http://local.example.com/app_dev.php/"`

The weird things is that i am monitoring my nginx error.log with tail -f error.log and the message appears immediately when the upload starts (before it ends). So nginx makes some kind of preventive check, but it doesn't stop/chunk the upload request...

I also tried to verify if php gets the control of the upload by issuing a echo 'something'; die(); on the page who handles the upload but it didn't printed out anything and didn't stopped the upload request. So it should be nginx or some php internals that make the whole upload continue until it's completely transmitted...

another edit: here's a view of my top (monitoring nginx and php) when a big file upload is in progress (nginx gets overloaded): top: monitoring php and nginx

edit: here's my nginx configuration

server {
    listen   80;
    server_name www.local.example.com local.example.com;
    access_log /vagrant/logs/example.com/access.log;
    error_log /vagrant/logs/example.com/error.log;
    root   /vagrant/example.com/web;
    index app.php;
    client_max_body_size 6M;

    location /phpmyadmin {
        root /usr/share;
        index index.php;
        location ~* \.php {
            fastcgi_intercept_errors on;
            fastcgi_pass   127.0.0.1:9000;
            fastcgi_split_path_info ^(.+\.php)(/.*)$;
            include fastcgi_params;
            fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;
            fastcgi_param  HTTPS              off;
        }
    }

    location / {
        try_files $uri @rewriteapp;
    }

    location @rewriteapp {
        rewrite ^(.*)$ /app.php/$1 last;
    }

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    location ~ ^/(app|app_dev)\.php(/|$) {
        fastcgi_intercept_errors on;
        fastcgi_pass   127.0.0.1:9000;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;
        fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;
        fastcgi_param  HTTPS              off;
    }
}
Luciano Mammino
  • 181
  • 1
  • 10
  • 4
    Are you sure it's Nginx that's buffering the file? – David Schwartz Jan 28 '13 at 10:46
  • No, how can I be sure of this? – Luciano Mammino Jan 28 '13 at 10:50
  • Updated my question with a specific test to verify if it's an nginx or php behavior that handles upload this way... – Luciano Mammino Jan 28 '13 at 11:23
  • 2 things, if you want to make a image uploader, you sould check the fileextension and only allow image extencions. you should doublecheck the size. it sounds like php does not give up and forces somehow to load up the file. – Harrys Kavan Jan 28 '13 at 11:27
  • 1
    @LucianoMammino: Is is the nginx process that's using the memory? It's memory consumption that's the problem, right? – David Schwartz Jan 28 '13 at 11:37
  • As you can see I can't do any check from php, it seems to never reach php... I also tried to put the `echo "something"; die();` as first line of the symfony front controller (to skip symfony internals) and, again, the request wasn't stopped... So I conclude it's not my php code fault. It should be some nginx/php internals... – Luciano Mammino Jan 28 '13 at 11:37
  • @DavidSchwartz honestly i don't know how to check if it's nginx or php-fm that consumes up the memory. It's not only about memory consumption but also about slowing down the whole server (many users could issue big file uploads simultaneously) – Luciano Mammino Jan 28 '13 at 11:41
  • @DavidSchwartz, added a screenshoot... – Luciano Mammino Jan 28 '13 at 11:57
  • nginx only has like 2.5MB resident. So it's not an nginx problem. – David Schwartz Jan 28 '13 at 12:02
  • @Luciano you should try to improve you php code, i guess it's php blocking the abort of the ngnix – Harrys Kavan Jan 28 '13 at 12:14
  • @DavidSchwartz so, are you saying it's a php5-cgi problem? how to dig further? I have no clue on how configuring php-cgi (if it creates the problem) to avoid this issue... – Luciano Mammino Jan 28 '13 at 12:14
  • @derty how can my php code blocking nginx abort? What configuration value should I check? – Luciano Mammino Jan 28 '13 at 12:16
  • No configuration, try to improve your code, so php checks if the file is to big, and if it is so, do not start the upload. – Harrys Kavan Jan 28 '13 at 12:19
  • @derty: I don't think I can actually perform any preventive check from php. As I said, I have added a `die()` that should have stopped everything (despite of the uploaded file) but the upload kept running until the end and then i got the default 413 nginx error page... My php code seems to not being executed at all. I'm really welcome to some idea about how to properly test this behavior (if you think my test is not appropriate)... – Luciano Mammino Jan 28 '13 at 12:25
  • Your application do have a formular or some kind of upload interface. And before this interface gives the command to start the upload, you should verify (as part as a "validation" if the file that is going to be uploaded is aceptable for you). If it is bigger then xMB you should give an error (file is to large). Do you understand? – Harrys Kavan Jan 28 '13 at 12:28
  • Thanks @derty, I just used symfony2 default upload code (closer to the one shown in the response here: http://stackoverflow.com/questions/11037115/symfony2-allocates-uploaded-file-to-memory) but nothing changed... it still seems to never hit php code! – Luciano Mammino Jan 28 '13 at 19:15

2 Answers2

3

Useful information here: https://stackoverflow.com/questions/4947107/nginx-upload-client-max-body-size-issue

I quote part of the most interesting answer (for my case):

Most clients don't read for responses until the entire request body is sent. Because nginx closes the connection the client sends data to the closed socket, causing a TCP RST.

If so it's only a browser problem that will not affect (overload) my web server. It seems to complain with my memory inspection that showed both nginx and php-fastcgi haven't been truly overloaded when uploading a large (700mb) file.

I could address the browser problem in several ways, for example:

  • add a <input type="hidden" name="MAX_FILE_SIZE" value="5242880" /> in my form
  • use a custom 413 nginx error page
Luciano Mammino
  • 181
  • 1
  • 10
0

What are you using php or rails or something? because you should check the size before uploading.
set post_max_size and upload_max_filesize to corresponding value in file:php.ini

edit: try to write something like this to your upload

$element = new Zend_Form_Element_File('foo');
$element->setLabel('Upload a file:')
        ->setDestination('/var/www/upload');
// make sure its only 1 file
$element->addValidator('Count', false, 1);
// Maximal 100k
$element->addValidator('Size', false, 102400);
// only JPEG, PNG, and GIFs
$element->addValidator('Extension', false, 'jpg,png,gif');
$form->addElement($element, 'foo');
Harrys Kavan
  • 402
  • 1
  • 5
  • 18
  • I have the following settings for these 2 config values: `upload_max_filesize => 5M => 5M` and `post_max_size => 8M => 8M` (taken with `php -i` from command line) – Luciano Mammino Jan 28 '13 at 10:54
  • have a look at the .htaccess in the root folder of you website. there might be some more modifications to. the definitions in the .htaccess are more dominant IF there are some. Are you hiding your Website behind a proxy? – Harrys Kavan Jan 28 '13 at 10:56
  • I do use nginx so no .htaccess files :) – Luciano Mammino Jan 28 '13 at 10:58
  • 1
    I would reupload a large sized file and have a look at the log's. try to find the message where it stops to upload and have at this information. Feel free to post it here so we might find out a little more – Harrys Kavan Jan 28 '13 at 11:05
  • Just updated my question to add additional informations about what appears in my logs – Luciano Mammino Jan 28 '13 at 11:22