1. 1
  2. 2
  3. 3
  4. 4

# теория · шаг 1 из 4

Весь синтаксис за один урок

Коротко

В языке конфига nginx две конструкции и ни одной больше.

  • Простая директива: имя аргументы;. Блочная: имя аргументы { ... }.
  • Аргументы читаются подряд, пока не встретится ; или {. Всё поведение языка - следствие этой одной строчки.
  • Кавычки нужны, если внутри аргумента есть пробел, ;, { или #.
  • \ внутри кавычек защищает кавычку, но не защищает $.
  • Пропущенная ; даёт три разных ошибки и одно предупреждение, и все указывают на строку НИЖЕ той, где ошибка. Предупреждение - худший случай: проверка проходит, а сайт сломан.
  • Отступы и табуляция ничего не значат, имена директив чувствительны к регистру, комментарий начинается с # в начале слова.

Если синтаксис давно не вызывает вопросов - листай до «Теперь сам».

Сначала ответь сам

Вот конфиг. В нём одна опечатка. Что скажет nginx -t?

/etc/nginx/conf.d/shop.confhttp
server {
    listen 80;
    server_name shop.local
    root /var/www/shop;
}

Инстинктивный ответ - «ошибка в строке 3, пропущена точка с запятой».

На самом деле nginx -t скажет, что конфиг в порядке. Сайт при этом сломан: у него нет корняРазбирается в разделе 5, глава «root против alias»: Как URI превращается в путь - каталога, из которого nginx берёт файлы сайта, - и он откликается на имя /var/www/shop. Почему так - станет очевидно через два абзаца, и после этого ты будешь узнавать этот класс поломок за секунду.

На что это похоже

Конфиг nginx - не программа, а список поручений, записанный на бумаге.

Представь записку сменщику: «полить цветы; проверить почту; закрыть окно». Разделитель тут - точка с запятой, и она единственное, что отличает три поручения от одного длинного. Убери её после «полить цветы» - и сменщик прочитает «полить цветы проверить почту», то есть одно поручение с непонятным уточнением. Он не увидит ошибки: с его точки зрения ты просто странно выразился.

Nginx читает ровно так же. У него нет представления о том, какие слова бывают именами директив, а какие - аргументами: он собирает слова в кучку, пока не упрётся в разделитель.

Механизм: где кончается директива

Разбор конфига устроен предельно просто. Nginx идёт по тексту и складывает слова в список аргументов текущей директивы. Список закрывается ровно двумя знаками: ; заканчивает простую директиву, { открывает блочную.

add_header X-Frame-Options DENY always ; имя аргументы - просто слова через пробел конец server_name shop.local root /var/www/shop ; имя четыре аргумента: разделителя между ними не было Пропала одна точка с запятой - и две директивы стали одной. Проверка синтаксиса при этом проходит.
Nginx не знает, какие слова бывают именами директив: он собирает аргументы до ближайшего разделителя. У server_name имён может быть сколько угодно, поэтому лишние слова его не смущают.

Из этого следует всё остальное. Директиве, которая принимает любое число аргументов (server_name, index, gzip_typesРазбирается в разделе 11, глава «gzip и brotli»: Что сжимать и чем - список типов для сжатия), лишние слова не мешают - она их молча съест. Директива с фиксированным числом аргументов (gzip on;) пожалуется, но пожалуется на себя, показав номер строки, где разбор остановился, - то есть следующей.

Правило

Аргументы директивы читаются подряд, пока не встретится ; или {. Поэтому номер строки в сообщении об ошибке - это строка, где nginx понял, что что-то не так, а не строка, где ты ошибся. Ошибка почти всегда выше.

Разбор: четыре исхода одной опечатки

Одна и та же забытая точка с запятой даёт четыре разных исхода. Каждый конфиг ниже целиком лежал в /etc/nginx/conf.d/shop.conf на nginx 1.31.5, номера строк настоящие - считай их по блоку.

Следом идёт блок. root /var/www/shop без ;, ниже location / {:

/etc/nginx/conf.d/shop.confhttp
server {
    listen 80;
    server_name shop.local;
    root /var/www/shop
    location / {
    }
}
↳  выводnginx -t
nginx: [emerg] directive "root" is not terminated by ";" in /etc/nginx/conf.d/shop.conf:5
nginx: configuration file /etc/nginx/nginx.conf test failed

Самое понятное сообщение из четырёх: ошибка в четвёртой строке, а указано на пятую, с location.

Директива была последней в блоке. Та же строка root без ;, а ниже только закрывающая скобка }:

↳  выводnginx -t
nginx: [emerg] unexpected "}" in /etc/nginx/conf.d/shop.conf:5
nginx: configuration file /etc/nginx/nginx.conf test failed

Скобка вполне ожидаемая - неожиданной её делает незакрытая root строкой выше.

Следом идёт директива со строгим числом аргументов. На месте root стоит gzip on без ;, ниже gzip_min_length 1024;:

↳  выводnginx -t
nginx: [emerg] invalid number of arguments in "gzip" directive in /etc/nginx/conf.d/shop.conf:5
nginx: configuration file /etc/nginx/nginx.conf test failed

Ругается на gzip из четвёртой строки, а показывает пятую: разбор дошёл до ; только там.

И четвёртый исход - предупреждение вместо ошибки. Это тот конфиг, с которого начался урок. server_name принимает сколько угодно имён, поэтому shop.local, root и /var/www/shop стали тремя именами сайта. Всё, что сказал nginx:

↳  выводnginx -t
nginx: [warn] server name "/var/www/shop" has suspicious symbols in /etc/nginx/conf.d/shop.conf:4
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok

Предупреждение - не ошибка, выкладка пройдёт. Сайт будет отдавать 404 на всё подряд, потому что корня у него нет.

Правила записи: что nginx прощает, а что нет

Каждый пункт проверен на nginx 1.31.5.

  • Отступы и табуляция не значат ничего. Пробел, табуляция и перевод строки для nginx одно и то же - граница между словами. Отступы пишут для людей.
  • Можно писать в одну строку и переносить. listen 80; server_name shop.local; - две директивы на одной строке. Аргументы можно разнести по нескольким строкам: директиву всё равно закончит ;.
  • Имена директив чувствительны к регистру. Listen 80; падает с unknown directive "Listen". Значения on и off - нет: gzip ON; проходит.
  • Комментарий - от # до конца строки, в том числе после директивы. Но только если с # начинается слово.
  • Размеры пишут с суффиксами k и m, изредка g, без суффикса это байты. Время - ms, s, m, h, d, без суффикса секунды. Единицы складываются, только если написаны слитно и от старшей к младшей: 1h30m - полтора часа, 30m1h - ошибка invalid value.

Разбор: где наивное правило подводит

Наивное правило звучит так: «кавычки нужны, когда в значении есть пробел». Оно неполное. Вот значение без единого пробела:

/etc/nginx/conf.d/shop.confhttp
server {
    listen 80;
    add_header Link </app.css>;rel=preload;
}
↳  выводnginx -t
nginx: [emerg] unknown directive "rel=preload" in /etc/nginx/conf.d/shop.conf:3
nginx: configuration file /etc/nginx/nginx.conf test failed

Директива закончилась на первой ; внутри значения, а rel=preload nginx прочитал как имя следующей директивы. Кавычки здесь нужны из-за разделителя: add_header Link "</app.css>;rel=preload";.

Пробел тоже ломает, но по другой причине - по счёту аргументов. add_header принимает имя заголовка, значение и необязательное третье слово always:

/etc/nginx/conf.d/shop.confhttp
server {
    listen 80;
    add_header Content-Security-Policy default-src 'self'; img-src *;
}
↳  выводnginx -t
nginx: [emerg] invalid parameter "self" in /etc/nginx/conf.d/shop.conf:3
nginx: configuration file /etc/nginx/nginx.conf test failed

Значением стало default-src, а третьим словом - self вместо always. Одинарные кавычки nginx понимает так же, как двойные, и снимает их при разборе, поэтому в сообщении self напечатан без них. Сам заголовок Content-Security-Policy говорит браузеру, откуда странице разрешено грузить скрипты и картинки. В значении есть и пробелы, и ;, так что кавычки нужны вокруг всего значения:

/etc/nginx/conf.d/shop.confhttp server
add_header Content-Security-Policy "default-src 'self'; img-src *" always;

always - то самое третье слово: с ним заголовок уйдёт и с ответами об ошибках. Зачем это нужно, разобрано в уроке про ловушку add_headerРазбирается в разделе 2, глава «Наследование директив»: Ловушка add_header.

Второе, что ломает наивное правило: \ не защищает знак доллара. $ в значении означает переменнуюРазбирается в разделе 2, глава «Переменные»: Откуда берутся $переменные - подстановку, которую nginx делает на каждый запрос. Кавычки разбираются одним слоем кода, а $ - другим и позже, поэтому обратный слэш до него не доживает.

/etc/nginx/conf.d/shop.confhttp server
add_header X-Price "cost \$5" always;
add_header X-Note  "cost $5 rub" always;
$  команда
curl -sI localhost/ | grep '^X-'
↳  выводcurl -sI localhost/ | grep '^X-'
X-Price: cost \
X-Note: cost  rub

curl -sI просит у сервера одни заголовки (-I, было в уроке установки), а -s убирает строку с ходом загрузки. Вертикальная черта | передаёт вывод одной программы на вход другой, и grep '^X-' оставляет из него только строки, которые начинаются с X-: остальные заголовки здесь не нужны.

В первом случае слэш остался в значении как есть, а $5 исчез. Во втором исчез тоже. Причина одна: $1-$9 - это ссылки на скобочные группы последней регуляркиРазбирается в разделе 4, глава «Как выбирается location»: Пять форм записи location. Регулярка - шаблон для поиска в тексте; кусок шаблона в скобках запоминается, и $1 - первый такой кусок. Вне регулярки они пустые.

Своя переменная со знаком доллара заводится через geo. Эта директива строит таблицу «адрес клиента → значение», default - значение для всех адресов, так что таблица из одной строки хранит просто знак доллара. Трюк работает, потому что внутри geo значение не разбирается как шаблон:

/etc/nginx/conf.d/shop.confhttp
geo $dollar { default "$"; }

После этого add_header X-Dollar "cost ${dollar}5" always; даёт X-Dollar: cost $5. Фигурные скобки отделяют имя переменной от следующих знаков: без них nginx искал бы переменную $dollar5.

Что ломается без этого

Три симптома, по которым узнаётся ошибка в синтаксисе, а не в логике:

  • nginx -t показывает строку, на которой всё выглядит правильно. Смотри строкой выше. Всегда.
  • Конфиг прошёл проверку, а сайт отдаёт 404 на всё. Ищи директиву, которая принимает список: server_name, index, gzip_types. Скорее всего, она съела соседнюю строку. Быстрая проверка - nginx -T | grep server_name (grep оставит из собранного конфига строки со словом server_name): лишнее слово там видно сразу.
  • Заголовок ушёл клиенту обрезанным или пустым. В значении есть $ или ;, а кавычек нет.

Зачем это в работе

Конфиги всё чаще пишет не человек, а шаблонизатор: Ansible, Helm, панель хостинга. Шаблон подставляет значение в строку, значение приезжает из переменной окружения, и однажды в нём оказывается точка с запятой или доллар.

Ты этого не увидишь в шаблоне - там всё аккуратно. Ты увидишь либо nginx -t с номером чужой строки, либо, что хуже, зелёную выкладку и сломанный сайт. Поэтому в шаблонах значения оборачивают в кавычки не «на всякий случай», а потому что подставляемый текст по определению неизвестен.

Теперь сам

Три вопроса. Ответы под каждым.

1. client_max_body_size 2g; работает, а client_body_buffer_size 2g; падает с invalid value. Почему?

Гигабайты понимает не всякая директива. У nginx два разбора размеров: для буферов в памяти - только k и m, для величин, которые могут относиться к файлу на диске, вроде предельного размера тела запросаТело сообщенияВсё, что идёт после пустой строки: содержимое файла, JSON, загружаемая картинка. У обычного GET его нет, а у ответов 204 и 304 не бывает по определению., - ещё и g. Замер: client_body_buffer_size 2m; проходит, 2g падает с invalid value. Правило для памяти: у буферов максимум - мегабайты.

2. Что сделает keepalive_timeout 1h 30m;?

Не полтора часа. Через пробел это два аргумента: первый - сколько держать простаивающее соединение (час), второй - что пообещать клиенту в заголовке ответа. Замер: Keep-Alive: timeout=1800, то есть тридцать минут. Полтора часа получатся только слитно: keepalive_timeout 1h30m;. И подвох в букве: m - минуты во времени и мегабайты в размере, различает их только директива.

3. Почему add_header X-Note "a#b"; не превращается в комментарий?

Внутри кавычек # - обычный символ, и заголовок уедет клиенту целиком: X-Note: a#b. Замер показал больше: даже без кавычек a#b доезжает целиком. Комментарий начинается только там, где с # начинается слово.

Главное

Аргументы директивы читаются, пока не встретится ; или {, - и всё остальное поведение языка следует отсюда. Номер строки в ошибке указывает на место, где разбор остановился, а не где ты ошибся: смотри выше.

Комментарии

Пока нет комментариев. Будь первым!

Оставить комментарий

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