Структура и контексты конфигурационного файла Nginx

Nginx – это высокопроизводительный веб-сервер, который часто используют для обслуживания сайтов. Он особенно хорошо справляется с обработкой большого количества параллельных соединений и обслуживанием статического контента.

Новички часто путаются в конфигурационных файлах Nginx. Данный мануал ознакомит вас с базовой структурой конфигурационного файла Nginx. Также здесь вы найдете рекомендации по созданию конфигурационных файлов.

Контексты Nginx

В этом мануале будет рассмотрена базовая структура, содержащаяся в главном конфигурационном файле Nginx. Местоположение этого файла будет отличаться в зависимости от того, как вы установили программное обеспечение на своем компьютере. Во многих дистрибутивах файл будет находиться в /etc/nginx/nginx.conf. Если его там нет, он также может быть в /usr/local/nginx/conf/nginx.conf или /usr/local/etc/nginx/nginx.conf.

Первое, что вы должны заметить при просмотре основного конфигурационного файла – это то, что он имеет древовидную структуру, определенную наборами фигурных скобок ({}). В языке Nginx области, которые определены этими скобками, называются контекстами, поскольку они разделяют детали конфигурации в зависимости от их предназначения. Такое разделение придает структуре некоторую условную логику и упорядоченность.

Поскольку контексты могут накладываться друг на друга, Nginx поддерживает наследование директив. Как правило, если директива действительна в нескольких вложенных областях, в более широком контексте она будет передана любому дочернему контексту в качестве значения по умолчанию. Дочерние контексты могут переопределять эти значения по своему усмотрению. Стоит отметить, что переопределение директив заменяет предыдущее значение, а не расширяет его.

Директивы можно использовать только в тех контекстах, для которых они были разработаны. Nginx не сможет читать конфигурационные файлы, в которых директивы объявлены в неправильном контексте. Документация Nginx содержит информацию о том, в каких контекстах действует каждая директива.

Далее рассмотрим подробнее основные контексты.

Основные контексты

Первая группа контекстов, которую мы обсудим, — это основные контексты, которые Nginx использует для создания иерархического дерева и разделения функций отдельных блоков конфигурации. Это контексты, которые составляют основную структуру конфигурации Nginx.

Контекст main

Основным контекстом является main. Этот глобальный контекст является единственным, который не содержится в типичных блоках контекста, которые выглядят следующим образом:

# контекст main находится здесь, вне любых других контекстов

. . .
context {
. . .
}

Любая директива, которая существует полностью за пределами других блоков, относится к глобальному контексту main. Имейте в виду, если ваша конфигурация Nginx настроена модульным способом, некоторые файлы будут содержать инструкции, которые существуют вне контекста, но они будут включены в контекст при объединении конфигурации.

Контекст main представляет собой самую широкую среду для конфигурации Nginx. Он используется для настройки параметров, которые влияют на все приложение на базовом уровне. Хотя директивы в этом разделе влияют на другие контексты, многие из них не наследуются, потому что их нельзя переопределить на более низких уровнях.

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

Контекст events

Контекст events находится внутри контекста main. Он определяет глобальные параметры, влияющие на то, как Nginx обрабатывает соединения. В конфигурации Nginx может быть только один контекст events.

Этот контекст будет выглядеть так:

# main context
events {
# events context
. . .
}

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

Как правило, метод обработки соединения выбирается автоматически – платформа выбирает наиболее эффективный из доступных методов. Для систем Linux обычно лучшим является метод epoll.

Другие элементы, которые можно настроить, — это максимальное количество подключений, которые может обрабатывать каждый рабочий процесс.

Контекст http

При настройке Nginx как веб-сервера или обратного прокси-сервера большую часть конфигурации будет содержать контекст http. Этот контекст хранит все директивы и другие контексты, необходимые для настройки обработки соединений HTTP или HTTPS.

Контексты http и events принадлежат одному уровню, потому они не вкладываются друг в друга, а определяются как одноранговые. Оба они являются дочерними по отношению к контексту main.

# main context
events {
# events context
. . .
}
http {
# main context
. . .
}

Контексты более низких уровней содержат более подробное описание того, как обрабатывать запросы, а директивы на этом уровне контролируют значения по умолчанию для каждого виртуального сервера, определенного внутри этого контекста. В этом контексте и ниже настраивается большое количество директив, в зависимости от того, какое вам нужно наследование.

Основные директивы, с которыми вы, вероятно, столкнетесь в этом контексте, настраивают местоположения по умолчанию для логов доступа и ошибок (access_log и error_log), асинхронный ввод-вывод для операций с файлами (aio, sendfile и directio) и состояния сервера при возникновении ошибок (error_page). Также здесь есть директивы для настройки сжатия (gzip и gzip_disable) и тонкой настройки TCP (keepalive_disable, keepalive_requests и keepalive_timeout), а также правила для оптимизации пакетов и системных вызовов (sendfile, tcp_nodelay и tcp_nopush). Дополнительные директивы настраивают корневой и индексный файл на уровне приложения (root и index) и хеш-таблицы, которые используются для хранения различных типов данных (*_hash_bucket_size и *_hash_max_size).

Контекст server

Контекст server обычно объявляется в контексте http. Это первый пример вложенного контекста в этом мануале. Это также первый контекст, который можно объявить несколько раз.

Общий формат контекста server может выглядеть примерно так (помните, что он находится в контексте http):

# main context
http {
# http context
server {
# first server context
}
server {
# second server context
}
}

Этот контекст можно объявлять много раз потому, что каждый отдельный контекст server отвечает за настройку отдельного виртуального хоста для обработки клиентских запросов. Вы можете создать любое количество блоков server, каждый из которых может обрабатывать определенное подмножество соединений.

Блоки server очень похожи друг на друга, потому Nginx должен использовать алгоритм выбора для определения нужного контекста. Каждый запрос клиента будет обрабатываться в соответствии с конфигурацией, определенной в одном из существующих блоков server, поэтому веб-сервер Nginx на основе данных самого запрос должен решить, какой контекст server больше всего подходит. Вот директивы, которые решают, будет ли блок server использоваться для обслуживания запроса:

  • listen: комбинация ip–адреса и порта, которую прослушивает этот блок server. Если запрос выполняется клиентом, который запрашивает эти значения, этот блок будет выбран для обработки соединения.
  • server_name: эта директива также используется для выбора блока server для обработки запроса. Если в файле есть несколько блоков server с одинаковыми директивами listen, Nginx будет анализировать заголовок Host запроса и сопоставлять его с этой директивой.

Директивы в этом контексте могут переопределять многие из директив контекста http, включая логирование, document root, сжатие и т.п. В дополнение к директивам контекста http, тут можно также настроить использование найденных файлов для обработки запроса (try_files), перенаправление и перезапись (return и rewrite) и установить произвольные переменные (set).

Контекст location

Следующий контекст, который постоянно используется, — это location. Контексты location имеют много общего с контекстами server. Например, вы можете определить несколько контекстов location, каждый из которых используется для обработки определенного типа клиентского запроса. Блок location выбирается в результате сопоставления местоположения с запросом клиента с помощью алгоритма выбора.

Если директивы, определяющие, следует ли выбирать блок server, определяются в контексте server, то компонент, который позволяет выбрать location, находится в самом определении location (в строке, которая открывает блок location).

Общий синтаксис этого блока выглядит так:

location match_modifier location_match {
. . .
}

Блоки location находятся в контексте server и, в отличие от блоков server, могут вкладываться друг в друга. Это может быть полезно для создания более общего контекста location и для улавливания определенного подмножества трафика, а также для дальнейшей обработки его на основе более конкретных критериев с дополнительными контекстами:

# main context
server {
# server context
location /match/criteria {
# first location context
}
location /other/criteria {
# second location context
location nested_match {
# first nested location
}
location other_nested {
# second nested location
}
}
}

Хотя контексты сервера выбираются на основе запрошенной комбинации из IP-адреса, порта и имени хоста в заголовке Host, блоки location дополнительно делят обработку запросов в блоке server, просматривая URI запроса. URI запроса – это часть запроса, которая идет после имени домена или комбинации IP-адреса/порта.

Таким образом, если клиент запрашивает http://www.example.com/blog по порту 80, то для определения  нужного блока server будут использоваться http, www.example.com и порт 80. После выбора блока будет оцениваться URI запроса – в данном примере это часть /blog – чтобы определить, какой дополнительный контекст должен использоваться для ответа на запрос.

Многие из директив, которые вы, вероятно, увидите в контексте location, также доступны на родительских уровнях. Директивы этого уровня позволяют определять места вне корневого каталога (alias), отмечать местоположение как внутренне доступное (internal), настраивать прокси-сервер для других серверов или местоположений (с помощью HTTP, fastcgi, scgi и uwsgi проксирования).

Другие контексты

Приведенные выше примеры демонстрируют основные контексты, с которыми вы столкнетесь с Nginx, но в конфигурациях существуют и другие контексты. Описанные ниже контексты зависят от дополнительных модулей, они используются только в определенных обстоятельствах, либо же для поддержки узкой функциональности, которую большинство людей не будет использовать.

  • split_clients: этот контекст делит клиентов сервера на категории путем их маркировки. Затем их можно использовать для тестирования А/B, предоставляя нескольким хостам разный контент.
  • perl / perl_set: эти контексты настраивают Perl-обработчики для местоположения, в котором они отображаются. Они используются только для обработки Perl.
  • map: этот контекст используется для установки значения переменной в зависимости от значения другой переменной. Он обеспечивает сопоставление значений переменных, чтобы определить, какое значение должна иметь вторая переменная.
  • geo: Как и предыдущий контекст, этот контекст используется для сопоставления. Однако это сопоставление специально используется для категоризации IP-адресов клиентов. Он устанавливает значение переменной в зависимости от IP-адреса подключения.
  • types: этот контекст используется для сопоставления типов MIME с расширениями файлов, которые должны быть связаны с ними. Обычно для этого создается отдельный файл, на который ссылается основной конфигурационный файл nginx.conf.
  • charset_map: этот контекст используется для сопоставления таблицы преобразования одного набора символов в другой. Оба набора перечислены в заголовке контекста, а в теле происходит их сопоставление.

Вышеперечисленные контексты не так популярны, но очень полезны в отдельных ситуациях.

Контекст upstream

Контекст upstream используется для определения и настройки серверов upstream. В основном, этот контекст определяет именованный пул серверов, в который Nginx может передавать проксированные запросы. Этот контекст, скорее всего, будет использоваться при настройке различных типов проксирования.

Контекст upstream находится в контексте http. Его общий синтаксис выглядит так:

# main context
http {
# http context
upstream upstream_name {
# upstream context
server proxy_server1;
server proxy_server2;
. . .
}
server {
# server context
}
}

Затем контекст upstream может ссылаться на имя в пределах блоков server или location для передачи запросов определенного типа в пул серверов. После этого контекст upstream использует алгоритм (по умолчанию round-robin), чтобы определить, какой конкретный сервер должен обработать запрос. Этот контекст дает серверу Nginx возможность выполнять балансировку нагрузки при проксировании запросов.

Контекст mail

Nginx чаще всего используется как веб-сервер или обратный прокси-сервер, однако он также может функционировать как высокопроизводительный почтовый прокси-сервер. Контекст, который используется для директив этого типа, называется, соответственно, mail. Контекст mail определяется в контексте main (вне контекста http).

Основная функция контекста mail – предоставить блок для настройки проксирования почты. Nginx имеет возможность перенаправлять запросы на аутентификацию на внешний сервер. Затем он может предоставлять доступ к почтовым серверам POP3 и IMAP для обслуживания фактических почтовых данных. Контекст mail также может поддерживать подключения к SMTP Relayhost, если это необходимо.

Общий синтаксис этого контекста имеет такой вид:

# main context
events {
# events context
}
mail {
# mail context
}

Контекст if

Контекст if настраивает условную обработку определенных в нем директив. Как и обычный оператор if в программировании, директива if в Nginx будет выполнять инструкции, если заданная операция возвращает true.

Контекст if в Nginx предоставляется модулем rewrite, и это основное назначение этого контекста. Поскольку обычно Nginx проверяет условия запроса с помощью других директив, контекст if не следует использовать в большинстве форм условного поведения. Это настолько важно, что сообщество Nginx создало страницу под названием if is evil.

Проблема в основном заключается в том, что порядок обработки Nginx может очень часто приводить к неожиданным результатам, которые, по-видимому, искажают значение блока if. Единственными директивами, которые считаются безопасными для использования внутри этого контекста, являются директивы return и rewrite (те, для которых и был создан этот контекст). Еще одна вещь, которую следует иметь в виду при использовании контекста if, заключается в том, что он отключает директиву try_files в том же контексте.

Чаще всего if используется для определения необходимости перезаписи или возврата. Эти директивы чаще всего находятся в блоках location, поэтому общая форма будет выглядеть примерно так:

# main context
http {
# http context
server {
# server context
location location_match {
# location context
if (test_condition) {
# if context
}
}
}
}

Контекст limit_except

Контекст limit_except используется для ограничения использования определенных HTTP-методов в контексте location. Например, если только определенные клиенты должны иметь доступ к содержимому POST, но иметь возможность читать контент должен каждый, вы можете использовать блок limit_except.

Вот пример подобной настройки:

. . .
# server or location context
location /restricted-write {
# location context
limit_except GET HEAD {
# limit_except context
allow 192.168.1.1/24;
deny all;
}
}

Это применит внутри контекста директивы, предназначенные для ограничения доступа, при получении HTTP-методов, не перечисленных в заголовке контекста. Благодаря этой настройке любой клиент может использовать GET и HEAD, но использовать другие методы могут только клиенты, входящие в подсеть 192.168.1.1/24.

Общие правила определения контекстов

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

Применяйте директивы в наиболее широком контексте

Многие директивы действительны в нескольких контекстах. Например, существует множество директив, которые могут находиться в контексте http, server или location. Это дает вам гибкость при настройке этих директив.

Однако обычно лучше объявлять директивы в контексте наиболее высокого уровня из доступных, и при необходимости переопределять их в более узких контекстах. Это возможно благодаря модели наследования, которую реализует Nginx. Существует много причин использовать эту стратегию.

Прежде всего, объявление директивы в широком контексте позволяет избежать ее избыточного повторения в контекстах того же уровня. В приведенном ниже примере каждый блок location объявляет один и тот же document root:

http {
server {
location / {
root /var/www/html;
. . .
}
location /another {
root /var/www/html;
. . .
}
}
}

Можно переместить это определение в контекст server или http:

http {
root /var/www/html;
server {
location / {
. . .
}
location /another {
. . .
}
}
}

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

Используйте несколько контекстов одного уровня вместо логических контекстов

Чтобы обрабатывать запросы по-разному в зависимости от наличия информации в запросе клиента, многие пользователи часто внедряют контекст if. Ранее вы уже рассмотрели несколько его недостатков.

Во-первых, директива if часто возвращает результаты, которые не соответствуют ожиданиям администратора. Хотя обработка одного и того же ввода всегда приведет к одному и тому же результату, способ интерпретации среды Nginx может значительно отличаться.

Во-вторых, для многих из этих целей есть специальные оптимизированные директивы. Nginx уже использует хорошо документированный алгоритм выбора для таких вещей, как выбор блоков server и location. Поэтому, если это возможно, лучше всего попытаться переместить различные конфигурации в отдельные блоки, чтобы этот алгоритм мог обработать логику выбора.

Например, вместо преобразования запроса пользователя в нужный формат путем перезаписи нужно попытаться настроить два блока для запросов, один из которых представляет желаемый метод, а другой обрабатывает перенаправления (возможно, переписывает их) и передает в правильный блок.

В результате файл проще читать и поддерживать.

Правильные запросы не подвергаются дополнительной обработке, и во многих случаях неправильные запросы можно обработать с помощью перенаправления, а не перезаписи. Это уменьшает расход ресурсов.

Заключение

Теперь вы знакомы с основными контекстами Nginx и умеете работать с ними.

Больше примеров и полезной информации о конфигурациях Nginx можно найти в документации веб-сервера.

Правильная конфигурация не только проста в поддержке, но и может оптимизировать производительность вашего приложения.

Tags: