Displaying Upload Progress With nginx On Debian

This tutorial shows how to use the nginx upload progress module to upload one or multiple files and display an upload progress bar for the user. This is useful, for example, if users upload large files, so that they know that something is going on in the background.

I do not issue any guarantee that this will work for you!


1 Installing nginx-extras

The nginx upload progress module isn't enabled by default. You can run

nginx -V

to check if it is enabled - you should then seen something like --add-module=/tmp/buildd/nginx-1.4.4/debian/modules/nginx-upload-progress in the output. If it's not enabled, install the nginx-extras package:

apt-get install nginx-extras

Afterwards, run

nginx -V

again - the upload progress module should now appear in the output:

[email protected]:/var/www# nginx -V
nginx version: nginx/1.4.4
TLS SNI support enabled
configure arguments: --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-log-path=/var/log/nginx/access.log --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid --with-pcre-jit --with-debug --with-http_addition_module --with-http_dav_module --with-http_flv_module --with-http_geoip_module --with-http_gzip_static_module --with-http_image_filter_module --with-http_mp4_module --with-http_perl_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_spdy_module --with-http_stub_status_module --with-http_ssl_module --with-http_sub_module --with-http_xslt_module --with-ipv6 --with-mail --with-mail_ssl_module --add-module=/tmp/buildd/nginx-1.4.4/debian/modules/headers-more-nginx-module --add-module=/tmp/buildd/nginx-1.4.4/debian/modules/nginx-auth-pam --add-module=/tmp/buildd/nginx-1.4.4/debian/modules/nginx-cache-purge --add-module=/tmp/buildd/nginx-1.4.4/debian/modules/nginx-dav-ext-module --add-module=/tmp/buildd/nginx-1.4.4/debian/modules/nginx-development-kit --add-module=/tmp/buildd/nginx-1.4.4/debian/modules/nginx-echo --add-module=/tmp/buildd/nginx-1.4.4/debian/modules/ngx-fancyindex --add-module=/tmp/buildd/nginx-1.4.4/debian/modules/nginx-http-push --add-module=/tmp/buildd/nginx-1.4.4/debian/modules/nginx-lua --add-module=/tmp/buildd/nginx-1.4.4/debian/modules/nginx-upload-progress --add-module=/tmp/buildd/nginx-1.4.4/debian/modules/nginx-upstream-fair --add-module=/tmp/buildd/nginx-1.4.4/debian/modules/ngx_http_substitutions_filter_module
[email protected]:/var/www#

To activate the module, we need to create a small zone in nginx.conf (in the http {} container) that is used to track uploads (I name the zone uploads in this example):

vi /etc/nginx/nginx.conf

http {
        # reserve 1MB under the name 'uploads' to track uploads
        upload_progress uploads 1m;

Reload nginx afterwards:

/etc/init.d/nginx reload


2 Configuring Your Vhost

Now open your vhost configuration file and put the following inside the server {} container (if you use ISPConfig, put this in the nginx Directives field of the website):

client_max_body_size 100M;
location = /upload.php {
   try_files $uri =404;
   include /etc/nginx/fastcgi_params;
   fastcgi_pass unix:/var/lib/php5-fpm/web1.sock;
   fastcgi_index index.php;
   fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
   fastcgi_intercept_errors on;
   ## Track uploads for this location on the zone defined
   ## above with a 60 seconds timeout.
   track_uploads uploads 60s;
location ^~ /progress {
   report_uploads uploads;

In my case the file that processes the uploads (i.e., the file in the action attribute of the HTML upload form) is called upload.php and resides in the document root. If your file is named differently, adjust location = /upload.php { accordingly. Also make sure you use the correct fastcgi_pass options for your website (you will probably use another socket than the one I'm using here, or you are using TCP connections instead of sockets).

In the track_uploads line it is important that you use the name of the upload_progress zone from chapter 1 (uploads in this case).

The location ^~ /progress {} container has to be taken literally, which means don't change it (well, you should change the report_uploads line if your upload_progress zone isn't named uploads). This location will be used to poll nginx for the status of the file uploads.

Reload nginx afterwards (unless you use ISPConfig which does the reload for you):

/etc/init.d/nginx reload


3 Building An Upload Form

Next we need an upload form with some JavaScript that polls the progress location during uploads. My file looks like this - name it index.html, upload.html, etc. and put it in your document root:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" dir="ltr" xml:lang="de">
    <title>nginx Upload Progress</title>
        body {
            font-family: arial;
            font-size: 11px;
        #statbar_box {
            width: 500px;
            height: 20px;
            background-color: #FFF;
            position: relative;
            text-align: center;
            margin-bottom: 12px;
            display: none;
            border-radius: 4px;
        #statbar_bar {
            position: absolute;
            height: 20px;
            top: 0px;
            background-color: darkred;
            text-align: center;
            z-index: 100;
            border-radius: 4px;
        #status {
            margin-bottom: 12px;
            text-align: center;
            width: 500px;
        iframe {
            width: 500px;
            height: 30px;
            margin-bottom: 12px;
            position: absolute;
            top: -300px;
        #footer {
            text-align: center;
            width: 500px;
            background: #ddd;
            padding: 5px;
            border-radius: 4px;
        #base {
            width: 500px;
            padding: 20px;
            background: #EFEFEF;
            border-radius: 4px;
            position: relative;
            margin: auto;
    <script src="http://code.jquery.com/jquery-1.8.3.min.js" type="text/javascript"></script>
    <script type="text/javascript">
        (function ($) {
            var interv;
            var test = 1;
            var statbarWidth;
            $(document).ready(function () {
                statbarWidth = $("#statbar_box").width();
                $("#upload").submit(function () {
                                        /* generate random progress-id */
                                        uuid = "";
                                        for (i = 0; i < 32; i++) {
                                                uuid += Math.floor(Math.random() * 16).toString(16);
                                        /* patch the form-action tag to include the progress-id */
                                        $("#upload").attr("action", "/upload.php?X-Progress-ID=" + uuid);
                    $("#statbar_bar").css("width", "1px");
                    $(this).disabled = false;
                    interv = setInterval(function () {
                    }, 800);
            var firststart = true;
            function progress(uuid) {
                $.getJSON("progress", {"X-Progress-ID": uuid}, function (data) {
                    if (data.state == 'done') {
                        $("#statbar_bar").animate({"width": statbarWidth + "px"}).animate({"width": "1px"}, 400, function () {
                    var prozent = Math.round((data.received * 100) / data.size);
                    prozent = !prozent?0:prozent;
                    var pixel = Math.round((prozent * statbarWidth) / 100);
                    $("#status").html(prozent + '%');
                    firststart = false;
                    $("#statbar_bar").animate({"width": pixel + "px"});

<div id="base">
    print '- Max. Postsize: ' . ini_get('post_max_size');
    print '<br>';
    print '- Max. Uploadsize: ' . ini_get('upload_max_filesize');
    <div id="status">0%</div>
    <div id="statbar_box">
        <div id="statbar_bar"></div>
    <iframe src="upload.php" name="hidden_upload" frameborder="0"></iframe>
    <form id="upload" action="upload.php" target="hidden_upload" method="post" enctype="multipart/form-data">
        <div id="footer">
                        <input type="hidden" name="MAX_FILE_SIZE" value="30000000"  />
            <input type="file" name="uploader[]"/>
            <input type="file" name="uploader[]"/>
            <input type="submit"/>

Next put the following upload.php in your document root:

if(is_array($_FILES['uploader']['name']) && !empty($_FILES['uploader']['name'])){
                move_uploaded_file($_FILES['uploader']['tmp_name'][$i], 'cache/'.$_FILES['uploader']['name'][$i]);
                echo "File ".$_FILES['uploader']['name'][$i]." uploaded successfully.<br>";

upload.php stores uploaded files in a directory called cache, so don't forget to create that directory in your document root and give it the proper permissions so that PHP can write to it.

That's it. You can now call the file with the upload form in your browser and try to upload some files:


Falko Timme

About Falko Timme

Falko Timme is an experienced Linux administrator and founder of Timme Hosting, a leading nginx business hosting company in Germany. He is one of the most active authors on HowtoForge since 2005 and one of the core developers of ISPConfig since 2000. He has also contributed to the O'Reilly book "Linux System Administration".

Share this page:

Suggested articles

1 Comment(s)

Add comment


By: Natan Felles

Thank you!