Настраиваем nginx как reverse proxy для Apache

Эта статья является продолжением предыдущей статьи (в которой я рассказывал о настройке небольшого LAMP-хостинга на VPS под управлением Debian).

Зачем нужен еще и nginx — можно почитать здесь и в других источниках.

Программа на сегодня такая:

  1. Сначала делаем, чтобы все запросы шли через nginx (делаем тупой прокси)
  2. Изучаем как отдавать статичный контент в обход Апача
  3. Настраиваем ограничение нагрузки

В заключение я немного порассуждаю о возможных проблемах и подводных камнях.

 

Вставляем nginx перед Apache

apt-get install libapache2-mod-rpaf nginx

Теперь надо перевесить Апач на порт 81 и спрятать от внешнего мира. В /etc/apache2/ports.conf меняем:

NameVirtualHost *:81
Listen 127.0.0.1:81

Во всех конфигах внутри /etc/apache2/sites-available тоже меняем порт:

<VirtualHost *:81>

Теперь переходим в /etc/nginx.

Удаляем «заводской» сайт:

  • sites-available/default
  • sites-enabled/default

Вместо него добавляем наш собственный:

touch /etc/nginx/sites-available/apache_proxy

Внутрь помещаем:

server {
    listen          80 default;
    server_name     localhost;

    location / {
        proxy_pass          ;
        proxy_set_header    <a href="http://en.wikipedia.org/wiki/X-Forwarded-For" data-mce-href="http://en.wikipedia.org/wiki/X-Forwarded-For">X-Forwarded-For</a> $proxy_add_x_forwarded_for;
        proxy_set_header    Host $http_host;
    }
}

Сохраняем и делаем его «enabled»:

ln -s /etc/nginx/sites-available/apache_proxy /etc/nginx/sites-enabled/apache_proxy

Линк приходится создавать руками — утилит вроде a2ensite тут к сожалению нет.

Перезапускаем сервера:

/etc/init.d/apache2 restart
/etc/init.d/nginx restart

После всех этих манипуляций сайты должны продолжить работать без изменений.

Статика в обход Апача

Пора получить от nginx какую-нибудь пользу. Вносим вот такие изменения в недавно созданный файл apache_proxy:

server {
    listen          80 default;
    server_name     localhost;

    <b>location /**static**/<em>domain1.com</em>/ {
        internal;
        alias       /home/<em>user1</em>/<em>domain1.com</em>/www;
        expires     24h;
    }</b>    

    location / {
        <b>if ($http_host = <em>domain1.com</em>) {	
            set $my_static_location <em>domain1.com</em>;
        }

        if ($my_static_location) {
            rewrite ^(.*\.(css|js|png|jpg|gif))$ /**static**/$my_static_location/$1;
        }</b>

        proxy_pass          ;
        proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header    Host $http_host;
    }
}

Применяем:

/etc/init.d/nginx reload

Теперь статичные файлы должны отдаваться напрямую. Убедиться в этом можно через dev tools браузера: в заголовках ответа появилось max-age=86400 и исчез ETag, который генерировал Апач.

Список расширений (в директиве rewrite) можно дополнить по собственному желанию: doc, xls, mp3, flv, ico, txt, html, xml, pdf, zip и многое другое.

Важным нюансом является то, что процесс nginx должен иметь доступ к отдаваемым файлам (на чтение конечно). Если вы в целях безопасности делаетеchmod 0750 домашним директориям пользователей, то это создаст препятствия.

Теперь пара слов о конфиге. «Легко видеть», что для каждого домена, для которого мы хотим отдавать статику, заводится internal location, указывающий на web-root. Далее идет выбор этого location-а и перекидывание на него запросов за статикой.

Такой подход позволяет сохранять конфиг компактным — не надо создавать параллельные конфигурации серверов в Apache и nginx. В то же время не теряется гибкость: вы сами решаете, для каких доменов вам это надо, и у вас есть свобода в настройке путей, кеширования и других фишек для каждого домена отдельно.

Ограничение нагрузки

limit_req — еще одна плюшка, которую может нам дать nginx:

limit_req_zone      $pid zone=proxy_global:64k rate=10r/s;

server {
    listen          80 default;

    server_name     localhost;

    location /**static**/domain1.com/ {
        internal;
        alias       /home/user1/domain1.com/www;
        expires     24h;
    }

    location / {
        if ($http_host = domain1.com) {	
            set $my_static_location domain1.com;
        }

        if ($my_static_location) {
            rewrite ^(.*\.(css|js|png|jpg|gif))$ /**static**/$my_static_location/$1;
        }

        proxy_pass          ;
        proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header    Host $http_host;

        limit_req           zone=proxy_global burst=100;
    }
}

Внесенные изменения ограничивают нагрузку на Апач до 10 запросов в секунду.burst=100 означает, что «неуспевшие» выстраиваются в очередь, но когда длина очереди перевалит за 100, их начнут отстреливать (HTTP 503 Service Unavailable).

Числа, разумеется, надо подбирать в зависимости от нагрузки и мощности сервера.

Обратите внимание, что при описании зоны я использовал $pid вместо «классического» $binary_remote_addr. Таким образом, нагрузка ограничивается глобально, а не для каждого пользователя отдельно.

Не знаю, защитит ли такая мера ваш любимый VPS от DDoS-атак, но завалить путем зажатия F5 его станет сложнее.

Возможные проблемы

Теперь порассуждаем о возможных обратных сторонах медали.

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

  • Если файл находится в папке, доступ к которой ограничен средствами .htaccess, то очевидно эти ограничения перестанут работать, т.к. nginx про этот .htaccess ничего не знает.
  • При использовании mod_rewrite за статичным с виду урлом может скрываться динамика: например, /something.json, /something.xml и something.html, отдающие одинаковый контент в разных форматах, или перехват запросов за картинками с целью встраивания их в html-рамочку. Вариантов полно.

Именно поэтому отдача статики не включена по умолчанию, а прописывается по условию. В более сложных сценариях придется отказаться и от центрального$my_static_location, тщательно подбирая регулярные выражения для rewrite-правил под конкретный случай.

Теперь вы предупреджены, а как известно предупрежден значит вооружен. Так что используйте nginx во благо, а не во вред!