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. Что пораждает следующую проблему — конфигурация конфигурируется при запуске и ее изменение на лету достаточно проблематично.
1. В nginx нет понятия, подобного апачевскому Directory, только Location, а содержимое .htaccess приписывается как раз в Directory. Это было сделано намеренно, чтобы убрать эту ужасную двойственность конфигурации, где на один запрос влияет куча мест в конфиге. Это уже делает очень затруднительным выбор пути для того, чтобы искать в нем .htaccess.
2. Поддержка динамической конфигурации потребует весьма ресурсоемких операций, которые неприменимы на высоких нагрузках.
Во-первых, надо сделать stat() для всех возможных .htaccess файлов.
Во-вторых, надо прочитать их содержимое.
В третьих, надо скомпилировать их с с'merge'ить с текущей конфигурацией.
3. Что пораждает следующую проблему — конфигурация конфигурируется при запуске и ее изменение на лету достаточно проблематично.
проблема в другом. Мы говорим о разных сегментах пользователей: вы о высокой производительности и сис. админах, я — про «обычных» пользователей, которые не могут «записать в конфиг». Для них единственный вариант — иметь под рукой .htaccess (пусть с небольшой потерей производительности, но все лучше, чем делать то же самое через PHP), который можно как-то менять.
Процентное соотношение этих сегментов примерно 1 к 99.
Процентное соотношение этих сегментов примерно 1 к 99.
упс…
*ушёл переписывать конфиги
*ушёл переписывать конфиги
а если в location появляется
if ($request_filename ~*
это нормально?
if ($request_filename ~*
это нормально?
Честно говоря, не вижу смысла — ведь $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;
}
}
мы не ходим чтобы клиенты не из 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, намного красивее:
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 надо гасить в себе «программисткие» порывы :)
всё-таки при конфигурировании 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;
}
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;
}
Хочется перефразировать известное:
«Despite the tons of examples and docs, nginx is voodoo. Damned cool voodoo, but still voodoo.» © ;-)
«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) выносить конфигурацию в отдельные блоки, например
Увы, сейчас ничего из этого не реализовано :-(
Давно упоминалось несколько идей, как это можно сделать:
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 я вообще молчу :) Ещё не нашёл как сделать.
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
Захожу на habr.ru а там: Welcome to nginx!
По поводу ошибки 2.3. Есть такая конструкция:
Как тут можно убрать if $host?
server {
listen 80;
server_name blabla.ru *.blabla.ru "";
if ($host != blabla.ru ) {
rewrite ^(.*)$ httр://blabla.ru$1 permanent;
}
Как тут можно убрать if $host?
Хотелось бы задать такой вопрос…
Возможно ли (и если да — то как) через реврайт nginx'a запретить доступ к некоторым ссылкам сайта. Ну например:
host.su/admin/... — nginx, ссылка не работает.
host.su:8080/admin/… — apache, ссылка работает.
Возможно ли (и если да — то как) через реврайт 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) Проблема в том, что % не считается членом именем переменной. Сейчас попробую сделать патч.
1) для работы переменной надо, чтобы все тело запроса не писалось во временный файл (то есть, размер тела был меньше, чем размер одного буфера), client_body_in_single_buffer должен быть on, в location, где хочется получить значение переменной, должна быть директива proxy_pass или fastcgi_pass. Хотя, матчить POST по регекспу — не очень хорошая затея, пожалейте процессор и память :) Заюзайте iptables и его модуль string.
2) Проблема в том, что % не считается членом именем переменной. Сейчас попробую сделать патч.
client_body_in_single_buffer не помогал, iptables теоретически должен быть тяжелее — строка же больше :)
2) насколько я понял он вообще uri не unescape-ит
Патч для второй проблемы (на 0.8.21, должен сработать для большого числа версий) — dolgov.name/nginx/vars_names.patch
Сработало написание переменной как $arg_user%5fid.
Рекомендую после наложение проверить, не сломалось ли еще что-то.
Сработало написание переменной как $arg_user%5fid.
Рекомендую после наложение проверить, не сломалось ли еще что-то.
client_body_in_single_buffer не помогал, iptables теоретически должен быть тяжелее — строка же больше :)
у меня тоже есть вопрос. не знаю возможно ли такое реализовать средствами nginx
стандартная ситуация:
location ~ \.(jpg|gif|png)$ {
root images;
}
location / {
proxy_pass ...;
}
можно ли кешировать ответы апача, но отдавать всегда свежий контент (ну как если бы мы кешировали с таймаутом в 0 секунд). а в случае если апач возвращает 50х ошибку — выдавать страницу из кеша.
немного сумбурно объясняю, я просто сам еще толком не уяснил для себя как это должно работать.
пример: все работает в штатном режиме — всегда отдается свежий контент без всякого кеширования. но вдруг на бекэнде что-то ломается (допустим отваливается база данных) и бекенд завершает работу с ошибкой 503 допустим. может ли nginx получив такой ответ посмотреть есть ли у него в кеше такая страничка (ее последняя нормальная версия) и если есть — отдать?
стандартная ситуация:
location ~ \.(jpg|gif|png)$ {
root images;
}
location / {
proxy_pass ...;
}
можно ли кешировать ответы апача, но отдавать всегда свежий контент (ну как если бы мы кешировали с таймаутом в 0 секунд). а в случае если апач возвращает 50х ошибку — выдавать страницу из кеша.
немного сумбурно объясняю, я просто сам еще толком не уяснил для себя как это должно работать.
пример: все работает в штатном режиме — всегда отдается свежий контент без всякого кеширования. но вдруг на бекэнде что-то ломается (допустим отваливается база данных) и бекенд завершает работу с ошибкой 503 допустим. может ли nginx получив такой ответ посмотреть есть ли у него в кеше такая страничка (ее последняя нормальная версия) и если есть — отдать?
Можно попробовать
proxy_cache_use_stale http_502 http_503 http_504;
proxy_cache_valid 0;
proxy_cache_use_stale http_502 http_503 http_504;
proxy_cache_valid 0;
при proxy_cache_valid 0; он ничего в кеш не записывает.
ls -la /var/cache/nginx/ — всегда пусто (чтобы было понятно о чем я говорю)
притом если поставить например proxy_cache_valid 5m; — то кешируются все запросы на 5 минут
видимо ему нужно как то дать понять чтобы он в кеш писал каждый нормальный ответ апача в независимости от времени жизни кеша
вот так более-менее работает:
но есть непонятные баги:
во-первых если быстро рефрешить страницу в нормальном режиме (когда бекэнд отдает 200) nginx через раз показывает закешированную копию. так, будто время жизни кеша 1-2 секунды, а не 1 милисекунда.
во-вторых в «аварийном» режиме (когда апач отдает 503) nginx отдает последнюю имеющуюся у него версию из кеша, но через раз вместо нее предлагает скачать какой-то файл. т.е. просто вываливается диалог сохранения/открытия файла с непонятным именем zEDex4TV.dms.part. причем происходит это бессистемно. мне так и не удалось понять в каком случае он отдает правильную страницу, а когда предлагает вдруг что-то сохранить/скачать. раз 5 показывает последнюю версию из кеша как и надо, а потом вдруг бац.
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)
Только начал осваивать 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 — старая. Попробуйте так, но не уверен. Если не будет работать, как надо — сообщите, посмотрим еще :)
Есть вариант переделать скрипт, чтобы он принимал не то, что после 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$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
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 :)
Я использую последнюю ветку все время, таких уж страшных проблем, как при использовании бета-ПО, никогда не имел.
Я использую последнюю ветку все время, таких уж страшных проблем, как при использовании бета-ПО, никогда не имел.
Sign up to leave a comment.
Ошибки конфигурирования nginx (или как правильно писать рерайты)