# теория · шаг 1 из 3
Что именно слушает nginx
Коротко
listen отвечает на один вопрос: по какому адресу и порту принимать
соединения. Всё, что стоит после адреса, - флаги.
- Адрес без порта означает 80. Порт без адреса означает «на всех адресах».
- IPv4 и IPv6 - разные строки. Одна
listen 80;открывает только IPv4, и по IPv6 сайт не откроется вовсе. - Флаг
sslотносится к СОКЕТУ, а не к блоку: поставил его одному блоку - весь порт стал зашифрованным. - Часть параметров (
backlog,reuseport,default_server) можно задать только одному блоку на сокет. Второму nginx скажетduplicate listen options. - Адрес отбирает кандидатов раньше имени, и более точный адрес побеждает - это разобрано в уроке про блок по умолчаниюРазбирается в разделе 3, глава «default_server и чужие запросы»: Кто ответит на неизвестное имя.
Если listen давно разбирается с одного взгляда - листай до «Теперь сам».
Сначала ответь сам
Сайт настроен, nginx -t доволен, с ноутбука всё открывается. Пользователь
пишет: «с мобильного интернета не грузится». Конфиг такой:
server {
listen 80;
server_name shop.local;
root /var/www/shop;
}
Инстинктивный ответ - «проблема у оператора». Настоящий: в конфиге не хватает одной строки, и клиент, у которого есть только IPv6, до сайта не достучится.
На что это похоже
listen - это указание, на каком телефоне отвечать.
У офиса может быть несколько линий: городская, внутренняя, отдельный номер для поставщиков. Секретарь снимает трубку только тех линий, которые ему назвали. Позвонившему на четвёртую линию никто не ответит - не потому, что он не тот, а потому, что эту трубку никто не берёт.
Аналогия держится и дальше. Городской и внутренний номер - два разных телефона, и «отвечаю на городской» не означает «отвечаю на внутренний». Ровно так же устроены IPv4 и IPv6: это два разных телефона, и назвать надо оба.
Механизм: сокет отбирает кандидатов раньше имени
Каждая строка listen заводит запись «такой-то адрес, такой-то порт». Когда
приходит соединение, nginx смотрит, на какой адрес и порт оно пришло, и
оставляет только те 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
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 "код=$?"
код=7
[::1] - IPv6-адрес этой же машины, аналог 127.0.0.1; квадратные скобки
отделяют адрес от порта. $? - код завершения прошлой команды, и у curl 7
значит «не удалось соединиться». Не 404, не таймаут: на том конце никто не
слушает. Клиенту, у которого есть только IPv6-адрес и нет трансляции в IPv4,
сайта просто нет.
Добавляем в тот же server-блок вторую строку:
listen 80;
listen [::]:80;
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-блок».
Половина параметров относится не к блоку, а к сокету, потому что сокет один на всех, кто его слушает. Отсюда два следствия, оба проверены.
Первое: задать такой параметр можно только одному блоку.
server { listen 80 backlog=511; server_name a; }
server { listen 80 backlog=1024; server_name b; }
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, второй без него - и конфиг принимается:
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 порт больше не отвечает:
<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 - её и забывают чаще всего. Часть
параметров относится к сокету, а не к блоку, поэтому задаются они один раз и
действуют на весь порт.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий