Using X-Accel-Redirect in Nginx to Implement Controlled Downloads

Posted by Oleksiy Kovyrin under Development · русский

Sometimes you may need to implement controlled downloads when all downloads requests being sent to your script and then this script decides what to do: to send some file to the user or to show some access denied page or, maybe, do something else. In lighttpd server it can be done by returning X-Sendfile header from script. Nginx have its own implementation of such idea using X-Accel-Redirect header. In this short post I will try to describe how to use this feature from PHP and Rails applications.

Lets assume, that you have some site and using Apache with PHP or Rails for dynamic content on this site. If you will use nginx as reverse-proxy in front of your Apache server, you will reach two goals:

  1. You would be able free some resources on server while nginx will handle all slow requests to dynamic content (details are here).
  2. You would be able to implement controlled downloads of static files from site.

In this article I will assume, that site is located in /var/www directory and there are some static files (like movies or song or something else) in /var/www/files directory. Apache is listening on http://127.0.0.1:8080.

First of all, lets take a look at our nginx configuration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
http {
    ....
    server {
        listen       80;
        server_name  your-domain.com;

        location / {
            rewrite ^/download/(.*) /down.php?path=$1 last;

            proxy_pass         http://127.0.0.1:8080/;
            proxy_redirect     off;

            proxy_set_header   Host             $host;
            proxy_set_header   X-Real-IP        $remote_addr;
            proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;

            client_max_body_size       10m;
            client_body_buffer_size    128k;

            proxy_connect_timeout      90;
            proxy_send_timeout         90;
            proxy_read_timeout         90;

            proxy_buffer_size          4k;
            proxy_buffers              4 32k;
            proxy_busy_buffers_size    64k;
            proxy_temp_file_write_size 64k;

        }

        location /files {
            root /var/www;
            internal;
        }
    }
}

As you can see, we have additional “internal” location /files there. This keyword “internal” allow us to have some locations, that will be available for user only in internal redirects and X-Accel-Redirect responses from backend scripts. So, we can use simple PHP script or Rails code on backend server to implement controlled downloads that will support Ranges header and all other features supported by direct static downloads from nginx servers.

Here is down.php script:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
// Get requested file name
$path = $_GET["path"];

//...
// Perform any required security checks, validation
// and/or stats accounting
//...

// And redirect user to internal location
header("X-Accel-Redirect: /files/" . $path);

?>

In Rails applications you can use following code in your controller:

1
2
3
4
5
6
7
8
9
10
// Get requested file name
path = @params["path"]

# ...
# Perform any required security checks, validation
# and/or stats accounting
# ...

# And redirect user to internal location
@response.headers['X-Accel-Redirect'] = "/files/" + path

That’s it! With described approach we are able to create very flexible and extremely performance systems for file distribution!


Related posts:

  1. Using Lighttpd, Mplayer/Mencoder and Flvtool2 to Implement Flash Video Streaming
  2. Using Nginx As Reverse-Proxy Server On High-Loaded Sites
  3. Nginx-Fu: X-Accel-Redirect From Remote Servers
  4. Typical Configurations Overview For Nginx HTTP(S) Reverse Proxy/Web Server
  5. Monitoring nginx Server Statistics With rrdtool

41 Responses to this entry

Rapsey says:

Will it work if you want to use the flv module with start=XXX parameter?

nir0 says:

привет!
я реализовал контролируемые скачивания, но есть одна проблема – nginx со всеми файлами передает заголовок text/html, и браузер его соответственно открывает. не подскажете, с чем это может быть связано, и как испраить?

Denis says:

$r->send_http_header posulaet eto,
4to-bu zapretit – powli pustoj $r->send_http_header();

nir0 says:

да я затупил, короче… там ошибка была, типа, нет такого темплейта. она и заменяла заголовок на text/html, а тело документа менялось на содержимое файла.

Ruby says:

Неужели и не решили проблеммы с сменой заголовка ??

peroksid says:

Здорово. Часто под контролируемым скачиванием понимают еще и отметку, когда скачивание файла было закончено. Тут nginx помочь может?

Scoundrel says:

2pereksid: Можно и такое. Называлось вроде post_action – нгинкс умеет дергать нужный урл когда юзер все докачает.

Andrey Popov says:

Можно по подробнее, из-за чего nginx выдает на .exe файлы, text/html вместо application/octet-stream?

nir0 says:

> Можно по подробнее, из-за чего nginx выдает на .exe файлы, text/html вместо application/octet-stream?
Это может быть потому, что процесс выдает ошибку “500 No template for action download”
так как ошибка выводится в html, то она подменяет заголовок на text/html.
проверьте логи ruby

jackaXs says:

DOES NGINX IPV6 CAPABLE ???????!!!!!!!!!!!!!??????????????????!!!!!!!!!!!!!???????????????????!!!!!!!!!!!!!!!!!!!!

znikus says:

нет, это статья не все. если использовать пхп как модуль апача, то сервер ляжет под нагрузкой, когда обратятся несколько юзверов при помощи флашгета с 10 потоками. Лучшим вариантом будет запуск проверяющего скрипта на фастCGI.

si-rus says:

Как вы смотрите на то, чтобы для уменьшения нагрузки дописать к nginx модуль или фильтр какой нить для реализации антилича.

square says:

Ошибочка маленькая в rewrite, должно быть так, видимо:
rewrite ^/download/(.*)$ /down.php?path=$1 last;

2 Andrey Popov:
Попробуй перед редиректом (X-Accel-Redirect) отдавать заголовок руками т.е.:
Content-type: application/octet-stream

Alex Egg says:

What about the rest of the code for the rails controller.

This confuses me because you have to render something….?

I have nginx setup and I am setting the header, but then what? render :text => “”???

WIll says:

For explorer 6 users downloading pdfs you also need to set the cache-control header, or you get a ‘document not found’ error message from the acrobat reader. It seems this is because the explorer regards the file as temporary and deletes it before acrobat can see it.

anyway, i use this to make pdf downloads work:

1
2
3
4
5
6
7
   location /download_control/ {
       root ...;
       default_type  application/pdf;
       expires 1h;
       add_header  Cache-Control  private;
       internal;
   }

Otherwise, I’ve been very happy with nginx and this download control mechanism (and I’ve written a radiant plugin to make use of it, which should appear soon). thank you.

jsmirnoff says:

При таком подходе в заголовке Content-Type всегда оказывается text/html. Соответственно, бинарный файл загружается браузером в окно а не предлагается для скачивания.

Как оказалось, это заголовок по умолчанию, который выдает сам PHP скрипт, и nginx его уже не трогает.

Выход довольно прост, надо запретить PHP выдавать этот заголовок, например, так:

ini_set(‘default_mimetype’, ”);

Если кто подскажет, как это сделать более элегантно – буду благодарен.

Goga says:

перед X-Accel-Redirect вставьте:

header(“Content-Disposition: attachment; filename=\”" . $filename . “\”");

где $filename – имя файла (только имя, путь – отсутствует).
После этого все браузеры должны открывать окно с приглашением сохранить файл или открыть. Ну у MIME типы уже сам nginx отдаст если в его конфиге добавлено include conf/mime.types

Ivan says:

Добрый день. А никто не сталкивался со следующей ситуацией: есть файл который отдается через X-Accel-Redirect. При попытке его скачать с двух различных машин – качается только на одной. Проблема имеет место только при скачивании через браузер (тестировался ie6-7 и ff2), выдается невозможность отобразить страницу. При использовании менеджера закачек – все качается нормально.

Ivan says:

в тему предыдущего коммента – вопрос закрыт. проблема была в размере файла – больше 4 гб. Интернет експлорер побоялся что на машине фат32 и не стал качать.

Vladimir says:

При попытке использовать описанную схему контролируемого скачивания (nginx+apache+php) столкнулся со след. проблемой: иногда вместо предложения сохранить файл в браузер выдается ошибка :
502 Bad Gateway
nginx/0.5.33
При этом в PHP скрипт передается $path с запорченным окончанием – последние несколько символов заменены на 0×0. В error.log nginx’а при этом выдается сообщение:
upstream prematurely closed connection while reading response header from
upstream …
Как с этим бороться?

Morales says:

>What about the rest of the code for the rails controller.
>This confuses me because you have to render something….?
>I have nginx setup and I am setting the header, but then what? >render :text => “”???

render :nothing => true

алексей says:

Про использование в lighttpd контролируемых скачек с помощью скриптов можно поподробнее?

Stas says:

Спасибо за статью.

С заголовками таки что-то не то – FF и IE отрабатывают нормально, а вот Опера упорно предлагает сохранить в html …

А. Александров says:

Чтобы правильно отдавать тип и имя файла:
<?php
header(“Content-type: video/mpeg”);
header(“Content-Disposition: attachment; filename=\”file.mpeg\”");
//или подставить свой
Чтобы не задалбывали потоками reget, dmaster etc..
в nginx.conf
http {
limit_zone one $binary_remote_addr 10m;

location /files {
root /home/www/…/files;
internal;
limit_conn one 1;
}
где one-имя зоны, 1-количество подкл. с одного ip