Welcome to the DjaoDjin Blog!

A place to share experiences in building Software-as-a-Service.

Nginx authenticated files access

by Stephane Robino on Tue, 7 Jul 2015

When dealing with large files (2Gb+), we would prefer nginx to serve those files directly. The trick is we want nginx to serve those files solely to authenticated users. We will see in this post how to have nginx serve the files directly, yet keep the authorized access logic inside our web application.

Configuring Nginx

We want to allow users to download files at the following location: "/files/file_name/". file_name represents the file to download.

We start from a classic nginx proxy configuration proxying requests to our web application.



upstream proxy_app {
        server  127.0.0.1:7000;
}

server {
    listen          80;
    server_name     example.com;

    rewrite_log on;
    access_log      /var/log/nginx/access.log;
    error_log       /var/log/nginx/error.log debug;

    client_max_body_size 4G;
    keepalive_timeout 5;

    root /var/www;

    location / {
        # checks for static file, if not found proxy to app
        try_files $uri/index.html $uri.html $uri @forward_to_app;
    }

    location @forward_to_app {
        include /etc/nginx/proxy_params;
        proxy_pass      http://proxy_app;
    }
}


The idea here is to add an internal directive to URLs for private files and a couple proxy headers.



$ diff -u prev /etc/nginx/conf.d/default
...
+   location /files/(.*)/ {
+       internal;
+       alias /path/to/your/private/files/$1/;
+   }

    location @forward_to_app {
        include /etc/nginx/proxy_params;
        proxy_pass      http://proxy_app;
+       proxy_set_header X-Sendfile-Type   X-Accel-Redirect;
+       proxy_set_header X-Accel-Mapping   /path/to/your/private/files/=/files/(.*)/;
...


Configuring the Web application

Django

We were serving PDF files through Django here. To have the files served by nginx instead, we return an HttpResponse with an empty body and add a X-Accel-Redirect header. For example:



...
    response = HttpResponse()
    response['Content-Type'] = 'application/pdf'
    response['Content-Disposition'] = 'attachment; filename=%s.pdf' % (file_name, )
    response['X-Accel-Redirect'] = '/files/%s' % (file_name, )
    return response


Rails

Working with Rails is about the same though a bit different. All the magic happens in the Rack::sendfile middleware. We only need to configure the appropriate x_sendfile_header then call send_file as usual.

In config/environments/production.rb:



    # Set header to use.
    config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'


In the controller:



    # In case of PDF file
    send_file(
        path,
        :disposition => 'inline',
        :type => "application/pdf")


More to read

If you are looking for more posts dealing with large files over HTTP, Browser direct upload to S3 is worth reading next. If you are looking into using nginx as a proxy, you might also enjoy Nginx / Gunicorn / Django.

More technical posts are also available on the DjaoDjin blog, as well as business lessons we learned running a subscription hosting platform.