Comments 56
Елки-палки, бывает же такое, пару часов назад для форума возникла необходимость переписать апачевский htaccess на nginx, сидел в раздумьях.
А тут статья попадается.

Огромное спасибо.

Спасибо большое за труд, инфа очень вовремя. Как раз в ближайшем будущем мне предстоит переезд с виртуального хостинга на VDS, где и хочу испытать на собственной шкуре связку nginx+apache.
В общем случае в связке nginx+apache нет особой необходимости заморачиваться с переписыванием .htaccess в конфиг nginx, поскольку обработка .htaccess по-прежнему остается на совести apache. Вот если выкидывать apache совсем, то да — тут возникает множество проблем.
В принципе, согласен. Однако, отключение у apache .htaccess может его немного ускорить, а большинстве VDS как раз нужно иметь возможность выжимать максимум из имеющегося :-)
В таком случае лучше еще немножко поднапрячься и выкинуть apache совсем, т.к. зачастую его оставляют лишь по причине невозможности (неумения, нежелания) переписать .htaccess. Разумеется, я говорю про общие случаи, т.к. в них обычно не используются специфические модули, настройки и прочее.
если бы nginx так же можно было настраивать «на лету», как Apache при помощи .htaccess (без админских прав), то цены бы ему не было :)
Увы, это невозможно по некоторым идеалогическим и техническим причинам :-)

1. В nginx нет понятия, подобного апачевскому Directory, только Location, а содержимое .htaccess приписывается как раз в Directory. Это было сделано намеренно, чтобы убрать эту ужасную двойственность конфигурации, где на один запрос влияет куча мест в конфиге. Это уже делает очень затруднительным выбор пути для того, чтобы искать в нем .htaccess.

2. Поддержка динамической конфигурации потребует весьма ресурсоемких операций, которые неприменимы на высоких нагрузках.
Во-первых, надо сделать stat() для всех возможных .htaccess файлов.
Во-вторых, надо прочитать их содержимое.
В третьих, надо скомпилировать их с с'merge'ить с текущей конфигурацией.

3. Что пораждает следующую проблему — конфигурация конфигурируется при запуске и ее изменение на лету достаточно проблематично.
проблема в другом. Мы говорим о разных сегментах пользователей: вы о высокой производительности и сис. админах, я — про «обычных» пользователей, которые не могут «записать в конфиг». Для них единственный вариант — иметь под рукой .htaccess (пусть с небольшой потерей производительности, но все лучше, чем делать то же самое через PHP), который можно как-то менять.

Процентное соотношение этих сегментов примерно 1 к 99.
Возможно. Но nginx как раз придумывался для этого 1%, а для 99% — придуман apache.
Честно говоря, не вижу смысла — ведь $request_filename зависит от $uri и root или alias, то есть его возможные значение при написании конфига можно предсказать и вынести в location.

Можете показать пример конфига, где приходится писать такое?
ну например есть сайт, на нем есть форум по урлу /forum.

мы не ходим чтобы клиенты не из RU и UA имели к нему доступ, поэтому надо что-то делать с /forum/*.php, иначе они уйдут на локейшен с регуляркой location ~* \.php$ и, соответственно, ограничение по стране не сработает.

то же самое и с картинками; чтобы никто из USA не видел милой картинки /forum/img/icon/post.png например.

вот пример:

location ~* /forum/ {
if ($country = 'en') {
rewrite ^ /404.html last;
}

if ($request_filename ~* "\.php$" ) {
break;
proxy_pass backend;
}

if ($request_filename ~* "\.(jpg|jpeg|gif|png|bmp|ico|swf|flv|css|txt|xml|rss|js)$" ) {
#if ($invalid_referer) {
# return 403;
#}
expires max;
}
}

Какой ужас :-)

location ~* /forum/(.*)\.php$
{
    proxy_pass ;
    if ($country = 'en') { return 404; }
}

location ~* /forum/
{
    if ($country = 'en') { return 404; }
}

location ~* "/forum/(.*)\.(jpg|jpeg|gif|png|bmp|ico|swf|flv|css|txt|xml|rss|js)$"
{
    expires max;
    if ($country = 'en') { return 404; }
}
.

Еще можно сделать вложенными location, намного красивее:
<code>location /forum
{
    if ($country = 'en') { return 404; }
    location ~ \.php$
    {
        proxy_pass ...;
    }
    location ~* "\.(jpg|jpeg|gif|png|bmp|ico|swf|flv|css|txt|xml|rss|js)$
    {
        expires max;
    }
}</code>
я просто читал рассылку, и что-то там не жаловали вложенные локейшены (правда, давно это было), а много локайшенов я не делал, чтобы избежать повторения if ($country = 'en') { return 404; }

всё-таки при конфигурировании nginx надо гасить в себе «программисткие» порывы :)
а как насчёт этого? :)

location ~* \.(avi|mp4|mkv|wma|flv|wmv|mpg|mpeg|mp3|rar|zip|7z)$ {
if ($invalid_referer) {
return 403;
}

limit_conn download_zone 5;
limit_rate 1m;
set $limit_rate 1m;

output_buffers 1 256k;

directio 10m;
expires max;

types {}
default_type application/octet-stream;

#internal;
#root $_application_storage;

if ($uri ~ /([^/]*)$) {
set $_f $1;
add_header Content-disposition «attachment;filename=$_f»;
}

#add_header Content-type application/octet-stream;
#add_header Content-Type application/force-download;

}

location ~* ^/([^/]*)\.(avi|mp4|mkv|wma|flv|wmv|mpg|mpeg|mp3|rar|zip|7z)$ {
    if ($invalid_referer) {
        return 403;
    }
    прочее
    add_header Content-disposition "attachment;filename=$1.$2";
}

Насколько я понимаю, как-то так.
Хочется перефразировать известное:
«Despite the tons of examples and docs, nginx is voodoo. Damned cool voodoo, but still voodoo.» © ;-)

А что, если мне нужно здесь: location / { try_files $uri $uri/ @fallback; } только проверку существования файла, и если его нет, то отдавать управление на index.php? Просто убрать $uri/?
А как сделать, чтобы в @fallback падали не только не найденные файлы, но и .php$? Можно, конечно, прописать ещё один локейшон с содержанием повторяющем @fallback, но это как-то не тру…
На данный момент — никак, только дублированием location'a (прямо или через include).

Давно упоминалось несколько идей, как это можно сделать:
1) сделать хендлер redirect, который сможет перенаправлять обработку запроса в именованный location или по другому uri.
2) выносить конфигурацию в отдельные блоки, например
use php-conf
{
    fastcgi_pass ...;
}
location ~ \.php$
{
    use php-conf;
}
location @fallback
{
    use php-conf;
}

Увы, сейчас ничего из этого не реализовано :-(
Может быть Вам создать тему ликбеза, с человеческими объяснениями и примерами? Хотя, может это никому не нужно, но мне было бы весьма полезно.
Увы, типичные вопросы, чтобы вынести их в тему, мне неизвестны.

А вопросы — можно задавать в комментариях, все они будут отвечены :) По их материалам потом тоже можно будет сделать отдельный пост.
Ну, например. Есть структура каталогов:

data/
cache/
index.php

Нужно при запросе:
host.name/hello.html

Проверить, есть ли такой файл в кэше.
1. Если есть, отдать его (это получается без проблем)
2. Если его нет, то передать управление index.php
3. Если пришли переменные методом POST, то не проверяя наличие файла, передать управление в index.php

Вот, что я пытался сделать:

location ~ \.html$ {
root cache;
try_files $uri @fallback;
} // файл отдаётся

location @fallback {
index index.php;
} // тут, похоже, я всё сделал неправильно

Про POST я вообще молчу :) Ещё не нашёл как сделать.
Я бы сделал так:
location ~ \.html$
{
    root cache;
    error_page 406 =@indexphp;
    if ( $request_method = 'POST' ) { return 406; }
    try_fiiles $uri @indexphp;
}
location @indexphp
{
    fastcgi_pass ...;
    fastcgi_param script_FILENAME $document_root/index.php;
    include fastcgi_params;
}</code>


Тут мы по error_page .. if .. return или try_files уходим в другой location, когда кеширование неприменимо.

Если бекенд - apache, то можно написать что-то такое:
location ~ \.html$
{
    root cache;
    error_page 406 =/index.php;
    if ( $request_method = 'POST' ) { return 406; }
    try_fiiles $uri /index.php;
}
location =/index.php
{
    proxy_pass ...;
}</code>


Вообще, для подобного есть встроенное кеширование nginx, которое работает для proxy_pass и fastcgi_pass, учитывает и отсутствие файла, и POST, и истечение. Есть замечательная статья по этому поводу: dklab.ru/chicken/nablas/56.html
По поводу ошибки 2.3. Есть такая конструкция: server {
listen 80;
server_name blabla.ru *.blabla.ru "";

if ($host != blabla.ru ) {
rewrite ^(.*)$ httр://blabla.ru$1 permanent;
}

Как тут можно убрать if $host?
server
{
    listen 80;
    server_name *.bla.ru "";
    rewrite ^ httр://blabla.ru$request_uri? permanent;
}
server
{
    listen 80;
    server_name bla.ru;
    ... обычный конфиг ...
}
Хотелось бы задать такой вопрос…

Возможно ли (и если да — то как) через реврайт nginx'a запретить доступ к некоторым ссылкам сайта. Ну например:

host.su/admin/... — nginx, ссылка не работает.
host.su:8080/admin/… — apache, ссылка работает.
Насколько я помню,
1) для работы переменной надо, чтобы все тело запроса не писалось во временный файл (то есть, размер тела был меньше, чем размер одного буфера), client_body_in_single_buffer должен быть on, в location, где хочется получить значение переменной, должна быть директива proxy_pass или fastcgi_pass. Хотя, матчить POST по регекспу — не очень хорошая затея, пожалейте процессор и память :) Заюзайте iptables и его модуль string.

2) Проблема в том, что % не считается членом именем переменной. Сейчас попробую сделать патч.
client_body_in_single_buffer не помогал, iptables теоретически должен быть тяжелее — строка же больше :)
Регексп тяжелее поиска подстроки :)

Надо смотреть, что происходит внутри. Сейчас уже не смогу, скорее всего — завтра днем.
ну там все равно нужен регексп был и плюс ко всему в зависимости от условия надо было менять апстрим на конкретный, т.е. тока на уровне приложения можно принять решение.
Патч для второй проблемы (на 0.8.21, должен сработать для большого числа версий) — dolgov.name/nginx/vars_names.patch
Сработало написание переменной как $arg_user%5fid.

Рекомендую после наложение проверить, не сломалось ли еще что-то.
client_body_in_single_buffer не помогал, iptables теоретически должен быть тяжелее — строка же больше :)
у меня тоже есть вопрос. не знаю возможно ли такое реализовать средствами nginx

стандартная ситуация:

location ~ \.(jpg|gif|png)$ {
root images;
}
location / {
proxy_pass ...;
}

можно ли кешировать ответы апача, но отдавать всегда свежий контент (ну как если бы мы кешировали с таймаутом в 0 секунд). а в случае если апач возвращает 50х ошибку — выдавать страницу из кеша.

немного сумбурно объясняю, я просто сам еще толком не уяснил для себя как это должно работать.

пример: все работает в штатном режиме — всегда отдается свежий контент без всякого кеширования. но вдруг на бекэнде что-то ломается (допустим отваливается база данных) и бекенд завершает работу с ошибкой 503 допустим. может ли nginx получив такой ответ посмотреть есть ли у него в кеше такая страничка (ее последняя нормальная версия) и если есть — отдать?
при proxy_cache_valid 0; он ничего в кеш не записывает.
ls -la /var/cache/nginx/ — всегда пусто (чтобы было понятно о чем я говорю)

притом если поставить например proxy_cache_valid 5m; — то кешируются все запросы на 5 минут

видимо ему нужно как то дать понять чтобы он в кеш писал каждый нормальный ответ апача в независимости от времени жизни кеша

вот так более-менее работает:

proxy_cache_use_stale http_503;
proxy_cache_valid 200 301 302 304 1ms;

но есть непонятные баги:
во-первых если быстро рефрешить страницу в нормальном режиме (когда бекэнд отдает 200) nginx через раз показывает закешированную копию. так, будто время жизни кеша 1-2 секунды, а не 1 милисекунда.
во-вторых в «аварийном» режиме (когда апач отдает 503) nginx отдает последнюю имеющуюся у него версию из кеша, но через раз вместо нее предлагает скачать какой-то файл. т.е. просто вываливается диалог сохранения/открытия файла с непонятным именем zEDex4TV.dms.part. причем происходит это бессистемно. мне так и не удалось понять в каком случае он отдает правильную страницу, а когда предлагает вдруг что-то сохранить/скачать. раз 5 показывает последнюю версию из кеша как и надо, а потом вдруг бац.

Сорри за некропостинг, но написано «все желающие» :)

Только начал осваивать nginx, перенес из апача что-то вроде

server {
listen example.com:80;
server_name example.com www.example.com;

location / {
root /var/www/example.com/www;
index index.php;
}

location /subdir/ {
root /var/www/example.com/www;
index index.php;
if (!-e $request_filename) {
rewrite ^/subdir/(.*)$ /subdir/index.php?/$1 last;
}

}
location ~ .php$ {
root /var/www/example.com/www;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param script_FILENAME $document_root$fastcgi_script_name;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGHT $content_length;
include fastcgi_params;
}
}

Смысл в том, что все существующие файлы из example.com/subdir отдаются напрямую (хотя в идеале надо исключить оттуда все .php, кроме example.com/subdir/index.php и тот не напрямую, а только через реврайт)

В принципе работает (после пары часов курения доков, гугления и использование метода научного тыка), но прочитал топик и понял, что допустил ошибку 2.1. Но, увы, как не мудрил не получилось сделать через вариант с @fallback, чтобы передать как QUERY_STRING на $document_root/subdir/index.php всё, что после example.com/subdir.

Это реально? (версия nginx 0.7.64)
0.7.64 — старая. Попробуйте так, но не уверен. Если не будет работать, как надо — сообщите, посмотрим еще :)
<code>
location = /subdir/index.php
{
    root /var/www/example.com/www;
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_index index.php;
    fastcgi_param script_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param QUERY_STRING $query_string;
    fastcgi_param REQUEST_METHOD $request_method;
    fastcgi_param CONTENT_TYPE $content_type;
    fastcgi_param CONTENT_LENGHT $content_length;
    include fastcgi_params;
}
location ~ /subdir/(.*)$
{
    root /var/www/example.com/www;
    try_files $uri /subdir/iindex.php?/$1;
}


Есть вариант переделать скрипт, чтобы он принимал не то, что после subdir, а весь путь, и написать тогда вот так:
<code>
location = /subdir/index.php
{
    root /var/www/example.com/www;
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_index index.php;
    fastcgi_param script_FILENAME $document_root/subdir/index.php;
    fastcgi_param QUERY_STRING $request_uri;
    fastcgi_param REQUEST_METHOD $request_method;
    fastcgi_param CONTENT_TYPE $content_type;
    fastcgi_param CONTENT_LENGHT $content_length;
    include fastcgi_params;
}
location ~ /subdir/(.*)$
{
    root /var/www/example.com/www;
    try_files $uri @fallback;
}


Спасибо большое, первый вариант заработал. Вроде смотрю на него, каждая строчка ясна и почему сам не додумался не понятно, просто надо думать по другому, наверное :) Второй вариант — возможности исправить скрипт нет, точнее каждый апдейт скрипта придётся править ручками.

0.7.64 — старая? вроде это последний стабильный билд от 16.11.2009 — чуть больше месяца. Или 0.8.xx ближе к сквиз, чем к сид (если говорить в терминах дебиан) и на некритичных серверах его вполне можно использовать?

P.S. Пока разбирался, понял что строчки
fastcgi_param QUERY_STRING $request_uri;
и т. д. не нужны, т. к. они (и еще несколько) уже есть в подключаемом дефолтном fastcgi_params
Увы, в терминах debian я не умепю оперировать :) 0.8 — текущая версия, 0.7 — старая версия. 0.7 не развивается, в нее только раз в N времени бекпортятся исправления багов, найденных во время разработки 0.8. Если Вы найдете в 0.7 баг, то его исправят в 0.8 :)
Я использую последнюю ветку все время, таких уж страшных проблем, как при использовании бета-ПО, никогда не имел.
Only those users with full accounts are able to leave comments. Log in, please.