Sorry this page looks weird. It was automatically migrated from my old blog, which had a different layout and different CSS.

Rails, Nginx, and X-Accel-Mapping

I ran into a small hitch getting Rails to hand off files to Nginx to send to the browser.

First I read How Rails, Nginx and X-Accel-Redirect Work Together and Sending files with Nginx X-Accel-Redirect.

Then I read the Nginx docs on XSendfile and X-accel.

Then I tried it. The files I wanted to send were under /path/to/rails/app/private/ so I did this:

# Rails: config/environments/production.rb
config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'

# Nginx: /etc/nginx/conf.d/mysite.conf
# inside server block
location /private_files/ {
  alias /path/to/rails/app/current/private/;
  internal;
}

location @app {
  proxy_set_header X-Accel-Mapping /path/to/rails/app/current/private/=/private_files/;
  # ...
}

Unfortunately this gave a 404 when I tried to download a file.

It turned out Rails was handing off the expanded file path (i.e. /path/to/rails/app/releases/20120801123456/...) instead of the symlinked path (/path/to/rails/app/current/...). The expanded path didn’t match the X-Accel-Mapping, so Rack::Sendfile wasn’t able to map the path to Nginx’s internal /private_files/ location. Consequently the new request for the file was passed through to Rails which was unable to route it.

So the question became how to map a directory whose path changes every release?

Fortunately Rack::Sendfile takes a regular expression for the left hand side of the X-Accel-Mapping value. So the solution was:

proxy_set_header X-Accel-Mapping "/path/to/rails/app/releases/\d{14}/private/=/private_files/";

The header value had to be quoted because of the \d{14}.

Not complicated when you know how, but it took longer than I expected to solve.

Andrew Stewart • 2 August 2012 • RailsNginx
You can reach me by email or on Twitter.