Debian.pro/

Про Debian


Большой мануал: часть 20. Создаём конфиг для нашего сайта в nginx.

Эта статья — часть Большого Мануала по настройке lamp-сервера на debian.

Предыдущая часть цикла — Создаём конфиг для нашего сайта в apache2.

Следующая часть цикла — Учимся делать редиректы в nginx.

Уххххрррр. Часть 20. Сегодня этот сайт наконец-то заработает %).
Мы тут где-то когда-то ставили nginx, а я забыл рассказать, для чего. Попробуем исправить это упущение. TLDR — чтобы связка lamp жрала меньше памяти.
Апач устроен так, что на каждый запрос к нему, он занимает один процесс на некоторое время (или тред, не суть важно). Например, к вам пришел клиент с мобильника и 30 секунд качает страницу — один из процессов занят этим клиентом. Что ещё хуже, потом этот же чувак в верстке найдет 4 картинки и начнет качать их, займёт ещё 4 процесса. В апаче 2.4 «картинки с диска» стали отдаваться намного легче, но nginx в этом плане всё ещё на голову быстрее и менее затратен по ресурсам. Количество процессов апача ограничено, каждый запущенный весьма прожорлив по памяти (а когда клиентов нет, апач тушит часть процессов/тредов, в зависимости от настроек).
И тут впрыгивает дядя nginx. У него схема работы немного другая. Называется state machine. Не помню наизусть, но попробую рассказать на пальцах. В единый момент времени воркер nginx-а либо принимает от кого-то (в нашем случае — апача) данные, либо отправляет их кому-то. То бишь всё это выглядит так. Клиент пришел к nginx-у, сказал ему «дай мне ххх», nginx ответил ему «ща» и спросил ххх у апача. Пока апач ворочается и пытается сгенерить страницу, nginx бежит и отдаёт другому клиенту пару картинок с диска. Возвращается к апачу, спрашивает между делом «есть чо?», идет ещё дальше отдать пару картинок. И даже когда nginx получит ответ от апача, медленный клиент nginx-у не помешает — его процесс будет занят ровно в тот момент, когда медленному клиенту будут отправляться пакетики (а это достаточно короткий промежуток времени). Пока клиент придет и скажет «давай дальше» — можно ещё пару картинок запулить другим клиентам.

Всё это приводит к тому, что с клиентами общается быстро работающий nginx, который тратит мало памяти и CPU. А апач занимается только тем, что из лапши php-кода рисует страницы. При этом, с точки зрения апача, единственный его клиент — nginx на localhost-е. В итоге прожорливый по памяти апач в активном состоянии проводит ровно то время, которое необходимо для генерации страницы.

Ну и в целом, торчать голым задомапачем наружу в наше время не очень спокойно — с безопасностью, возможностями фильтрации и прочим у nginx-а сильно лучше. Но генерить страницы из php-исходников nginx не умеет ни в каком виде — поэтому за него и нужно ставить какой-то ещё софт (будь-то апач, php-fpm или ещё что-то).

Ещё важно понимать, что apache и nginx на самом деле ни в какой связке не настраиваются. Они настраиваются каждый сам по себе и работают отдельно (но апач мы настроили так, чтобы он слушал только 127.0.0.1, ибонех). Просто в части конфигурации nginx будет написано «вот на такие запросы сходи туда по http, получи ответ и отдай его клиенту». Важно понимать, что по такой же схеме там может быть не апач, а любой другой софт, отвечающий по http(s) (ну или по другим протоколам, но тогда настраивать уже по-другому нужно будет).

Ладно. Давайте уже переходить к конфигу, а то я как-то однажды целый час рассказывал ответ на вопрос «зачем нужен nginx».

Допустим, у нас сайт example.com. Он будет доступен и по https, и по http, но с домена www.example.com будет редирект (сам редирект сделаем в следующей статье). Так же для наглядности допустим, что у нас есть домен-алиас dev.example.com, по которому этот сайт тоже должен отвечать (например, мы хотим абсолютно одинаковый конфиг положить на prod и dev сервер положить).
Создаём файл /etc/nginx/sites-available/example.com.conf:

# открываем секцию server {}
server {
    # Подцепляем файл /etc/nginx/listen, мы его подготовили в части 16
    include listen;
    # подцепляем файл /etc/nginx/listen_ssl, если делаем https (перечитайте часть 17)
    include listen_ssl;
    # подцепляем файл includes/letsencrypt
    # (только если не подцепляли его в файле listen)
    #include includes/letsencrypt;

    # указываем значения заголовка Host:, на которые этот конфиг будет отзываться:
    server_name example.com dev.example.com;
    # заводим переменную $root_path - она будет несколько раз использоваться в дальнейшем.
    # это путь до докрута нашего сайта.
    set $root_path /home/example_com/data/www/example.com;
    # отключаем переход по символическим ссылкам в докруте, если ссылка и исходный файл принадлежат разным пользователям (например, нельзя будет создать симлинку на /etc/passwd обычным пользователем и почитать её по http).
    disable_symlinks if_not_owner from=$root_path;
    # указываем, какие страницы считать индексными (если эти страницы существуют - то по пути каталога по http откроется файл с одним из этих имен (первый совпавший) в каталоге)
    index index.html index.php index.htm;

# открываем секцию location / {}
# в location / будут попадать все запросы, не попавшие в иные location
    location / {
        # запросы здесь нужно проксировать в апач
        proxy_pass http://127.0.0.1:81;
        # если апач отдаст редирект на урл http://127.0.0.1:81/example, то клиенту нужно передать редирект на /example
        proxy_redirect http://127.0.0.1:81/ /;
        # передаём в апач значение заголовка host, чтобы апач мог выбрать у себя конфиг сайта
        proxy_set_header Host $host;
        # передаём заголовки, содержащие исходные ip-адреса нашего клиента
        # разные CMS могут смотреть на разные заголовки, так что отправляем оба
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real-IP $remote_addr;
        # передаём апачу в специальном заголовке, по http или по https пришел к нам клиент
        # некоторые CMS этот заголовок игнорируют, но большинство - нет
        # например wordpress исходя из этого заголовка выбирает протокол в урлах для верстки
        proxy_set_header X-Forwarded-Proto $scheme;
        # закрываем секцию location / {}
    }

# заводим именованный location @apache
# в него нельзя обратиться напрямую через web, но его можно использовать в конфигурации.
# по сути у нас конфигурация location @apache полностью дублирует location / (и для корректной работы должна дублировать его).
    location @apache {
        proxy_pass http://127.0.0.1:81;
        proxy_redirect http://127.0.0.1:81/ /;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

# открываем секцию location для статических файлов.
# "~*" - матчинг location и url будет происходить по регулярке без учета регистра (т.е., jpg = JPG).
# сама регулярка достаточно простая
# ^ - начало строки
# .+ - любое количество любых символов
# \. - точка
# (jpg|jpeg) - jpg или jpeg (в самой регулярке - полный список расширений, которые мы будем отдавать прямо с диска)
# $ - конец строки
# в итоге в этой регулярке мы имеем любые урлы вида *.jpg, *.jpeg и так далее.
    location ~* ^.+\.(jpg|jpeg|gif|png|rar|txt|tar|bz2|pdf|bmp|js|ico|css|zip|tgz|gz)$ {
        # статические файлы ищем на диске в докруте (переменную завели раньше)
        root $root_path;
        # браузер уведомляем о том, что файл можно закешировать локально на 30 дней
        # можно не париться о том, что файлы на сервере будут меняться - браузер всё равно будет переспрашивать (и получать либо 200 и качать файл, либо 301 и брать из кеша)
        expires 30d;
        # если статического файла нет, то запрос проксируем в апач.
        # это полезно, если мы хотим показывать фирменную 404 там, где нет файла на диске
        # некоторые CMS не будут работать без этого - они на 404й странице перехватывают запросы к превьюшкам картинок и генерируют их на лету в php (привет, drupal)
        error_page 404 = @apache;
    }

# для потенциально админских урлов показываем апачем 404-ку
    location ~* ^/(webstat|awstats|webmail|myadmin|pgadmin|phpmyadmin)/ {
        error_page 404 = @apache;
        return 404;
    }

# для файлов вида .htaccess/.htpasswd - тоже 404 (только уже nginx-овскую стандартную)
# по вкусу/необходимости упомяните здесь .git, .subversion или какие у вас там vcs используются.
    location ~ /\.ht {
        return 404;
    }

# закрываем секцию server {}
}

Этот же конфиг в txt без комментариев (чуть досыпал там статических файлов в регулярку, потому что в статье не влазило в 1600px по ширине) — https://debian.pro/files/big-man/nginx-vhost.conf
Этот же конфиг в txt с раскрытыми инклудами — https://debian.pro/files/big-man/nginx-vhost-full.conf

Создаём симлинк из sites-available в sites-enabled:

root@server:~# ln -s /etc/nginx/sites-available/example.com.conf /etc/nginx/sites-enabled/example.com.conf

Проверяем валидность конфига:

root@server:~# nginx -t

Рестартим nginx:

root@server:~# /etc/init.d/nginx restart

Ну и если что-то не работает — пишем ниже.

29.01.2017 byinkvizitor68sl|big-manual

Комментарии (13):

  1. алексей :

    Возможен такой вариант ?
    goto это я предположил скорей всего чтото другое

    location / {
    goto @apache
    }

    По Моему одно и тоже писать дважды не комильфо

  2. > и тоже писать дважды не комильфо
    Никогда так не думайте, когда пишете конфиг к nginx-у. А то потом весь конфиг в if-ах и try_files там, где они в зопу не уперлись.

    Написать 10 раз одно и то же (как вариант — инклудом) — вполне себе способ частенько оптимизировать скорость работы конфига.

    Ну а goto без лишних переключений location внутри (а это ещё пара мс) — нет, невозможно.

  3. mrz :

    В 2.4 NameVirtualHost

    Nov 30 12:14:43 apachectl[1863]: AH00548: NameVirtualHost has no effect and will be removed in the next release /etc/apache2/ports.conf:5

    Server version: Apache/2.4.25 (Debian)
    Server built: 2017-09-19T18:58:57

    The NameVirtualHost directive no longer has any effect, other than to emit a warning. Any address/port combination appearing in multiple virtual hosts is implicitly treated as a name-based virtual host.
    (с) https://httpd.apache.org/docs/2.4/upgrading.html

  4. mrz :

    удали мой коммент. не туда запостил. сорри

  5. Там ссылка, его и без меня в спам отправило) Доставать не буду, ок.
    ЗЫ — да, namevirtualhost начиная с apache2.4 можно уже не писать. Но пока что можно и писать.

  6. Максим :

    Добрый день, Влад!
    Подскажите, пожалуйста, как должна выглядеть секция «location» для того чтобы для всех URL без расширения (например /download/123) возвращался HTTP 404 ?

  7. Гм. Никак.
    location / {return 404; }
    А те location, на которых 404 быть не должно — описываете отдельно.

  8. Neuro75 :

    Здравствуйте!
    Огромное спасибо за такой отличный мануал!
    Собираю по нему сервер. Одновременно прописываю несколько доменов. По результатам данной главы, у меня в каждом sites-available соответственно инклуд letsencrypt’a (include includes/letsencrypt;).
    При проверке валидности конфига получаю
    nginx: [emerg] duplicate location «/.well-known/acme-challenge/» in /etc/nginx/includes/letsencrypt:7
    nginx: configuration file /etc/nginx/nginx.conf test failed

    Закомментировал в /etc/nginx/includes/letsencrypt все 3 строки (т.е. вообще получается его отключил), проверка заканчивается успешно.

  9. Либо 2 раза инклудили includes/letsencrypt, либо /.well-known/acme-challenge/ где-то ещё описана.

    egrep -rni ‘(letsencrypt|acme-)’ /etc/nginx
    посмотрите внимательно.

  10. Neuro75 :

    Нашел.
    Он прописан в /etc/nginx/listen:

    listen 80;
    include includes/letsencrypt;

    Это в главе 16 было: либо просто listen 80; в этом файле либо так как выше, чтобы работало на любом сайте.
    Еще раз спасибо.

  11. Neuro75 :

    Ничего не понимаю. Все сделал точно по мануалу. В созданную директорию /home/тут_мой_пользователь/data/www/тут_мой_домен скопировал index.html, который apache в /var/www создал.
    Пробовал зайти и по http и по https, и указывал явно /index.html и просто по имени домена — пытается минуты две, потом «504 Gateway Time-out. nginx/1.6.2».
    В error-логе apacha пусто, в error-логе nginx’a только это:

    2018/02/21 02:04:39 [error] 493#0: *53 upstream timed out (110: Connection timed out) while connecting to upstream, client: 185.13.112.127, server: тут_мой_домен, request: «GET / HTTP/1.1», upstream: «http://127.0.0.1:81/», host: «тут_мой_домен»
    2018/02/21 02:04:39 [error] 493#0: *53 openat() «/home/тут_мой_пользователь/data/www/тут_мой_домен/favicon.ico» failed (2: No such file or directory), client: 185.13.112.127, server: тут_мой_домен, request: «GET /favicon.ico HTTP/1.1», host: «тут_мой_домен»

    Причем ошибка в лог записывается уже после того, как браузер 504 выдаст.
    nginx и apache показывают статус active (running).
    Думал дело в правах на index.html, дал ему 777, присваивал и www-data:www-data и мой_пользователь:мой_пользователь.
    Безрезультатно.
    В чем может быть дело?

  12. Neuro75 :

    Прошу прощения, удалите, пожалуйста мой предыдущий комментарий.
    При настройке файрвольных правил iptables случайно lo трафик запретил. Ибо нечего до пяти утра сервера настраивать.
    Мануал работает на все 100%.

  13. > Это в главе 16 было: либо просто listen 80; в этом файле либо так как выше, чтобы работало на любом сайте.
    Поправил чуть статью, спасибо)

Написать комментарий