I've set up Nginx and the upload module following Martin Fjordvald's guide, but whenever I submit a file to it, I receive Nginx's generic "404 Not Found" page. The file is not uploaded.

Here's my nginx.conf:

user              nginx nginx;
worker_processes  1;
worker_rlimit_nofile 20000;

pid               /var/run/nginx.pid;

events {
    worker_connections  2048;

http {
    include       mime.types;
    default_type  application/octet-stream;
    client_max_body_size 64M;
    sendfile        on;
    tcp_nopush      on;

    keepalive_timeout  3;

    gzip  on;
    gzip_comp_level 2;
    gzip_proxied any;
    gzip_types      text/plain text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript;

    server_tokens off;

    # Allocate RAM for upload modules
    upload_progress uploads 5m;

    include /etc/nginx/conf.d/*;

Files in conf.d (included in nginx.conf):


server {
    server_name  bitload.biz;
    return       301 http://www.bitload.biz$request_uri;

server {
    server_name www.bitload.biz;
    root /var/www/bitload.biz;
    include php;
    access_log  /var/www/bitload.biz-access.log;
    error_log  /var/www/bitload.biz-error.log;
    index index.php index.html index.htm;

    # Drop requests to non-PHP requests
    location ~ \.(aspx|jsp|cgi)$ {
        return 404;

        # Set up the upload handler
    # More info: http://blog.martinfjordvald.com/2010/08/file-uploading-with-php-and-nginx/
    location /upload {
      # Pass altered request body to this location
      upload_pass   @uploadhandler;

      # Store files to this directory
      # The directory is hashed, subdirectories 0 1 2 3 4 5 6 7 8 9 should exist
      upload_store /var/tmp/bitload_fuploads 1;

      # Allow uploaded files to be read only by user
      upload_store_access user:r group:r all:r;

      # Set specified fields in request body
      upload_set_form_field $upload_field_name.name "$upload_file_name";
      upload_set_form_field $upload_field_name.path "$upload_tmp_path";

      # Inform backend about hash and size of a file
      upload_aggregate_form_field "$upload_field_name.sha1" "$upload_file_sha1";
      upload_aggregate_form_field "$upload_field_name.size" "$upload_file_size";

      # This directive specifies any extra POST fields which should be passed along.
      #upload_pass_form_field "^fallback$|^login$|^usession$";

      upload_cleanup 400 404 499 500-505;

      track_uploads uploads 5s;

    # Set URL to redirect to after a user's upload finishes
    location @uploadhandler {
      rewrite ^ /processes/upload.php last;

        # Set up the upload progress handler
    # More info: http://blog.martinfjordvald.com/2010/08/file-uploading-with-php-and-nginx/
    location = /progress {
      report_uploads uploads;

    # Nice Download URLs
    location / {
        rewrite ^/download/([\d]*)\-.*$ /download.php?file=$1 last;

    # Redirect 404s to the homepage
    error_page 404 /index.php;



server {
    listen 80 default;
    server_name _;
    root /var/www/html;


server {
    server_name gigabyte.bitload.biz;
    root /var/www/html;

Here's the code that I'm using to submit the file upload (I have tried it with the generic example found on the module's homepage, however, it it still didn't work):


  <form method="post" name="uploadForm" id="upload" action="/upload/" enctype="multipart/form-data">
    <div class="inputrow">

      <div class="rowElem">
        <label for="name">Choose Category:</label>
        <span class="inputField">

          <select name="file_category" id="file_category">
            <?php echo getCategoryOptions();?>

        </span> </div>

      <div class="rowElem">

        <label for="contact_number">File Title:</label>
        <span class="inputField">
        <input name="file_title" id="file_title" class="required" type="text" title="Please enter a file title." />
        </span> </div>

      <div class="rowElem">
        <label for="payout_method_details">File Description:</label>
        <span class="inputField">
       <textarea name="file_description" rows="1" cols="1" class="tinymce" id="file_description" title="Please enter a file description." style="width:100px;"></textarea>
        </span> </div>

      <div class="rowElem">

        <label for="contact_number">File:</label>
        <span class="inputField">
            <input id="file_upload" name="file_upload" type="file" />
        </span> </div>

      <div class="rowElem">
        <input type="hidden" name="process" value="upload" />

        <input type="submit" name="submit_button" value="Upload" title="Upload" class="normal submitbutton" style="width:auto;" />
<div id="uploadProgress">
    <div id="uploadProgressBar"></div>
    <span id="percent" style="font-weight:bold;"></span><br /><br />
    <strong>Uploaded:</strong> <span id="received"></span>&nbsp;&nbsp;&nbsp;<strong>Speed:</strong> <span id="speed"></span>
<script type="text/javascript">

            [snip (TinyMCE init code)]

            $row = $ado->fetch($ado->exec("SELECT max_upload_file from template"));
                $file_size_mb = $row['max_upload_file']; // Get max file size
            } else {
                $file_size_mb = 10;
            $file_size = $file_size_mb*1024*1024; // Convert max file size to bytes

        // File Upload Handler (fired on form submit)
        // More information: http://blog.martinfjordvald.com/2010/08/file-uploading-with-php-and-nginx/
            var received = 0;
            var percent  = 0.0;
            var periodical;
            var uuid = Math.random().toString(36).substring(2); // Unique uploader ID
            var check = 20; // Milliseconds between each XHR request.
            var action = $("#upload").attr("action") + '?X-Progress-ID=' + uuid;

            //$("#upload").attr("action", action); // Assign ID to upload.

            periodical = setInterval(function(){
                   url: '/progress?X-Progress-ID=' + uuid, // Using same identifier!
                   dataType: 'json',
                   type: 'GET',
                   success: function(data) {
                        var json = JSON.decode(data);
                        if (json.state == 'uploading') {
                            var delta = json.received - received;
                            var bytes = delta / (check / 1000);
                            received = json.received;
                            percent = (json.received / json.size) * 100;

                            $("#uploadProgressBar").progressbar("option", "value", percent);
                            $('#percent').html = percent + '%';
                            $('#received').html = Math.round(json.received / 1024) + '/' + Math.round(json.size / 1024) + ' KB';
                            $('#speed').html = Math.round(bytes / 1024) + ' KB/s';

                            if (percent >= 100) {
                                clearInterval(periodical); // Upload done, stop polling Nginx.
            }, check);

            width: 500,
            modal: true,
            closeOnEscape: false,
            draggable: false,
            title: 'Uploading, please wait...',
            zIndex: 9999,
            resizable: false,
            open: function(event, ui) { $(".ui-dialog-titlebar-close").hide(); },
            autoOpen: false




Here's the output of nginx -V:

nginx version: nginx/1.0.14
built by gcc 4.4.6 20110731 (Red Hat 4.4.6-3) (GCC)
TLS SNI support enabled
configure arguments: --prefix=/etc/nginx/ --sbin-path=/usr/sbin/nginx --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-http_ssl_module --with-http_realip_module --with-http_addition_module --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gzip_static_module --with-http_random_index_module --with-http_secure_link_module --with-http_stub_status_module --with-mail --with-mail_ssl_module --with-ipv6 --with-cc-opt='-O2 -g -march=i386 -mtune=i686' --add-module=../gnosek-nginx-upstream-fair-5f6a3b7 --add-module=../nginx_upload_module-2.2.0 --add-module=../masterzen-nginx-upload-progress-module-82b35fc

Can anyone shine some light on this situation? It really has me stumped.

Thanks in advance!


Well, after some fiddling with the config, I finally got it to work. It turns out that it was a stupid mistake on my part (as it usually is):

In my Nginx config, I had this:

# Set URL to redirect to after a user's upload finishes
location @uploadhandler {
  rewrite ^ /processes/upload.php last;

When I actually needed this:

# Set URL to redirect to after a user's upload finishes
location @uploadhandler {
  rewrite ^ /processes/upload/upload.php last;

Lesson learned: always check your rewrites.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.