Что такое сокет: TCP-, UDP- и unix-сокеты простыми словами, и почему сайт падает с «Too many open files»
Слово «сокет» встречается в ошибках nginx, конфиге PHP-FPM и выводе ss, и каждый раз означает чуть разное. Разбираем, что такое сокет для операционной системы, чем он отличается от порта и соединения, зачем nginx и PHP-FPM общаются через файл в /run и почему под нагрузкой сервер отдаёт 502 с записью «Too many open files» в логе.
Сокет — это конечная точка обмена данными между программами, которую операционная система выдаёт процессу как файловый дескриптор. Сетевой сокет задаётся тройкой «протокол + IP-адрес + порт» (например, TCP 203.0.113.10:443), unix-сокет — путём в файловой системе (например, /run/php/php8.3-fpm.sock). Соединение — пара сокетов на двух концах, порт — только номер внутри адреса. Поскольку каждый сокет занимает дескриптор, при их исчерпании сервер получает ошибку EMFILE — «Too many open files» — и перестаёт принимать подключения.
Чем сокет отличается от порта, соединения и файлового дескриптора
Порт — число от 0 до 65535 в заголовке TCP или UDP; сокет — объект ядра, к которому порт привязан вместе с адресом и протоколом; соединение — пара сокетов «клиент — сервер». Так определяет их спецификация TCP: RFC 9293 (действующая редакция, заменившая RFC 793) называет сокетом конкатенацию интернет-адреса и порта, а соединением — пару сокетов.
| Термин | Что это | Пример | Чем отличается |
|---|---|---|---|
| Порт | 16-битный номер в заголовке TCP/UDP | 443 | Адресная метка, сам ничего не «слушает» |
| Сокет | Объект ядра с адресом, портом, протоколом и буферами | TCP 203.0.113.10:443 | На одном порту — один слушающий сокет и тысячи принятых |
| Соединение | Пара сокетов: IP и порт с обеих сторон | 198.51.100.7:51234 ↔ 203.0.113.10:443 | Уникально четвёркой, поэтому на одном порту помещаются миллионы соединений |
| Unix-сокет | Сокет AF_UNIX для процессов одной машины | /run/php/php8.3-fpm.sock | Нет IP и порта, адрес — путь; доступ через права на файл |
| WebSocket | Протокол прикладного уровня поверх TCP (RFC 6455) | wss://example.com/chat | Не сокет ОС, а протокол внутри обычного TCP-сокета |
| Файловый дескриптор | Номер, которым процесс ссылается на открытый файл, сокет или пайп | fd 7 → socket:[184532] | Сокет — один из видов дескриптора; ulimit -n считает их вместе |
Подробнее о портах — в статье «Что такое порт».
Какие бывают сокеты: SOCK_STREAM, SOCK_DGRAM, unix и raw
Тип сокета задают два параметра вызова socket(): семейство адресов (AF_INET, AF_INET6, AF_UNIX) и тип передачи (SOCK_STREAM, SOCK_DGRAM, SOCK_RAW). По man 2 socket, вызов «создаёт конечную точку для обмена данными и возвращает файловый дескриптор, который на неё ссылается».
| Тип | Семейство + тип | Свойства | Где используется | Пример адреса |
|---|---|---|---|---|
| TCP-сокет | AF_INET / AF_INET6 + SOCK_STREAM | Поток байтов, соединение, гарантия доставки и порядка | HTTP, HTTPS, SSH, SMTP, MySQL по сети | 0.0.0.0:443, [::]:22 |
| UDP-сокет | AF_INET / AF_INET6 + SOCK_DGRAM | Отдельные датаграммы, без соединения и гарантий | DNS, NTP, QUIC/HTTP/3, VoIP, WireGuard | 0.0.0.0:53, 0.0.0.0:443 (QUIC) |
| Unix stream | AF_UNIX + SOCK_STREAM | Как TCP, но внутри одной машины, без сетевого стека | nginx ↔ PHP-FPM, MySQL, Redis, Docker | /run/php/php8.3-fpm.sock |
| Unix datagram | AF_UNIX + SOCK_DGRAM | Сообщения с границами, доставка надёжная (man 7 unix) | syslog, journald | /dev/log |
| Raw-сокет | AF_INET + SOCK_RAW, AF_PACKET | Прямой доступ к пакетам и кадрам, нужны привилегии | ping (ICMP), traceroute, tcpdump, nmap | Порта нет: протокол ICMP, интерфейс |
Проверка порта открывает TCP-сокет, а ping работает через raw-сокет и портов не касается. Разница между TCP и UDP — в статье «TCP и UDP: в чём разница».
Как живёт TCP-соединение: socket, bind, listen, accept и connect
Сервер делает четыре вызова — socket(), bind(), listen(), accept(); клиент два — socket() и connect(). Слушающий сокет данных не передаёт: на каждое соединение accept() возвращает новый сокет и новый дескриптор.
| Шаг | Кто | Что происходит | Состояние в ss | Типичная ошибка |
|---|---|---|---|---|
| socket() | Оба | Ядро создаёт объект и выдаёт дескриптор | UNCONN (не показывается без -a) | EMFILE — Too many open files |
| bind() | Сервер | Сокету назначаются адрес и порт (или путь для unix) | UNCONN с локальным адресом | EADDRINUSE — Address already in use |
| listen() | Сервер | Сокет становится слушающим, создаётся очередь backlog | LISTEN, Send-Q = размер backlog | Переполнение очереди: клиент ждёт или получает RST |
| connect() | Клиент | Ядро выбирает эфемерный порт, идёт рукопожатие | SYN-SENT → ESTAB | ECONNREFUSED, ETIMEDOUT, EADDRNOTAVAIL (порты кончились) |
| accept() | Сервер | Готовое соединение забирается из очереди, выдаётся новый дескриптор | ESTAB с адресом клиента | EMFILE — соединение висит в очереди |
| close() | Оба | Обмен FIN | CLOSE-WAIT, TIME-WAIT | Много CLOSE_WAIT — приложение не вызывает close() |
# Слушающие TCP-сокеты: Send-Q у LISTEN — размер backlog $ ss -ltn # пример вывода State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 511 0.0.0.0:80 0.0.0.0:* LISTEN 0 511 0.0.0.0:443 0.0.0.0:* LISTEN 0 511 127.0.0.1:6379 0.0.0.0:* # Слушающие unix-сокеты $ ss -lxn | grep php # пример вывода u_str LISTEN 0 511 /run/php/php8.3-fpm.sock 21873 * 0 # Сводка: сколько соединений в TIME-WAIT $ ss -s # пример вывода TCP: 3412 (estab 1189, closed 2106, orphaned 3, timewait 2094)
Recv-Q у LISTEN, подбирающийся к Send-Q, — переполнение очереди accept. Разбор состояний и ключей ss — в статье «netstat и ss: какие порты слушает сервер».
Unix-сокеты в веб-стеке: nginx, PHP-FPM, MySQL и Redis
Когда nginx и PHP-FPM работают на одной машине, они обычно общаются через unix-сокет — файл в /run, а не через TCP-порт 9000. Unix-сокет не проходит через сетевой стек: нет заголовков IP и TCP, TIME_WAIT и эфемерных портов, а доступ контролируется правами на файл. Документация PHP рекомендует unix-сокет для веб-сервера на том же хосте: открытый наружу TCP-порт FastCGI позволяет выполнить произвольный код.
# /etc/php/8.3/fpm/pool.d/www.conf — PHP-FPM слушает unix-сокет
listen = /run/php/php8.3-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
listen.backlog = 511
pm.max_children = 20
# /etc/nginx/sites-enabled/example.conf — nginx подключается к нему
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
По man 7 unix, для подключения к stream-сокету нужно право записи на файл сокета. Поэтому три самые частые ошибки в error.log nginx — про файл, а не про сеть:
# /var/log/nginx/error.log — примеры записей # 1. PHP-FPM не запущен или слушает другой путь [crit] 1832#1832: *44 connect() to unix:/run/php/php8.3-fpm.sock failed (2: No such file or directory) while connecting to upstream # 2. nginx работает не от пользователя из listen.owner [crit] 1832#1832: *45 connect() to unix:/run/php/php8.3-fpm.sock failed (13: Permission denied) while connecting to upstream # 3. Все воркеры заняты, очередь backlog переполнена [error] 1832#1832: *46 connect() to unix:/run/php/php8.3-fpm.sock failed (11: Resource temporarily unavailable) while connecting to upstream # В /var/log/php8.3-fpm.log в это же время: WARNING: [pool www] server reached pm.max_children setting (20), consider raising it
Ошибка 11 (EAGAIN) — самая коварная: сокет есть, права в порядке, но все воркеры заняты, очередь listen.backlog (по умолчанию 511 на Linux) полна, и ядро отказывает nginx мгновенно. Снаружи это 502. Лечится не переходом на TCP, а увеличением pm.max_children в пределах памяти и ускорением скриптов.
| Критерий | Unix-сокет (fastcgi_pass unix:) | TCP (fastcgi_pass 127.0.0.1:9000) |
|---|---|---|
| Накладные расходы | Нет сетевого стека и рукопожатия — ниже задержка подключения | Полный TCP даже через loopback; на фоне работы PHP разница мала |
| Контроль доступа | Права на файл: listen.owner, listen.group, listen.mode | listen.allowed_clients и файрвол |
| Эфемерные порты и TIME_WAIT | Не расходуются | Каждый запрос без keepalive занимает порт на 60 с в TIME_WAIT |
| Сеть и контейнеры | Одна машина; в Docker — общий volume с сокетом | Работает между хостами и контейнерами |
| Типичные ошибки | 2: No such file · 13: Permission denied · 11: Resource temporarily unavailable | 111: Connection refused · 99: Cannot assign requested address |
Почему сервер пишет «Too many open files» и как поднять лимит
«Too many open files» (errno 24, EMFILE) означает, что процесс упёрся в RLIMIT_NOFILE — лимит одновременно открытых дескрипторов, а каждое соединение это дескриптор. Soft-лимит по умолчанию — 1024: systemd с версии 240 поднимает hard-лимит до 524288, но soft оставляет 1024 ради совместимости с вызовом select(), который не работает с дескрипторами ≥ 1024. Для nginx 1024 дескрипторов — примерно 500 клиентов: на каждый проксируемый запрос уходит два сокета, к клиенту и к бэкенду.
# Как это выглядит в /var/log/nginx/error.log [alert] 28720#0: *59757 socket() failed (24: Too many open files) while connecting to upstream [alert] 28720#0: *59801 accept4() failed (24: Too many open files) [alert] 28720#0: 512 worker_connections are not enough # Лимит оболочки (soft / hard) и реальный лимит процесса $ ulimit -n; ulimit -Hn 1024 524288 $ cat /proc/$(pgrep -o nginx)/limits | grep 'open files' Max open files 1024 524288 files # пример вывода # Дескрипторов у воркера сейчас $ ls /proc/$(pgrep -n nginx)/fd | wc -l 1019
Поднимать лимит нужно на том слое, где он срабатывает. Правка /etc/security/limits.conf не влияет на сервисы systemd — они берут LimitNOFILE из юнита. У nginx есть своя директива worker_rlimit_nofile; worker_connections (по умолчанию 512) должен быть меньше неё.
# 1. systemd: лимит процесса
$ sudo systemctl edit nginx
[Service]
LimitNOFILE=65536
$ sudo systemctl daemon-reload && sudo systemctl restart nginx
# 2. nginx.conf: лимит воркера и число соединений
worker_rlimit_nofile 65536;
events {
worker_connections 16384;
}
# Проверка
$ cat /proc/$(pgrep -n nginx)/limits | grep 'open files'
Max open files 65536 65536 files # пример вывода
| Слой | Параметр | Значение по умолчанию | Как посмотреть | Ошибка при упоре |
|---|---|---|---|---|
| Процесс | RLIMIT_NOFILE: ulimit -n, LimitNOFILE | soft 1024, hard 524288 (systemd ≥ 240) | cat /proc/PID/limits | EMFILE, 24: Too many open files |
| Система | fs.file-max | Зависит от памяти, обычно очень велик | cat /proc/sys/fs/file-nr | ENFILE, 23: Too many open files in system |
| nginx | worker_rlimit_nofile, worker_connections | не задан; 512 | nginx -T | grep worker_ | «worker_connections are not enough», 502/504 |
| Очередь listen | somaxconn; backlog в listen nginx; listen.backlog PHP-FPM | 4096 с ядра 5.4 (ранее 128); 511; 511 | ss -ltn (Send-Q), nstat | grep ListenOverflows | Клиент ждёт или получает RST; 11: Resource temporarily unavailable |
| PHP-FPM | pm.max_children | 5 в шаблоне пула Debian | php-fpm.log, pm.status_path | «server reached pm.max_children setting», 502/504 |
Если число файлов в /proc/PID/fd растёт при ровной нагрузке, а в ss -tan копятся CLOSE-WAIT, приложение не закрывает сокеты — поднятый лимит лишь отодвинет падение на несколько часов.
Почему при большой нагрузке кончаются эфемерные порты
Каждое исходящее соединение занимает эфемерный порт из диапазона net.ipv4.ip_local_port_range, по умолчанию 32768–60999 — 28 232 порта на пару «адрес источника — адрес и порт назначения». Закрытое соединение ещё 60 секунд держит порт в TIME_WAIT (константа TCP_TIMEWAIT_LEN в ядре Linux, tcp_fin_timeout на неё не влияет), поэтому к одному бэкенду без keepalive можно открывать не больше ~470 соединений в секунду: 28 232 / 60. Дальше connect() возвращает EADDRNOTAVAIL — «Cannot assign requested address» (errno 99).
| Параметр | По умолчанию (Linux, сентябрь 2026) | Что делает | Что менять |
|---|---|---|---|
| net.ipv4.ip_local_port_range | 32768 60999 | Диапазон портов для исходящих TCP и UDP | Расширить до 1024 65535, если на этих портах нет сервисов |
| TIME_WAIT | 60 с, не настраивается | Закрывшая первой сторона держит четвёрку, чтобы опоздавшие сегменты не попали в новое соединение | Не бороться, а сокращать число коротких соединений: keepalive к upstream |
| net.ipv4.tcp_tw_reuse | 2 — только для loopback | Разрешает connect() занять порт в TIME_WAIT через 1 с при включённых TCP timestamps | 1 на клиентах и прокси с большим числом исходящих соединений |
| net.ipv4.tcp_tw_recycle | Удалён в ядре 4.12 | Ломал клиентов за NAT | Убрать из старых sysctl.conf |
# Текущие значения
$ sysctl net.ipv4.ip_local_port_range net.ipv4.tcp_tw_reuse
net.ipv4.ip_local_port_range = 32768 60999
net.ipv4.tcp_tw_reuse = 2
# К какому адресу назначения скопились TIME-WAIT
$ ss -tan state time-wait | awk 'NR>1 {print $4}' | sort \
| uniq -c | sort -rn | head -3
# пример вывода
18342 127.0.0.1:9000
2071 10.0.0.5:3306
514 10.0.0.7:6379
# Лечение в nginx: keepalive к бэкенду
upstream backend {
server 127.0.0.1:9000;
keepalive 32;
}
18 тысяч TIME-WAIT к 127.0.0.1:9000 — nginx открывает новое TCP-соединение к PHP-FPM на каждый запрос. Unix-сокет убирает проблему полностью, keepalive к upstream — почти полностью. На Windows динамический диапазон — 49152–65535 (netsh int ipv4 show dynamicport tcp), а исчерпание видно как WSAENOBUFS 10055 или WSAEADDRINUSE 10048 из Winsock — реализации того же API Berkeley sockets с приставкой WSA у кодов ошибок.
Как исчерпание сокетов выглядит снаружи и как его поймать
Снаружи ни одна из этих проблем не называется своим именем: посетитель видит 502 Bad Gateway, 504 Gateway Timeout или бесконечную загрузку, а при переполненном backlog — SYN без ответа. Разовый curl почти всегда проходит: лимиты срабатывают на пиках, через минуту TIME_WAIT рассасывается и всё «само чинится».
Такие сбои ловят регулярными проверками. HTTP-проверка запишет момент, когда сервер начал отдавать 502 и 504, а проверка TCP-порта отделит «порт не отвечает» (упал nginx, переполнен backlog) от «порт открыт, но приложение отдаёт ошибку» (PHP-FPM, лимит дескрипторов). Сопоставьте время инцидента с error.log — и станет видно, какой лимит сработал.
Пруфен проверяет сайт и порты с интервалом от 2 минут из 20 точек мониторинга — 17 в России, а также Минск, Астана и Испания. Как только проверка перестаёт проходить, приходит уведомление в Email, Telegram или Макс, и второе — когда сайт восстановился; если ресурс так и не поднялся, подключается голосовой звонок. Время обнаружения равно интервалу проверки. Бесплатный план включает 5 проверок — хватит на главную, API и порт базы данных.
Частые вопросы
Сокет и порт — одно и то же?
Нет. Порт — номер от 0 до 65535, часть адреса. Сокет — объект операционной системы с адресом, портом и протоколом, через который процесс читает и пишет данные. На порту 443 у сервера один слушающий сокет и по одному на каждое соединение; «занят ли порт» означает «есть ли на нём слушающий сокет».
Что быстрее для PHP-FPM — TCP или unix-сокет?
Unix-сокет: нет сетевого стека, рукопожатия и эфемерных портов. На одном запросе разница на фоне работы PHP невелика, но при тысячах запросов в секунду TCP через loopback упирается в TIME_WAIT, а unix — нет. TCP нужен, когда nginx и PHP-FPM живут на разных машинах или в контейнерах без общего volume.
Почему при большой нагрузке кончаются порты, если их 65 535?
Исходящим соединениям Linux по умолчанию выдаёт только 32768–60999, и каждое закрытое соединение держит порт 60 секунд в TIME_WAIT: к одному адресу без keepalive выходит около 470 новых соединений в секунду. Лечение — keepalive к бэкендам, unix-сокеты внутри машины, расширение ip_local_port_range и tcp_tw_reuse = 1 на прокси.
WebSocket — это сокет?
Нет, это протокол прикладного уровня (RFC 6455): начинается как HTTP-запрос с заголовком Upgrade и дальше использует то же TCP-соединение для двустороннего обмена. Для операционной системы это обычный TCP-сокет с теми же лимитами дескрипторов: 10 000 подключённых браузеров — 10 000 сокетов у процесса.