1. 1
  2. 2
  3. 3

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

Что именно слушает nginx

Коротко

listen отвечает на один вопрос: по какому адресу и порту принимать соединения. Всё, что стоит после адреса, - флаги.

Если listen давно разбирается с одного взгляда - листай до «Теперь сам».

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

Сайт настроен, nginx -t доволен, с ноутбука всё открывается. Пользователь пишет: «с мобильного интернета не грузится». Конфиг такой:

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

Инстинктивный ответ - «проблема у оператора». Настоящий: в конфиге не хватает одной строки, и клиент, у которого есть только IPv6, до сайта не достучится.

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

listen - это указание, на каком телефоне отвечать.

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

Аналогия держится и дальше. Городской и внутренний номер - два разных телефона, и «отвечаю на городской» не означает «отвечаю на внутренний». Ровно так же устроены IPv4 и IPv6: это два разных телефона, и назвать надо оба.

Механизм: сокет отбирает кандидатов раньше имени

Каждая строка listen заводит запись «такой-то адрес, такой-то порт». Когда приходит соединение, nginx смотрит, на какой адрес и порт оно пришло, и оставляет только те server-блоки, у которых есть подходящая запись. Имена сравниваются потом и только среди оставшихся.

соединение :80 отбор по адресу и порту - listen отбор по имени server_name Кандидаты после первого шага: listen 80 подходит listen 0.0.0.0:80 то же самое listen 8080 выбыл listen [::]:80 другой адрес Выбывший блок не участвует в сравнении имён вообще - каким бы подходящим ни было его server_name.
Адрес и порт отбирают кандидатов, имя выбирает среди оставшихся. Обратного порядка не бывает.

Формы записи, которые встречаются каждый день:

http server
listen 80;                     # порт 80 на всех адресах
listen 127.0.0.1:8080;         # только на локальном адресе
listen 443 ssl;                # порт 443, соединение зашифровано
listen [::]:80;                # то же, что первая строка, но IPv6
listen 80 default_server;      # этот блок отвечает за «всё остальное»

Это пять вариантов по отдельности, а не блок для копирования: listen 443 ssl без сертификата конфиг не пропустит, а listen 80 и listen 80 default_server в одном блоке дают a duplicate listen 0.0.0.0:80 - проверено. ssl здесь - это HTTPS, шифрование соединения (разбор в разделе про TLSРазбирается в разделе 9, глава «Сертификат, цепочка и ключ»: Рукопожатие: что происходит до первого запроса), а default_server помечает блок, который ответит на запрос с незнакомым именем (следующий урок разделаРазбирается в разделе 3, глава «default_server и чужие запросы»: Кто ответит на неизвестное имя).

Флаги пишутся через пробел в любом порядке: reuseport default_server и default_server reuseport проходят оба. У одних флагов есть значение через = (backlog=511 - длина очереди соединений, ждущих воркера), другие - просто слово (reuseport - отдельный сокет каждому воркеру).

listen 8000; и listen *:8000; - одно и то же, и это же 0.0.0.0:8000 на схеме: все IPv4-адреса машины, и локальный 127.0.0.1, и сетевые. listen 127.0.0.1; без порта означает 80-й.

Правило

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

Разбор: одна строка против двух

Вернёмся к вопросу из начала урока. Смотрим, какие сокетыСокетТо, что операционная система заводит, когда программа начинает слушать адрес с портом или подключается куда-то. Два разных listen на один и тот же адрес с портом дают один сокет на двоих - отсюда общие для них настройки. открылись при одной строке listen 80;. Проверено на nginx 1.31.5.

$  команда
netstat -ltn
↳  выводnetstat -ltn
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN

netstat -ltn печатает сокеты: -l - только слушающие, -t - только TCP, -n - адреса цифрами. В официальном образе он есть, на сервере его заменяет ss -ltn с теми же флагами. Главная колонка - Local Address: на каком адресе и порту ждут соединений.

Один сокет, только IPv4. Запрос по IPv6 никуда не приходит:

$  команда
curl -s -H 'Host: shop.local' 'http://[::1]/'; echo "код=$?"
↳  выводcurl -s -H 'Host: shop.local' 'http://[::1]/'; echo "код=$?"
код=7

[::1] - IPv6-адрес этой же машины, аналог 127.0.0.1; квадратные скобки отделяют адрес от порта. $? - код завершения прошлой команды, и у curl 7 значит «не удалось соединиться». Не 404, не таймаут: на том конце никто не слушает. Клиенту, у которого есть только IPv6-адрес и нет трансляции в IPv4, сайта просто нет.

Добавляем в тот же server-блок вторую строку:

/etc/nginx/conf.d/shop.confhttp server
listen 80;
listen [::]:80;
↳  выводnetstat -ltn
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN
tcp        0      0 :::80                   :::*                    LISTEN

:::80 - это [::]:80, так netstat пишет «все IPv6-адреса». Теперь сокета два, и запрос по IPv6 доходит: тот же curl печатает магазин и код=0. Эти две строки всегда пишут парой, и вторую забывают чаще всего остального в конфиге.

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

Наивное правило: «флаг в listen настраивает свой server-блок».

Половина параметров относится не к блоку, а к сокету, потому что сокет один на всех, кто его слушает. Отсюда два следствия, оба проверены.

Первое: задать такой параметр можно только одному блоку.

/etc/nginx/conf.d/shop.confhttp
server { listen 80 backlog=511;  server_name a; }
server { listen 80 backlog=1024; server_name b; }
↳  выводnginx -t
nginx: [emerg] duplicate listen options for 0.0.0.0:80 in /etc/nginx/conf.d/shop.conf:2
nginx: configuration file /etc/nginx/nginx.conf test failed

Второе, более коварное: параметр действует на весь порт, включая чужие блоки. Один блок с ssl, второй без него - и конфиг принимается:

/etc/nginx/conf.d/shop.confhttp
server { listen 8443 ssl; server_name a.local; ssl_certificate     /etc/ssl/c.pem;
                                               ssl_certificate_key /etc/ssl/k.pem; }
server { listen 8443;     server_name b.local; }

Откуда берутся файлы сертификата и ключа - в разделе про TLSРазбирается в разделе 9, глава «Сертификат, цепочка и ключ»: Что лежит в двух файлах, для опыта годится самодельная пара.

А по HTTP порт больше не отвечает:

↳  выводcurl -s http://127.0.0.1:8443/
<html>
<head><title>400 The plain HTTP request was sent to HTTPS port</title></head>
<body>
<center><h1>400 Bad Request</h1></center>
<center>The plain HTTP request was sent to HTTPS port</center>
<hr><center>nginx/1.31.5</center>
</body>
</html>

Блок b.local не просил шифрования и его не настраивал - он просто оказался на том же порту.

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

  • «У части пользователей сайт не открывается». Проверь, есть ли строка listen [::]:80[::]:443). Быстрая проверка снаружи - curl -6 http://твой-домен/ с машины, где IPv6 есть: -6 велит ходить только по IPv6.
  • duplicate listen options. Два блока задают одному сокету разные параметры. Оставь параметр у одного - действовать он будет на всех.
  • 400 The plain HTTP request was sent to HTTPS port. На порт с флагом ssl пришли по HTTP. Либо клиент ошибся схемой, либо ssl попал на порт, который должен был остаться открытым.
  • Блок настроен верно, но не отвечает. Сравни порт в listen с портом, на который реально идёт запрос: nginx -T | grep -n listen показывает все строки разом.

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

Перед nginx почти всегда стоит что-то ещё: балансировщик (распределяет запросы между серверами), докер, файрвол (пропускает или режет соединения по правилам). И первый вопрос при разборе «сайт не открывается» - на какой адрес и порт запрос доходит физически.

$  команда
ss -ltnp | grep nginx

Флаг p добавляет имя процесса, по нему grep nginx и находит строки. Команда отвечает на вопрос за секунду и разводит две совершенно разные ситуации. Если нужного сокета в списке нет - дело в конфиге, и читать надо listen. Если сокет есть, а снаружи не отвечает - дело не в nginx: файрвол, проброс портов, группа безопасности. Половина времени на таких разборах уходит именно на то, чтобы перепутать эти два случая.

Теперь сам

1. В контейнере nginx слушает listen 127.0.0.1:8000;, порт проброшен наружу как 8080:8000. Снаружи сайт не открывается. Почему?

127.0.0.1 внутри контейнера - это его собственный локальный адрес, а соединение из проброса приходит на адрес контейнера в его сети, вроде 172.17.0.2. Нужен listen 8000; без адреса. Классическая ошибка переезда конфига с ноутбука в контейнер.

2. Зачем вообще писать listen 127.0.0.1:8080;, если можно слушать везде?

Чтобы порт не был доступен снаружи вовсе. Так поднимают служебные адреса: stub_status (страница со счётчиками соединений nginx), отладочный бэкенд, панель метрик. Это надёжнее любого правила файрвола, потому что запретом тут занимается сам сокет.

3. В чужом конфиге стоит listen 443 ssl http2;. Что с ним не так?

Форма устарела: с nginx 1.25.1 HTTP/2 - новая версия протокола, у неё свой раздел - включается отдельной директивой http2 on;. Конфиг работать будет, но при проверке скажет the "listen ... http2" directive is deprecated, use the "http2" directive instead. И дело не в косметике: параметр относится к сокету, поэтому включает HTTP/2 всем блокам порта - проверено на стенде, и даже http2 off; у соседнего блока этого не отменяет. Заодно это метка возраста: строку писали до 2023 года или копировали со старого руководства.

Главное

listen задаёт адрес и порт, и они отбирают кандидатов раньше имени. IPv6 требует отдельной строки listen [::]:80 - её и забывают чаще всего. Часть параметров относится к сокету, а не к блоку, поэтому задаются они один раз и действуют на весь порт.

Комментарии

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

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

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