# теория · шаг 1 из 4
Весь синтаксис за один урок
Коротко
В языке конфига nginx две конструкции и ни одной больше.
- Простая директива:
имя аргументы;. Блочная:имя аргументы { ... }. - Аргументы читаются подряд, пока не встретится
;или{. Всё поведение языка - следствие этой одной строчки. - Кавычки нужны, если внутри аргумента есть пробел,
;,{или#. \внутри кавычек защищает кавычку, но не защищает$.- Пропущенная
;даёт три разных ошибки и одно предупреждение, и все указывают на строку НИЖЕ той, где ошибка. Предупреждение - худший случай: проверка проходит, а сайт сломан. - Отступы и табуляция ничего не значат, имена директив чувствительны к
регистру, комментарий начинается с
#в начале слова.
Если синтаксис давно не вызывает вопросов - листай до «Теперь сам».
Сначала ответь сам
Вот конфиг. В нём одна опечатка. Что скажет nginx -t?
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 идёт по тексту и складывает
слова в список аргументов текущей директивы. Список закрывается ровно двумя
знаками: ; заканчивает простую директиву, { открывает блочную.
Из этого следует всё остальное. Директиве, которая принимает любое число
аргументов (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 / {:
server {
listen 80;
server_name shop.local;
root /var/www/shop
location / {
}
}
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: [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: [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: [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.
Разбор: где наивное правило подводит
Наивное правило звучит так: «кавычки нужны, когда в значении есть пробел». Оно неполное. Вот значение без единого пробела:
server {
listen 80;
add_header Link </app.css>;rel=preload;
}
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:
server {
listen 80;
add_header Content-Security-Policy default-src 'self'; img-src *;
}
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 говорит браузеру, откуда странице разрешено грузить
скрипты и картинки. В значении есть и пробелы, и ;, так что кавычки нужны
вокруг всего значения:
add_header Content-Security-Policy "default-src 'self'; img-src *" always;
always - то самое третье слово: с ним заголовок уйдёт и с ответами об
ошибках. Зачем это нужно, разобрано
в уроке про ловушку add_headerРазбирается в разделе 2, глава «Наследование директив»: Ловушка add_header.
Второе, что ломает наивное правило: \ не защищает знак доллара. $ в
значении означает переменнуюРазбирается в разделе 2, глава «Переменные»: Откуда берутся $переменные - подстановку,
которую nginx делает на каждый запрос. Кавычки разбираются одним слоем кода, а
$ - другим и позже, поэтому обратный слэш до него не доживает.
add_header X-Price "cost \$5" always;
add_header X-Note "cost $5 rub" always;
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 значение не разбирается как шаблон:
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 доезжает целиком.
Комментарий начинается только там, где с # начинается слово.
Главное
Аргументы директивы читаются, пока не встретится ; или {, - и всё
остальное поведение языка следует отсюда. Номер строки в ошибке указывает на
место, где разбор остановился, а не где ты ошибся: смотри выше.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий