The Data Asylum

Programming, Databases and People

How Rails, Nginx and X-Accel-Redirect Work Together

TL;DR - To use X-Accel-Redirect to serve static files from e.g /mnt/filestorage using send_file, put this in your nginx server configuration.

1
2
3
4
5
6
proxy_set_header  X-Accel-Mapping       /mnt/filestorage/=/private_files/;

location /private_files/ {
  internal;
  alias   /mnt/filestorage/;
}

The need for X-Accel-Redirect (and it’s sibling X-Sendfile) comes from two distinct requirements

  • The need to deliver large files.
  • The need for those files to not be available to the public.

Here we are going to see how to set up X-Accel-Redirect and Rails. This is a bit complex so I’m going to run through a specific example of downloading a file. Along the way we’ll see the configuration, code and HTTP headers that are used. This assumes you already have Rails and Nginx installed and working.

1. Browser makes a request for a file

1
2
# HTTP Headers
GET /download/SecretSquirrel.zip HTTP/1.1

2. Nginx receives this request. It adds on a header with configuration data that will be required by rails.

1
2
3
4
5
6
#Nginx configuration
proxy_set_header   X-Accel-Mapping       /mnt/filestorage/=/private_files/;

# Example HTTP Headers with additional header added by nginx
GET /download/SecretSquirrel.zip HTTP/1.1
X-Accel-Mapping: /mnt/filestorage/=/private_files/

3. Nginx passes the request onto Rails and it invokes the relevant controller.

4. The controller makes its authorization checks and calls send_file. Use the absolute path to the file.

1
2
# controller code (e.g. app/controllers/downloads_controller.rb)
send_file('/mnt/filestorage/SecretSquirrel.zip')

5. Rails (Rack to be precise) then decides what to with the file. We need to tell rails to use X-Accel-Redirect in its configuration as shown below. Instead of using the file as the body of the request, it will add a header to the response. It uses the X-Accel-Mapping that nginx added earlier to change the file path.

1
2
3
4
5
6
7
8
9
10
# Rails configuration (e.g. config/environments/production.rb)
config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'

# HTTP Response
HTTP/1.1 200 OK
X-Accel-Redirect: /private_files/SecretSquirrel.zip
Content-Type: application/octet-stream
Content-length: ...
Content-Disposition: attachment; filename="SecretSquirrel.zip"
<empty body>

6. Nginx receives this header from rails and interprets it. It finds the location directive and reverses the changes to the path that rails made in step 5.

1
2
3
4
5
6
7
8
9
10
11
12
#Nginx configuration
    location /private_files/ {
    internal;
        alias   /mnt/filestorage/;
    }

# HTTP Response
HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Length: ...
Content-Disposition: attachment; filename="SecretSquirrel.zip"
<contents of /mnt/filestorage/SecretSquirrel.zip>

7. Browser receives the file as if it was a normal download.

Also, if you have compiled passenger into nginx, remember to use passenger_set_cgi_param instead of proxy_set_header

Software Versions used

  • Rails 3.0.7
  • Passenger 3.0.5
  • Nginx 0.8.54
  • on Ubuntu 10.04.2 LTS

Resources

Comments