- Установка nginx
- Nginx в Docker
- 502 bad gateway и другие ошибки nginx
- Ошибка 404 not found
- Балансировка нагрузки
- Проксирование запросов
- Метод балансировки
- Работа nginx с php-fpm
- Настройка location в конфигурации
- Пример универсального конфига для nginx
- Nginx в связке с Apache
- Проблемы балансировки нагрузки
- Полный конфиг балансировщика nginx
- Добавление бэкендов
- Кэширование в nginx
- Настройка редиректов и rewrite правил
- Мониторинг nginx
- Рестарт nginx и другие параметры командной строки
- Виртуальные хосты
Установка nginx
В своей статье я не буду привязываться к конкретному дистрибутиву, так как настройка nginx одинакова везде. Формат файла один и тот же, поэтому конфигурация без проблем может мигрировать на разные системы. Править придется только пути к файлам и директориям. Тем не менее, я расскажу, как выполнить установку на популярные дистрибутивы.
Установку nginx на CentOS 7 я подробно разбирал в соответствующей статье про настройку web сервера на centos
. Здесь просто перечислю необходимые шаги.
Подключаем репозиторий nginx для CentOS 7:
# rpm -Uvh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm
# yum install nginx
Вот и все. На этом установка на Centos 7 закончена. Пример установки nginx на Debian.
Подключаем репозиторий для Debian:
# echo "deb http://nginx.org/packages/debian `lsb_release -cs` nginx" | tee /etc/apt/sources.list.d/nginx.list
Импортируем ключ для проверки подлинности пакетов:
# curl -fsSL https://nginx.org/keys/nginx_signing.key | sudo apt-key add -
Устанавливаем nginx на Debian:
# apt update && apt install nginx
Установим nginx на Ubuntu. Подключаем репозиторий:
# echo "deb http://nginx.org/packages/ubuntu `lsb_release -cs` nginx" | tee /etc/apt/sources.list.d/nginx.list
# curl -fsSL https://nginx.org/keys/nginx_signing.key | sudo apt-key add -
Выполняем установку nginx на Ubuntu:
# apt update && apt install nginx
На всех указанных системах запуск веб сервера выполняется командой:
# systemctl start nginx
Добавляем nginx в автозагрузку:
# systemctl enable nginx
В последнее время большое распространение получили контейнеры, в частности docker. Довольно популярна ситуация, когда nginx работает в докере, поэтому отдельно рассмотрю вопрос установки nginx в docker, хоть там и нет ничего сложного.
Nginx в Docker
Неоспоримые преимущества от использования docker получают разработчики, поэтому они очень часто его используют. В том числе в виде контейнеров docker с nginx. Установка nginx через docker может быть выполнена из официального образа nginx в Docker Hub
. Если не умеете работать с docker, смотрите мою статью по этой теме — установка docker в centos
. Установить и запустить nginx в docker можно примерно такой командой:
# docker run --name nginx01 -p 80:80 -d nginx
В данной команде:
- nginx01
— имя созданного контейнера, основанного на базовом образе nginx - -p 80:80
— сопоставление порта на локальной машине, порту в контейнере. Сначала указывается локальный порт, потом внутри контейнера. - -d
— ключ, обозначающий запуск контейнера в режиме службы.
Это общий случай установки. Образ nginx в данном случае будет скачан автоматически при создании первого контейнера. Для удобства используется большее количество параметров. Для примера запустим еще один контейнер с другим названием и с расширенными параметрами.
# docker run --name nginx02 -p 81:80 -v ~/nginx/www:/usr/share/nginx/html -v ~/nginx/conf:/etc/nginx -v ~/nginx/logs:/var/log/nginx -d nginx
В данном примере мы создали еще один контейнер naginx02, назначили ему порт 81 на локальной машине, в контейнер смонтировали 3 локальные директории:
- ~/nginx/www
— здесь будут лежать исходники сайта. - ~/nginx/conf
— полная конфигурация nginx. Ее нужно будет скопировать откуда-то. - ~/nginx/logs
— логи nginx.
Не обязательно выносить на хост все эти папки. Я показываю для примера. Вы выбирайте только то, что вам действительно нужно.
Таким образом можно легко работать с nginx с помощью docker. Можно без проблем запустить сколько угодно контейнеров с nginx, указав каждому свой порт, конфигурацию и директорию с исходниками. Для разработки это действительно удобно. Для продакшена отдельный разговор. Те, кто используют nginx в docker в production вряд ли нуждаются в данной статье.
502 bad gateway и другие ошибки nginx
Ошибка 502 bad gateway
знакома многим пользователям интернета, не только системным администраторам. Ее рано или поздно можно увидеть на любом сайте. Что она означает? В общем случае, это значит, что на веб сервере какие-то проблемы.
Ошибка 502 описана в RFC Hypertext Transfer Protocol (HTTP/1.1)
в разделе 6.6.3. Там говорится, что во время обработки запроса веб сервер, в данном случае nginx, не получил ответ от какого-то бэкенда. Расскажу, что это обычно значит на практике.
Допустим, у вас nginx работает в связке с apache или php-fpm. Вы видите описываемую ошибку. Причин может быть две:
- Службы php-fpm или apache перестали отвечать, потому что просто упали. В таком случае, nginx будет показывать всем пользователям ошибку 502, пока бэкенд не начнет отвечать.
- Служба просто не справляется с нагрузкой. Ошибка будет только у части пользователей, а у других все будет нормально.
То же самое может произойти, если вы настроили проксирование запросов через proxy_pass на какой-то другой сервер и этот сервер перестал отвечать. Nginx будет показывать ошибку 502 bad gateway в данном случае.
Для того, чтобы исправить 502-ю ошибку, надо разобраться, с чем конкретно она связана. Если бэкенд просто упал, то надо его поднять. Если же причина не в этом, то надо более детально разбираться. Чаще всего в логах ошибок nginx есть вся информация для решение проблем с ошибкой 502. Наиболее распространенная ситуация с этой ошибкой при работе в связке с php-fpm в том, что php-fpm просто не выдерживает нагрузки, либо неправильно настроен. Возможно, у него не хватает процессов для обработки всех запросов. Тогда часть запросов будут возвращать ошибку.
Вот типичный пример ошибки 502 при работе с php-fpm. Пользователь видит ошибку. Идем смотреть лог ошибок виртуального хоста. Видим там такую строку:
2019/03/29 15:00:42 [error] 2058#2058: *18 connect() failed (111: Connection refused) while connecting to upstream, client: 192.168.13.16, server: localhost, request: "GET /1.php HTTP/1.1", upstream: "fastcgi://127.0.0.1:9000", host: "192.168.13.34"
В данном случае php-fpm просто упал и перестал обрабатывать запросы.
Я достаточно часто встречаюсь с этой серверной ошибкой. Иногда разработчики напрограммируют таких конструкций, что они валят php-fpm по какой-то причине. Причем, если проект уже сдали и он давно работает, никто с этими ошибками разбираться уже не хочет. Тогда я просто ставлю костыль — автоматически перезапускаю php-fpm с помощью zabbix, ели вылезает ошибка 502 bad gateway в nginx. Такие вещи иногда годами работают и в целом всех устраивают.
Вы можете настроить внешний вид страницы с ошибкой. То, что я показал на скрине — стандартный вид ошибки 502 bad gateway в nginx при отсутствии каких-то настроек на этот счет. Вы же можете установить свою страницу при возникновении этой ошибки. На ней можно написать, к примеру, что на сервере ведутся технические работы и скоро он заработает вновь. В каждый виртуальный хост можно установить свою страницу с ошибкой. Настраивается она в секции server.
error_page 500 502 503 504 /error50x.html; location = /50x.html { root /usr/share/nginx/html; }
Ошибка 404 not found
Еще одна популярная ошибка nginx — 404 not found
.
Тут все понятно и без объяснений — пользователь открывает ссылку, а документа по этой ссылке нет. Иногда бывает не очевидно, почему по ссылке выходит 404 ошибка. В таком случае рекомендую сразу смотреть лог ошибок. Вот пример.
2019/03/29 15:18:48 [error] 2058#2058: *20 open() "/usr/share/nginx/html/502" failed (2: No such file or directory), client: 192.168.13.16, server: localhost, request: "GET /502 HTTP/1.1", host: "192.168.13.34"
Страницу с этой ошибкой можно так же кастомизировать. Это делают практически все крупные сайты. Вот, к примеру, как выглядит эта ошибка у меня на сайте.
В данном случае внешний вид страницы с ошибкой формирует сам wordpress. Но вы так же можете это настроить самостоятельно через nginx.
Балансировка нагрузки
Nginx может выступать в качестве балансировщика. Настройку балансировки в nginx
я так же подробно рассматривал отдельно. Все подробности в статье. Здесь кратко упомяну, что это такое. Nginx умеет распределять нагрузку между несколькими серверами. Правила распределения настраиваются, как и количество серверов. Покажу как это делать на предыдущем примере с apache.
Допустим, у вас очень нагруженный сайт. А нагрузить Bitrix без настроенного кэширования, очень просто. Вы хотите добавить еще один сервер с Apache, чтобы распределить нагрузку между двумя серверами. Сразу скажу, что это не такой простой вопрос, как может показаться вначале. Со стороны nginx настройки действительно простые, но нужно будет рассмотреть отдельно вопрос общего файлового хранилища для обоих серверов и общей базы данных. Ее, скорее всего, тоже нужно будет выносить на отдельный сервер, хотя и не обязательно. Если nginx будет раздавать статику, то доступ к файлам должен быть и у него. В общем, тут нужно хорошенько все продумать и подготовить. Может как-нибудь напишу статью с примером на данную тему.
Итак, распределим нагрузку между двумя бэкендами с Apache. Вот конфигурация виртуального хоста в этом случае.
upstream backend { server 192.168.0.10:8080; server 192.168.0.11:8080; } server { listen 80; server_name example.com www.example.com; access_log /var/log/nginx/example.com.access.log main; location ~* ^.+.(js|css|png|jpg|jpeg|gif|ico|woff|woff2|swf|ttf|svg|html|txt)$ { root /var/www/example.com/public; expires 1y; } location / { proxy_pass http://backend/; 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_connect_timeout 120; proxy_send_timeout 120; proxy_read_timeout 180; } }
В данном примере балансировки нагрузки, nginx отдает всю статику, а 2 сервера с apache обрабатывают все остальные запросы.
Проксирование запросов
Nginx очень производительный веб сервер, поэтому его часто используют в качестве Reverse Proxy для других служб и серверов. Подробно вопрос проксирования запросов в nginx с помощью proxy_pass
я рассмотрел отдельно. Сейчас же в двух словах объясню, что это такое. Допустим, у вас есть какой-то сервис на отдельном сервере и вы ходите перенаправлять на него часть запросов с вашего сайта. Для этого вы делаете отдельный location и указываете, что все запросы по определенному правилу нужно перенаправлять на этот сервер.
location /forum/ { proxy_pass http://192.168.13.31; 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_redirect default; }
Все запросы с урлами, содержащими /forum/ будут перенаправлены на отдельный сервер, где трудится тяжелый форум со своей базой данных и своими настройками. Им может управлять другой администратор и вообще он не имеет к вам никакого отношения. Таким образом, большой проект можно разделить на части с делегированием полномочий. Но это отдельная история.
Nginx proxy_pass удобно использовать разработчикам для проксирования запросов в разные docker контейнеры, которые подняты на рабочей машине на разных портах. Можно перенаправлять не только отдельные урлы, но и весь сайт. Допустим, вы делаете какие-то фильтрации трафика на стороне сервера с nginx, а потом все запросы отправляете на исходный сайт. Тогда делаете простую настройку для проксирования:
location / { proxy_pass http://192.168.13.31; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real-IP $remote_addr; }
Все запросы уходят на сторонний сервер. Частным случаем проксирования в nginx является работа в связке с Apache, о чем я расскажу далее.
Метод балансировки
Соединения к серверам для балансировки нагрузки могут распределяться по различным правилам. Существуют несколько методов распределения запросов. Я перечислю основные:
- round-robin
— используется по умолчанию. Веб сервер равномерно распределяет нагрузку на сервера с учетом их весов. Специально указывать этот метод в конфигурации не надо. - least-connected
— запрос отправляется к серверу с наименьшим количеством активных подключений. В конфигурации данный параметр распределения запросов устанавливается параметром least_conn. - ip-hash
— используется хэш функция, основанная на клиентском ip адресе, для определения, куда направить следующий запрос. Используется для привязки клиента к одному и тому же серверу. В предыдущих методах один и тот же клиент может попадать на разные серверы. - hash
— задаёт метод балансировки, при котором соответствие клиента серверу определяется при помощи хэшированного значения ключа. В качестве ключа может использоваться текст, переменные и их комбинации. - random
— балансировка нагрузки, при которой запрос передаётся случайно выбранному серверу, с учётом весов.
В платной версии существуют дополнительный более продвинутый метод распределения нагрузки — least_time
, при котором запрос передаётся серверу с наименьшими средним временем ответа и числом активных соединений с учётом весов серверов.
upstream cache-api { ip_hash; server 10.32.18.6:8080; server 10.32.18.7:8080; server 10.32.18.8:8080; }
Работа nginx с php-fpm
В предыдущих разделах я уже показал примеры конфигурации, где запросы по определенным URI перенаправляются на php-fpm. Еще более подробно я рассмотрел этот вопрос в отдельной статье по настройке php-fpm
. Сейчас просто покажу на реальном примере, как выглядит взаимодействие nginx и php-fpm.
Php-fpm может слушать как сокет unix, так и tcp порт. Эти настройки задаются в конфиге пула. Это может быть либо
listen = 127.0.0.1:9000
listen = /var/run/php-fpm/php-fpm.sock
В зависимости от того, в каком режиме работает php-fpm, зависят настройки в nginx.
Вот примерный конфиг php-fpm для пула www.conf на виртуальной машине с 1Gb памяти.
[www] listen = /var/run/php-fpm/php-fpm.sock listen.allowed_clients = 127.0.0.1 listen.mode = 0660 listen.owner = nginx listen.group = nginx user = nginx group = nginx ; как будут создаваться новые рабочие процессы pm = dynamic ; максимальное количество рабочих процессов pm.max_children = 15 ; число запущенных процессов при старте сервера pm.start_servers = 6 ; минимальное и максимальное количество процессов в простое pm.min_spare_servers = 4 pm.max_spare_servers = 8 slowlog = /var/log/php-fpm/www-slow.log pm.max_requests = 250 php_admin_value[error_log] = /var/log/php-fpm/www-error.log php_admin_flag[log_errors] = on php_value[session.save_handler] = files php_value[session.save_path] = /var/lib/php/session pm.status_path = /status
Чтобы заработал php в nginx через php-fpm, достаточно убедиться, что php-fpm работает и указать в виртуальном хосте location для php. Пример реальных настроек для wordpress сайта.
location ~ \.php$ { try_files $uri =404; fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock; #fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param DOCUMENT_ROOT /web/sites/example.com/www/; fastcgi_param SCRIPT_FILENAME /web/sites/example.com/www$fastcgi_script_name; fastcgi_param PATH_TRANSLATED /web/sites/example.com/www$fastcgi_script_name; fastcgi_param QUERY_STRING $query_string; fastcgi_param REQUEST_METHOD $request_method; fastcgi_param CONTENT_TYPE $content_type; fastcgi_param CONTENT_LENGTH $content_length; fastcgi_param HTTPS on; # включать, если сайт по https работает fastcgi_intercept_errors on; fastcgi_ignore_client_abort off; fastcgi_connect_timeout 60; fastcgi_send_timeout 180; fastcgi_read_timeout 180; fastcgi_buffer_size 128k; fastcgi_buffers 4 256k; fastcgi_busy_buffers_size 256k; fastcgi_temp_file_write_size 256k; }
В целом все. Этого достаточно для настройки связки nginx + php-fpm. Типовой ошибкой в данном случае является то, что nginx не имеет доступа к unix сокету php-fpm. По-умолчанию после установки он запускается с правами apache. Если пользователя не исправить на nginx, то у веб сервера не будет доступа к сокету, php не заработает. В своем примере конфига php-fpm я указал пользователя правильно.
Настройка location в конфигурации
Как вы могли заметить, в предыдущем примере я использовал 2 разных location для виртуального хоста. Постараюсь простыми словами рассказать, что это такое, как использовать и для чего вообще нужно.
С помощью location в nginx вы можете управлять конфигурацией в зависимости от URI запроса. Если в виртуальных хостах мы могли переопределять настройки в зависимости от имени домена, то тут мы спускаемся на уровень ниже и управляем параметрами в зависимости от пути запроса. В примере с виртуальными хостами у меня было 2 location:
- / — корень сайта, перехватывает вообще все запросы к домену.
- ~ \.php$ — запросы к файлам с расширением php. То есть если в запросе указан путь к файлу .php, то к нему применяется параметр fastcgi_pass и запрос отправляется на обработку к php-fpm.
Location можно задавать префиксной строкой или регулярным выражением. В регулярных выражениях используются модификаторы ~, либо ~* Без звездочки учитывается регистр, со звездочкой нет. Обработка location в конфигурационном файле идет в следующей последовательности:
- Первыми проверяются префиксные строки. Совпадения запоминаются.
- Дальше проверяются регулярные выражения в том порядке, как они перечислены в конфигурационном файле. Проверка по регулярным выражениям прекращается сразу же после совпадения. Если совпадение не было найдено, используется запомненный ранее префикс.
Так же в location может использоваться префикс =
Он означает точное совпадение запроса и заданного location. После такого совпадения, остальные проверки сразу же прекращаются. Рекомендуется использовать этот префикс, если у вас огромное количество конкретных запросов. Используя префикс =
для них, вы снизите нагрузку на сервер, так как запросы не будут проверяться по всем правилам.
В крупном сайте может быть огромное количество различных location. Вот несколько простых реальных примеров из моей практики.
На своем сайте я решил отказаться от использования amp страниц, выключил их и сделал перенаправление с этих страниц на обычные. В данном примере amp располагались по исходному урлу с добавлением /amp/ в конец пути.
location ~ /amp/$ { rewrite ^(.*/)amp/$ $1 permanent; }
В этом примере укажем максимальный срок хранения картинок и шрифтов в кэше а так же отключим для них логирование.
location ~* ^.+.(js|css|png|jpg|jpeg|gif|ico|woff|woff2|swf|ttf|svg)$ { access_log off; expires 1y; }
Закроем доступ к директории .git
на сайте.
location ~ /.git { return 404; }
Запретим исполнение скриптов в перечисленных директориях.
location ~* /(images|cache|media|logs|tmp)/.*.(php|pl|py|jsp|asp|sh|cgi)$ { return 404; }
И так далее. Думаю, смысл понятен. Примеров может быть огромное количество. Location важный параметр конфигурации в настройке nginx. Немного информации на эту тему можно посмотреть в соответствующем разделе
документации.
Пример универсального конфига для nginx
В завершении своей статьи про настройку nginx, я хочу привести шаблон универсального конфига, который я обычно использую при настройке веб сервера. Сил уже нет писать статью, поэтому привожу его без подробных комментариев. Надеюсь сами разберетесь с помощью документации. Статью писал несколько дней и под конец устал уже 🙂
пользователь nginx; рабочие_процессы авто; worker_cpu_affinity авто; worker_rlimit_nofile 30000; pid /var/run/nginx.pid; pcre_jit включен; события { worker_connections 8192; мульти_принять; } http { # Базовый ####################### отправить файл включен; tcp_nopush включен; tcp_nodelay включен; reset_timedout_connection включен; keepalive_timeout 120; keepalive_requests 1000; типы_хэш_макс_размер 2048; server_tokens отключены; send_timeout 30; client_body_timeout 30; client_header_timeout 30; server_names_hash_max_size 4096; # Ограничения ####################### client_max_body_size 10м; client_body_buffer_size 128 КБ; client_body_temp_path /var/cache/nginx/client_temp; proxy_connect_timeout 60; proxy_send_timeout 60; proxy_read_timeout 60; proxy_buffer_size 4k; proxy_buffers 8 16k; proxy_busy_buffers_size 64 КБ; proxy_temp_file_write_size 64 КБ; proxy_temp_path /var/cache/nginx/proxy_temp; включить /etc/nginx/mime.types; default_type application/octet-stream; # Журналы ######################### log_format main '$remote_addr - $host [$time_local] "$request" ' '$статус $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"' 'rt=$request_time ut=$upstream_response_time' 'cs=$upstream_cache_status'; log_format полный '$remote_addr - $host [$time_local] "$request" ' 'длина_запроса=$длина_запроса' 'status=$status bytes_sent=$bytes_sent' 'body_bytes_sent=$body_bytes_sent' 'реферер=$http_referer' 'user_agent="$http_user_agent" ' 'upstream_status=$upstream_status' 'время_запроса=$время_запроса' 'upstream_response_time=$upstream_response_time' 'upstream_connect_time=$upstream_connect_time' 'upstream_header_time=$upstream_header_time'; журнал_доступа /var/log/nginx/access.log основной; журнал_ошибок /var/log/nginx/error.log; # Gzip ######################### gzip включен; gzip_static включен; gzip_types текст/обычный текст/приложение css/приложение json/текст javascript/приложение xml/приложение xml/текст xml+rss/приложение javascript/изображение javascript/изображение x-icon/приложение svg+xml/x-font-ttf; gzip_comp_level 9; gzip_proxy любой; gzip_min_length 1000; gzip_disable "msie6"; gzip_vary включен; пометка выключена; # Если собран модуль видеозаписи brotli #brotli_static включено; #бротли на; #brotli_comp_level 6; #brotli_types текст/обычный текст/текст css/приложение xml/изображение javascript/изображение x-icon/svg+xml; # Кэш ######################## #proxy_cache_valid 1 м; #proxy_cache_key $scheme$proxy_host$request_uri$cookie_US; #proxy_cache_path /web/sites/nginx_cache level=1:2 keys_zone=main:1000m; # Границы зоны ################# limit_conn_zone $binary_remote_addr zone=perip:10m; limit_req_zone $binary_remote_addr zone=lim_5r:10m rate=5r/s; # lim для динамической страницы limit_req_zone $binary_remote_addr zone=lim_1r:10m rate=1r/s; # lim для страницы поиска limit_req_zone $binary_remote_addr zone=lim_10r:10m rate=10r/s; # SSL ########################## ssl_session_cache общий: SSL: 50 м; ssl_session_timeout 1 д; ssl_session_tickets включен; ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; ssl_ciphers 'TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-128-GCM-SHA256:TLS13-AES-256-GCM-SHA384:ECDHE:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECD ОН- ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE- RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE- ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA- AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256: AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:! Д СС'; ssl_prefer_server_ciphers включен; ssl_dhparam /etc/ssl/certs/dhparam.pem; ssl_stapling включен; ssl_stapling_verify включен; add_header Strict-Transport-Security max-age=15768000; резольвер 8.8.8.8; включить /etc/nginx/conf.d/*.conf; # Для мониторинга ########### сервер { слушать 127.0.0.1:80; имя_сервера status.localhost; keepalive_timeout 0; разрешить 127.0.0.1; отрицать все; доступ_лог выключен; местоположение/статус сервера { stub_status включен; } местоположение/статус { доступ_лог выключен; разрешить 127.0.0.1; отрицать все; включить fastcgi_params; fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; } } }
На этой базовой настройке nginx все! Надеюсь, было полезно.
Nginx в связке с Apache
Популярным кейсом работы nginx является работой в качестве обратного прокси для Apache. Лет 5-10 назад, когда nginx был не очень распространенным, это было очень популярное решение. Сейчас Nginx во многих полностью заменил Apache, но тем не менее, пока еще не до конца. К примеру, очень популярный в России движок для сайтов Bitrix до сих пор требует в качестве web сервера Apache.
Покажу на простом примере, как Nginx настроить в качестве front-end к Apache. Статические данные будет обрабатывать сам nginx, а динамические запросы перенаправлять на apache.
Для начала вам необходимо настроить apache
стандартным образом, с той лишь разницей, что слушать он должен не привычный 80-й или 443 порт, а, к примеру, 8080. Далее в настройках виртуального хоста указываете для каких запросов будет перенаправление на apache. Для примера отправим туда все, что не является статичным контентом. Создаем 2 location:
- Статика, которую будет напрямую отдавать Nginx.
- Все остальные запросы, которые будет обрабатывать Apache.
server { listen 80; server_name example.com www.example.com; access_log /var/log/nginx/example.com.access.log main; location ~* ^.+.(js|css|png|jpg|jpeg|gif|ico|woff|woff2|swf|ttf|svg|html|txt)$ { root /var/www/example.com/public; expires 1y; } location / { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_connect_timeout 120; proxy_send_timeout 120; proxy_read_timeout 180; } }
В данном примере Nginx и Apache работают на одном сервере. Но это не обязательно. Web сервер с apache вы без проблем можете разместить на другой машине. Отдельно будет стоять вопрос определения реальных ip адресов клиентов на сервере с Apache. Я его рассмотрел подробно в статье про nginx revers proxy, ссылку на которую привел в предыдущем разделе.
Проблемы балансировки нагрузки
Проблем при балансировки нагрузки с помощью nginx может быть масса. В обычном рабочем проекте нельзя просто взять и разделить нагрузку на несколько серверов. Само приложение, его БД должны быть готовы к этому. Первое, с чем вы столкнетесь — как правильно определить, что с сервером проблемы. В бесплатной версии nginx нет никаких инструментов для того, чтобы определить, что ваш бэкенд отвечает правильно.
Допустим, один из серверов перегружен и он отдает неправильные ответы. То есть он жив, отвечает, но ответы нам не подходят. Балансировщик будет считать, что все в порядке, запросы на проблемный сервер будут продолжать идти. Nginx исключит его из списка бэкендов только тогда, когда он полностью перестанет отвечать. Но это лишь малая часть проблем, которые могут приключиться. По моему опыту, чаще всего сервера не отваливаются полностью, а начинают тупить или отдавать, к примеру 502 ошибку. В бесплатной версии nginx будет считать, что все в порядке.
Для того, чтобы анализировать ответ бэкенда и в зависимости от этого ответа, решать, в каком состоянии находится сервер, вам необходим модуль ngx_http_upstream_hc_module
. Он доступен только в коммерческой подписке. С помощью этого модуля можно тестировать код ответа, наличие или отсутствие определённых полей заголовка и их значений, а также содержимое тела ответа. Без этих данных качественно настроить работу балансировщика трудно.
Отмечу еще несколько полезных настроек, на которые надо обратить внимание, при настройке балансировки нагрузки с помощью nginx:
- proxy_connect_timeout
— задаёт таймаут для установления соединения с проксированным сервером. При отсутствии ответа за указанное время сервер будет считаться неработающим. Дефолтное значение 60 секунд. Это очень много, если у вас большие нагрузки. За минуту скопится огромное количество висящих соединений, которые мог бы обработать другой бэкенд. - proxy_read_timeout
— задаёт таймаут при чтении ответа проксированного сервера. Дефолт тоже 60 секунд. Чаще всего имеет смысл уменьшить значение.
Полный конфиг балансировщика nginx
Привожу пример полного конфига виртуального хоста для балансировки нагрузки на примере nginx. Это не пример с рабочего сервера, так что возможны какие-то мелкие ошибки или опечатки. Не тестировал конфиг, так как это не готовое how to, а просто рекомендации и описание. Составил его чтобы было представление, как все это выглядит в единой конфигурации.
log_format вверх по течению '$remote_addr - $host [$time_local] "$request" ' 'длина_запроса=$длина_запроса' 'status=$status bytes_sent=$bytes_sent' 'body_bytes_sent=$body_bytes_sent' 'реферер=$http_referer' 'user_agent="$http_user_agent" ' 'upstream_status=$upstream_status' 'время_запроса=$время_запроса' 'upstream_response_time=$upstream_response_time' 'upstream_connect_time=$upstream_connect_time' 'upstream_header_time=$upstream_header_time'; API-интерфейс восходящего потока { ip_хэш; сервер 10.32.18.7:8080 max_fails=2 fail_timeout=10 с; сервер 10.32.18.6:8080 max_fails=2 fail_timeout=10 с; сервер 10.32.18.8:8080 max_fails=2 fail_timeout=10 с; } сервер { слушать 443 ssl http2; имя_сервера cache-api.sample.com; access_log /var/log/nginx/cache-api-access.log вверх по течению; журнал_ошибок /var/log/nginx/cache-api-error.log; ssl_certificate /etc/ssl/sample.com.crt; ssl_certificate_key /etc/ssl/sample.com.key; корень /var/www/html; расположение / { proxy_pass http://cache-api/; proxy_read_timeout 15; proxy_connect_timeout 3; proxy_set_header Хост $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real-IP $remote_addr; } }
За подробностями параметров разделов с proxy_pass
собраны в отдельной статье на эту тему.
Добавление бэкендов
Для того, чтобы начать балансировать нагрузку, необходимо добавить бэкенды в настройки nginx. Для примера, я возьму отдельный виртуальный хост. Идем в его конфиг и в самое начало перехода три бэкенда через директиву вверх по течению
.
вышестоящий cache-api { сервер 10.32.18.6:8080; сервер 10.32.18.7:8080; сервер 10.32.18.8:8080; }
Сейчас я не касаюсь тонкой настройки балансировки. Будем идти от простого к сложному. На момент использования мы добавили три сервера, на которые будет загружена нагрузка. В дальнейшем в случае возникновения хоста будет обнаружена локация, запросы к которой будут быстро приближаться.
местоположение / { proxy_pass http://cache-api/; proxy_set_header Хост $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real-IP $remote_addr; }
Этого минимального набора настроек достаточно, чтобы nginx начал отображать запросы между двумя серверами. В реальных ситуациях требуется более детальная настройка балансировщика. Для этого возможно использование параметров:
Подробнее об этом написано в официальной документации, в описании модуля ngx_http_upstream_module
. К примеру, конфиг бэкендов для балансировки может быть таким.
сервер 10.32.18.6:8080 max_fails=2 fail_timeout=10 с; сервер 10.32.18.7:8080 max_fails=2 fail_timeout=10 с; сервер 10.32.18.8:8080 max_fails=2 fail_timeout=10 с;
С настройками после двух неудачных соединений в течение 10 секунд, бэкенд будет выведен из работы на те же 10 секунд.
Кэширование в nginx
Тема кэширования очень обширна. Лучше использовать множество копий, сломанных о том, какое и где кэширование. В том же wordpress существует огромное множество дополнений для кэширования. Nginx может самостоятельно хранить и управлять кэшем. В некоторых случаях это будет эффективнее, чем использовать добавки. Но все сильно зависит от отдельных проектов.
Кэшировать Nginx может разные вещи:
- Статику, которую получает с удаленного сервера, сохраняет себе и раздает быстрее, чем удаленный сервер.
- Динамику, превращая ее в статику и раздавая самостоятельно, без обращения к бэкенду.
Конкретно с WordPress я обычно поступаю советом. Кэширование nginx я не использую. Вместо этого я использую WP Total Cache. Он формирует готовые html-страницы по заданным параметрам. А дальше я эти страницы отдаю напрямую через nginx, минуя вообще доход WordPress. За счет этого охватывается очень быстрое воздействие. Вот пример настройки Nginx из секции сервера существующего хоста для отдачи кэша WordPress.
set $cache_uri $request_uri; if ($request_method = POST) { set $cache_uri 'null cache'; } if ($query_string != "") { set $cache_uri 'null cache'; } if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|/wp-(app|cron|login|register|mail).php|wp-.*.php|/feed/|index.php|wp-comments-popup.php|wp-links-opml.php|wp-locations.php|sitemap(_index)?xml|[a-z0-9_-]+-sitemap([0-9]+)?xml)") { set $cache_uri 'null cache'; } if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_logged_in") { set $cache_uri 'null cache'; } location / { try_files /wp-content/cache/supercache/$http_host/$cache_uri/index-https.html $uri $uri/ /index.php?$args; }
Сначала идут проверки для добавления исключений к некоторым запросам, для которых кэширование не будет работать. А потом отдается статика из директории с кэшом. Если для заданного URI кэша нет, он уходит дальше в обработку.
В данном случае используется именно плагин WP, а не кэш nginx только из-за удобства управления кэшом через панель управления сайтом. Сам плагин делает ровно то же самое, что может делать nginx. Кэш в самом nginx настраивается следующим образом.
Сначала добавляются настройки кэширования в nginx.conf
. Дальше речь пойдет о кэшировании динамики через fastcgi.
http { . fastcgi_cache_path /var/cache/nginx/php levels=1:2 keys_zone=php_cache:32m max_size=3g; fastcgi_cache_key "$scheme$request_method$host$request_uri"; . }
- fastcgi_cache_path — директива для объявления кэша для fatcgi;
- /var/cache/nginx/php — директория, где будут храниться файлы кэша;
- levels=1:2 — уровень вложенности каталогов в директории с кэшом;
- keys_zone=php_cache — название зоны;
- max_size=3g — размер директории с кэшом в 3Гб.
Не забудьте создать указанную директорию для кэша /var/cache/nginx/php
. В конфигурацию виртуального хоста добавляем параметры кэширования в location с php.
location ~ \.php$ { fastcgi_pass unix:/var/run/php-fpm.sock; fastcgi_index index.php; include fastcgi_params; fastcgi_cache php_cache; fastcgi_cache_valid 200 120m; } }
Кэшируем ответы с кодом 200 на 120 минут.
Для кэширования запросов с ответами от бэкенда через proxy_pass, необходимо использовать директиву proxy_cache_path
.
proxy_cache_path /var/cache/nginx/proxy levels=1:2 keys_zone=proxy_cache:32m max_size=3G;
И далее в вирутальном хосте.
location / { proxy_pass http://backend; proxy_cache proxy_cache; proxy_cache_valid 200 120m; }
Подробнее о кэшировании читайте
в блоге nginx. Там очень много нюасов. Как минимум надо аккуратно прорабатывать исключения, чтобы в кэш не попадало то, что там быть не должно. Например, cookie или страницы из закрытой административной части.
Настройка редиректов и rewrite правил
Rewrite это то, что в первую очередь не позволяет отказаться от Apache. Многие проекты имеют массу rewrite
правил в .htaccess
, который поддерживает apache. Перенос этих правил в nginx не всегда прост и очевиден. Есть средства по автоматической конвертации правил rewrite из формата apache в формат nginx, но они далеко не всегда помогают. Пример такого сервиса — https://winginx.com/ru/htaccess
. В идеале, такую конвертацию следует проделывать вручную, подключая голову 🙂
Это что касается конвертации правил из апача. А в целом rewrite правила в nginx это очень мощный инструмент. Вот несколько примеров правил.
С помощью rewrite можно отрезать у доменных имен www. Лично я считаю эту добавку к адресу сайта ненужным рудиментом и отрезаю на своих сайтах. Пример правила rewrite для замены www на запрос без него.
server { listen 443 ssl http2; server_name www.example.com; rewrite ^ https://example.com$request_uri permanent; }
Это правило rewrite все запросы к домену с www переадресовывает на запрос без www. Тут это просто пример работы механизма. В данном конкретном случае, редирект www лучше сделать с помощью return
. Это более элегантное и быстрое решение, так как не придется обрабатывать лишнюю регулярку. На практике конкретно с www лучше поступить вот так:
server { listen 443 ssl http2; server_name www.example.com; return 301 $scheme://example.com$request_uri; }
Синтаксис rewrite запросов выглядит следующим образом:
rewrite regex URL [flag];
В моем примере получаем следующие элементы правила:
- ^
— регулярное выражение; - https://example.com$request_uri
— url, на который заменяем; - permanent
— флаг, который возвращает постоянное перенаправление с кодом 301.
Вот еще один пример, который я уже приводил в разделе про location. Я настраиваю замену страниц с /amp/ на конце на обычный url без /amp/. То есть просто его обрезаю.
location ~ /amp/$ { rewrite ^(.*/)amp/$ $1 permanent; }
Полезным является еще одно правило rewrite, которое к ссылкам в конце без слеша добавляет слеш. То есть заменяет ссылку вида http://example.com/page
на http://example.com/page/
rewrite ^([^.]*[^/])$ $1/ permanent;
Много видел в сети примеров, где rewrite используют для перенаправления запросов с http на https. Как и в случае с заменой www, так лучше не делать. Тем более не стоит это делать через условия if, например вот так:
if ($scheme = "http") { rewrite ^ https://example.com$uri permanent; }
Это рабочий вариант, но в данном случае return
вместо rewrite
будет работать более эффективно, затрачивая меньше ресурсов web сервера. То же самое относится к примерам, где требуется замена имени домена в случае переезда. Вместо rewrite лучше использовать return. То есть делать не вот так:
server { listen 80; server_name old-name.com; rewrite ^ $scheme://new-name.com$request_uri permanent; }
return 301 $scheme://new-name.com;
Такой редирект с одного домена на другой быстрее работает.
Принципиальная разница между rewrite и return в том, что в rewrite переписывается только та часть исходного URL, которая соответствует регулярному выражению, а в return весь URL-адрес переписывается на указанный URL-адрес. Из этой особенности следует то, что return работает быстрее rewrite, поэтому там, где можно использовать return, лучше использовать именно его. Условно, return следует использовать там, где требуется постоянная замена адреса, а rewrite где требуется временное изменение запроса в силу каких-то обстоятельств.
Вот пример, где без rewrite не обойтись. Допустим, у вас есть какой-то обработчик запросов, которому нужно передавать различные урлы в определенном формате. К примеру, пользователи запрашивают урл http://example.com/linux/ubuntu
, а нам надо его преобразовать в такой — http://example.com/linux.php?distro=ubuntu
. Делаем это с помощью следующего правила rewrite.
rewrite ^/linux/(.*)$ /linux.php?distro=$1 last;
Еще пример, как сделать постоянное перенаправление с одной страницы на другую. Как обычно, можно сделать двумя способами, с помощью rewrite или return. Более правильно использовать return. Меняем адрес http://example.com/linux/ubuntu/
на http://example.com/windows/win10/
location = /linux/ubuntu/ { return 301 /windows/win10/; }
Вот нежелательный вариант, который тем не менее постоянно рекомендуют в разных статьях. Ошибки тут нет, но с return более правильно.
location = /linux/ubuntu/ { rewrite ^/linux/ubuntu/$ /windows/win10/ permanent; }
Фух, надеюсь с return и rewrite более ли менее понятно объяснил. Есть еще для перенаправлений try_files
. Я немного плаваю в этой теме, поэтому решил не добавлять эти правила, чтобы вас не запутать и не наговорить неправды. К примеру, вопрос со слешами на конце урла в wordpress можно решить с помощью try_files таким образом:
try_files $uri $uri/ /index.php?$args;
Проверяется запрос со слешом, потом без него, если ничего не найдено, запрос уходит на index.php с параметрами. То есть не важно, что наберет пользователь, у него в любом случае будет открыта та или иная страница. Более подробно о различных правилах перенаправления можно почитать в этой англоязычной статье
. Это наиболее полная информация по данной теме, что мне нагуглилась.
Мониторинг nginx
Для настройки мониторинга nginx, необходимо внести некоторые параметры в конфигурационный файл nginx.conf
. После этого nginx сам будет отдавать базовую информацию о состоянии сервера с помощью модуля ngx_http_stub_status_module
. Добавляем в секцию http следующее.
server { listen 127.0.0.1:80; server_name status.localhost; keepalive_timeout 0; allow 127.0.0.1; deny all; location /server-status { stub_status on; } access_log off; }
Перезапускаем nginx и проверяем. Я разрешил отдавать состояние о своем статусе только при запросе с локального сервера, где сам nginx работает. Поэтому смотрим через консоль сервера.
# curl http://localhost/server-status
Какие метрики мы здесь видим:
- Active connections — количество активных клиентских соединений.
- accepts — число принятых клиентских соединений.
- handled — число обработанных соединений.
- requests — число клиентских запросов.
- Reading — число соединений, в которых nginx в настоящий момент читает заголовок запроса.
- Writing — число соединений, в которых nginx в настоящий момент отвечает клиенту.
- Waiting — число бездействующих клиентских соединений в ожидании запроса.
Что делать с этими данными — решать вам в зависимости от того, какую систему мониторинга вы используете. Более подробно о мониторинге nginx
читайте в отдельной статье.
Рестарт nginx и другие параметры командной строки
Перед тем, как двигаться дальше к настройке nginx, предлагаю пройтись по основным параметрам в командной строке. Это упростит и ускорит дальнейшую работу.
Прежде всего расскажу, как перезапустить nginx. С помощью systemctl на всех дистрибутивах это выглядит одинаково.
# systemctl restart nginx
Перед перезагрузкой nginx, рекомендую выполнить проверку конфигурации:
# nginx -t
Еще одна важная команда, с помощью которой можно применить новую конфигурацию nginx без остановки и перезапуска веб сервера. Будет запущен новый рабочий процесс с новой конфигурацией, а старые процессы плавно завершатся.
# nginx -s reload
Следующая команда помимо тестирования конфигурации, выводит полный конфиг на экран. Вывод можно направить в отдельный файл и там проанализировать. Это удобно, когда у вас конфигурация состоит из множества вложенных конфигов, правильность которых трудно оценить разом.
# nginx -T
Так же бывает полезно посмотреть полную информацию о версии nginx, параметрах сборки, модулях и т.д.
# nginx -V
Например, мне эта информация была нужна, когда я делал собственную сборку nginx c поддержкой tls 1.3 и модулем сжатия brotli
.
В принципе, на этом все. Не припоминаю, чтобы я использовал что-то еще. Плавно переходим к конфигурации nginx.
Виртуальные хосты
После дефолтной установки nginx у вас уже будет один виртуальный хост, который описан конфигом default.conf
. Обычно конфиги с виртуальными хостами расположены в директории /etc/nginx/conf.d
. Здесь, в отличие от Apache, структура размещения конфигурационных файлов одинаковая на всех дистрибутивах, что очень удобно.
Что такое виртуальный хост? Попробую объяснить своими словами. Это конфигурационный файл, который описывает настройку условно одного домена. Для удобства, каждый виртуальных хост выносят в отдельный конфиг, но никто не мешает вам все это описывать в общем конфигурационном файле. Дело в том, что если доменов у вас много, то работать в одном большом конфиге не удобно, поэтому я рекомендую вам придерживаться схемы файлов конфигурации, когда один конфиг описывает параметры одного домена.
Виртуальные хосты наследуют параметры из основного файла конфигурации nginx.conf, но эти параметры могут быть переопределены в конкретном виртуальном хосте. То есть можно задать дефолтные параметры для всех сайтов, но в случае необходимости переопределить какой-то параметр в конкретном виртуальном хосте. Вот типичный пример конфига.
server { listen 80; server_name example.com www.example.com; access_log /var/log/nginx/example.com.access.log main; root /var/www/example.com/public; location / { index index.html index.htm index.php; } location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; include fastcgi_params; } }
Все не указанные явно в этом виртуальном хосте параметры будут унаследованы из основного файла конфигурации nginx.conf
. Допустим, вам надо по какой-то причине отключить сжатие gzip на этом хосте и указать кодировку windows-1251. Тогда конфиг приобретет следующий вид.
server { listen 80; server_name example.com www.example.com; access_log /var/log/nginx/example.com.access.log main; root /var/www/example.com/public; charset windows-1251; gzip off; location / { index index.html index.htm index.php; } location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; include fastcgi_params; } }
С помощью виртуальных хостов в nginx вы можете очень гибко настраивать конфигурацию каждого домена. Как минимум я рекомендую для каждого домена делать отдельно:
- директорию с исходниками сайта;
- директорию с логами;
- в некоторых ситуациях отдельный php-fpm пул для каждого сайта или группы сайтов.
Более полный пример настройки виртуального хоста для реального сайта на wordpress смотрите в отдельной статье по настройке web сервера
. Мы же переходим к настройке location.