«Connection reset by peer» — це мережевий еквівалент знизування плечима. Хтось десь різко зачинив двері посеред розмови. Ваш додаток повідомляє про помилку. Бізнес повідомляє про відмову. І три команди миттєво починають енергійну гру «не я».
Цей кейс покликаний припинити цю гру. На Ubuntu 24.04 ви зазвичай можете довести, хто відправив reset (клієнт, проксі/балансувальник або сервер) за допомогою кількох захоплень пакетів, лічильників і логів — якщо зібрати правильні докази в правильному порядку.
Що насправді означає «connection reset by peer» (і чого це не означає)
На рівні дроту «reset» — це TCP, який каже: «Стоп. Ми це не продовжуємо.» Це сегмент RST. Це не таймаут, не втрата пакетів (хоча втрата може призвести до нього) і не обов’язково «сервер впав». Це явне розірвання, що миттєво припиняє з’єднання.
Додатки бачать це як:
- ECONNRESET (поширено в Node.js, Go, Python, Java)
- recv() failed (104: Connection reset by peer) (часто в логах Nginx)
- Connection reset by peer від curl/OpenSSL, коли peer перериває під час HTTP/TLS
Важлива нюанс: «peer» означає «інша сторона з точки зору додатка». Якщо ваш клієнт говорить із зворотним проксі, «peer» — це проксі. Якщо проксі говорить з апстримом, «peer» — це апстрим. Якщо фаєрвол вставив reset, «peer» фактично — «той, хто сфальшував reset», і тому ми робимо захоплення пакетів, щоб ідентифікувати відправника.
TCP reset з’являються з кількох легітимних причин:
- Процес закрив сокет раптово (або ОС зробила це за процесом).
- Інша сторона отримала дані для з’єднання, яке вона не впізнає (немає стану), тому вона відправляє RST.
- Посередник (проксі, фаєрвол, NAT) вирішив, що ваш потік небажаний, і відправив RST.
- Таймаут або виснаження ресурсів змушує компонент видалити стан; наступні пакети викликають RST.
Практичний висновок: найшвидший шлях до істини — знайти пристрій, який відправив RST. Це не філософія. Це tcpdump.
Швидкий план діагностики (перший/другий/третій)
Коли ви на виклику, вам не потрібний семінар з мереж. Потрібна послідовність дій, яка за кілька хвилин звузить область проблеми.
Перший: класифікуйте reset (в напрямку клієнта чи апстриму)
- Якщо користувачі звертаються через проксі/балансувальник, перевірте, чи логи проксі показують клієнтський reset або апстримний reset.
- Якщо сервер додатку безпосередньо відкритий, перевірте логи сервера й стани сокетів на предмет скидань.
Другий: захопіть RST на найближчому контрольованому хопі
- Захопіть на інтерфейсі проксі/балансувальника, якщо він є; він стоїть між світами.
- Якщо немає проксі, захопіть на NIC сервера.
- Якщо ви контролюєте клієнта (синтетичний монітор або бастіон), захопіть там теж.
Третій: корелюйте за часом, 5-тюплом і напрямком
- Зіставте по 5-тюплу (src IP, src port, dst IP, dst port, protocol).
- Визначте, хто відправляє RST, дивлячись на source IP/MAC пакета й точку захоплення.
- Підтвердіть причину компонента логами/лічильниками (логи додатка, логи проксі, kernel counters, conntrack, TLS-помилки).
Якщо ви запам’ятали лише одну річ: не сперечайтеся про відповідальність, поки не зможете вказати на RST-фрейм і сказати «ця коробка його відправила».
Факти та контекст, які змінюють підхід до налагодження
Це не дрібниці. Кожен з них змінює, яким доказам ви довіряєте і які гіпотези пріоритезуєте.
- TCP RST існував довше за більшість ваших інструментів. Це частина базової поведінки TCP з ранніх RFC; ваш модний сервіс-меш — лише новіший актор, що надсилає старі пакети.
- Linux відправлятиме RST, якщо немає слухаючого сокета. Якщо нічого не прив’язано до порту призначення, ядро відповість RST на SYN, що клієнти часто інтерпретують як «reset».
- RST не завжди «сервер». Фаєрволи, балансувальники, NAT-шлюзи та IDS/IPS можуть генерувати скидання. Посередники роблять це, щоб відмовитися безпечно — або творчо.
- Невідповідність keep-alive — класичний генератор reset. Одна сторона повторно використовує простій підключення після того, як інша сторона вже витратила стан. Перший новий запит отримує reset.
- Закінчення стану NAT породжує «примарні reset-и». Якщо NAT забуде відображення, а кінцеві точки продовжують говорити, наступний пакет може викликати reset або бути відкинутим — у будь-якому разі виглядає як випадкова ненадійність.
- Проблеми з Path MTU можуть маскуватися під reset-и. Технічно це частіше «зависання», ніж reset-и, але деякі стекі й посередники поводяться погано й припиняють з’єднання під час TLS або HTTP/2.
- Ubuntu 24.04 постачається з nftables як інструментарієм за замовчуванням. iptables може ще існувати як сумісність, але потрібно знати, чи правила в nft, а не в пам’яті.
- QUIC/HTTP/3 перемістили багато режимів відмов на UDP. Ваш браузер може «вилікувати» зламаний TCP-шлях переключенням протоколу, роблячи reset-и виглядати періодичними й залежними від user-agent.
- Набір засобів спостереження змінив соціальну динаміку. Десять років тому команди сперечалися за відчуттям. Сьогодні ви можете — і повинні — сперечатися на основі захоплень пакетів та структурованих логів.
Одна перефразована ідея, бо вона досі найбільш корисна в операціях: перефразована ідея
— «Надія — це не стратегія.» (приписують в опскультурі Едсгеру В. Дейкстрі)
Доведіть, хто зробив це: клієнт проти проксі проти сервера
Думайте шарами опіки. Reset виникає десь, проходить кілька хопів, а потім стає «peer reset» для того додатка, який це отримує. Ваше завдання — ідентифікувати ініціатора, а не жертву.
Випадок A: reset надіслав клієнт (таке трапляється частіше, ніж зізнаються)
Reset-и, ініційовані клієнтом, зазвичай виглядають так:
- Браузер перейшов на іншу сторінку або закрив вкладку посеред запиту.
- Мобільний додаток перемістився у фон; ОС закрила сокети, щоб заощадити енергію.
- Таймаут на клієнті коротший за таймаут сервера/проксі; клієнт припиняє раніше.
- Бібліотека повторів, яка агресивно скасовує поточний запит.
Як довести:
- На сервері або в захопленні проксі ви бачите RST з IP клієнта.
- Логи сервера показують «клієнт передчасно закрив з’єднання».
- Проксі показує 499 (Nginx) або стан завершення, що відповідає клієнтському аборту.
Випадок B: reset надіслав проксі/балансувальник
Проксі скидають з’єднання з причин, які в дизайн-документі звучать розумно, а в проді боляче:
- Вичерпався час бездіяльності; проксі звільняє ресурси.
- Досягнуто макс. кількість запитів на з’єднання; проксі закриває без гарного завершення.
- Логіка health-check або circuit-breaker вважає апстрим поганим і fail-fast.
- Ліміти буфера, обмеження заголовків або політики тіла запиту викликають abort.
- Пристрої TLS inspection вважають ваш сертифікат «дивним» і переривають.
Як довести:
- У захопленні на боці клієнта джерело RST — VIP проксі/узел LB або його node IP.
- У захопленні проксі не видно upstream RST — лише проксі генерує клієнтський RST.
- Логи проксі показують локальну помилку (таймаут, перевищення ліміту, апстрим недоступний), а не апстримний abort.
Випадок C: reset надіслав сервер
Сервери скидають з’єднання з трьох великих категорій причин:
- Поведінка додатка: процеси падають, панікують, виходять, закривають сокети раптово або відмовляють у з’єднаннях через логіку перевантаження.
- Поведінка ядра: немає слухача, переповнення SYN backlog, тиск пам’яті або правила фаєрволу, що відхиляють з tcp-reset.
- Поведінка інфраструктури: вичерпання conntrack, вичерпання ефермерних портів на сервері, що виступає як клієнт, або зміни маршрутизації.
Як довести:
- Пакетний захват біля сервера показує RST з IP сервера.
- Логи сервера збігаються з перезапусками, OOM kills або повідомленнями про локальні аварії додатка.
- Лічильники ядра показують збільшення listen drops або aborts у вікні інциденту.
Жарт №1: TCP reset — як кинути трубку посеред речення — раптом ви вже не сперечаєтеся про архітектуру, а хто зачинив телефон.
Польові завдання: команди, виводи та рішення (12+)
Це саме ті завдання, які я виконую на системах Ubuntu 24.04. Кожне містить: команду, реалістичний фрагмент виводу, що це означає, і яке рішення варто ухвалити далі. Виконуйте їх із точки, найближчої до симптомів: клієнт, проксі або сервер.
Завдання 1: Відтворіть з curl і примусьте одиничне з’єднання
cr0x@server:~$ curl -vk --http1.1 --no-keepalive https://api.internal.example/v1/ping
* Trying 10.20.30.40:443...
* Connected to api.internal.example (10.20.30.40) port 443 (#0)
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* Recv failure: Connection reset by peer
* OpenSSL SSL_read: Connection reset by peer, errno 104
curl: (56) Recv failure: Connection reset by peer
Значення: Reset стався під час TLS handshake. Це звужує список підозрюваних: TLS-термінатор (проксі), IDS, некоректний TLS-стек сервера або проблеми на шляху, що змушують посередників переривати.
Рішення: Захопіть пакети на TLS-термінаторі (проксі/LB) спочатку. Якщо проксі відсутній, захопіть на NIC сервера.
Завдання 2: Визначте, чи ви говорите з VIP проксі/LB
cr0x@server:~$ getent ahostsv4 api.internal.example
10.20.30.40 STREAM api.internal.example
10.20.30.40 DGRAM api.internal.example
10.20.30.40 RAW api.internal.example
Значення: Один IP. Все ще може бути VIP.
Рішення: Перевірте ARP/neigh, щоб побачити, чи виглядає як VIP (патерни MAC-вендорів допомагають) і дізнайтесь, де завершується TLS.
Завдання 3: Підтвердіть маршрут і наступний хоп (з боку сервера)
cr0x@server:~$ ip route get 10.20.30.40
10.20.30.40 via 10.10.0.1 dev ens5 src 10.10.2.15 uid 1000
cache
Значення: Трафік виходить через шлюз 10.10.0.1.
Рішення: Якщо підозрюєте фаєрвол/NAT-пристрій, цей шлюз — кандидат. Захопіть з обох боків, якщо можете.
Завдання 4: Базовий стан сокетів і лічильники reset з ss
cr0x@server:~$ ss -s
Total: 1234 (kernel 0)
TCP: 642 (estab 120, closed 410, orphaned 0, timewait 350)
Transport Total IP IPv6
RAW 0 0 0
UDP 18 12 6
TCP 232 180 52
INET 250 192 58
FRAG 0 0 0
Значення: Багато TIME-WAIT і закритих сокетів не обов’язково погано, але це запах проблеми, якщо різко зростає під час скидань.
Рішення: Якщо TIME-WAIT вибухає під навантаженням, перевірте агресивні клієнтські відключення, короткі keep-alive або погану повторну використовуваність з’єднань.
Завдання 5: Перевірте статистику TCP ядра на предмет aborts і listen drops
cr0x@server:~$ nstat -az | egrep 'TcpExtListen|TcpAbort|TcpTimeout|TcpRetrans'
TcpExtListenDrops 18 0.0
TcpExtListenOverflows 5 0.0
TcpAbortOnData 22 0.0
TcpAbortOnTimeout 9 0.0
TcpTimeouts 133 0.0
TcpRetransSegs 420 0.0
Значення: Listen overflows/drops свідчать про те, що сервер не встигає приймати з’єднання (SYN backlog / accept queue pressure). Лічильники abort вказують, що локальний стек скидає з’єднання.
Рішення: Якщо ці показники ростуть під час інцидентів, припиніть звинувачувати клієнта. Виправте accept backlog, цикл accept додатка, завантаження CPU або раптові потоки від проксі.
Завдання 6: Переконайтеся, що сервер справді слухає (і на чому)
cr0x@server:~$ sudo ss -ltnp '( sport = :443 )'
State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
LISTEN 0 4096 0.0.0.0:443 0.0.0.0:* users:(("nginx",pid=1421,fd=6))
Значення: Щось слухає на 443, глибина черги 4096 налаштована.
Рішення: Якщо ніхто не слухає, ядро відправляє RST на SYN. Це ваша відповідь. Якщо слухає — рухайтесь далі.
Завдання 7: Захоплення пакетів, щоб ідентифікувати, хто відправляє RST (з боку сервера)
cr0x@server:~$ sudo tcpdump -ni ens5 'tcp port 443 and (tcp[tcpflags] & (tcp-rst) != 0)' -vv
tcpdump: listening on ens5, link-type EN10MB (Ethernet), snapshot length 262144 bytes
14:22:10.118322 IP 10.20.30.40.443 > 10.10.2.15.51544: Flags [R.], seq 20394822, ack 129388, win 0, length 0
Значення: На сервері ми бачимо пакет із джерелом 10.20.30.40:443, який відправляє RST клієнту (10.10.2.15). Якщо 10.20.30.40 — VIP проксі, це підриває підозри на проксі. Якщо це IP сервера — це підриває сервер.
Рішення: Захопіть на самому проксі. Якщо проксі не згенерував його, ви побачите RST вхідним від апстриму. Якщо згенерував — не побачите.
Завдання 8: Захоплення на проксі, щоб відокремити «апстримний reset» від «проксі reset»
cr0x@server:~$ sudo tcpdump -ni ens5 'host 10.10.2.15 and tcp port 443' -vv
tcpdump: listening on ens5, link-type EN10MB (Ethernet), snapshot length 262144 bytes
14:22:10.118319 IP 10.10.2.15.51544 > 10.20.30.40.443: Flags [S], seq 129387, win 64240, options [mss 1460,sackOK,TS val 332191 ecr 0,nop,wscale 7], length 0
14:22:10.118322 IP 10.20.30.40.443 > 10.10.2.15.51544: Flags [R.], seq 0, ack 129388, win 0, length 0
Значення: Миттєвий RST на SYN (seq 0, ack SYN+1) від 10.20.30.40 вказує, що «нічого не слухає» на цьому VIP/порті з точки зору проксі або локальне правило фаєрволу відхиляє.
Рішення: Перевірте слухачів на хості/контейнері проксі та правила фаєрволу. Якщо проксі має термінувати TLS, підтвердіть, що сервіс запущено і прив’язано.
Завдання 9: Перевірте Nginx error/access логи на предмет клієнтського або апстримного аборту
cr0x@server:~$ sudo tail -n 5 /var/log/nginx/error.log
2025/12/30 14:22:10 [error] 1421#1421: *441 recv() failed (104: Connection reset by peer) while reading response header from upstream, client: 10.10.2.15, server: api.internal.example, request: "GET /v1/ping HTTP/1.1", upstream: "https://10.30.40.50:8443/v1/ping", host: "api.internal.example"
Значення: Nginx (проксі) каже, що апстрим скинув з’єднання під час читання заголовка відповіді з апстриму.
Рішення: Припиніть звинувачувати клієнта. Перейдіть до розслідування апстриму 10.30.40.50:8443 і захопіть там пакети.
Завдання 10: Перевірте стан термінації в HAProxy (якщо ви його використовуєте)
cr0x@server:~$ sudo tail -n 3 /var/log/haproxy.log
Dec 30 14:22:10 lb1 haproxy[2011]: 10.10.2.15:51544 [30/Dec/2025:14:22:10.118] fe_https be_api/srv2 0/0/1/2/3 200 512 - - ---- 12/12/0/0/0 0/0 "GET /v1/ping HTTP/1.1"
Dec 30 14:22:10 lb1 haproxy[2011]: 10.10.2.15:51545 [30/Dec/2025:14:22:10.221] fe_https be_api/srv2 0/0/0/0/0 0 0 - - SD-- 3/3/0/0/0 0/0 "GET /v1/ping HTTP/1.1"
Значення: Флаги термінації (тут «SD–») вказують на те, що закриття сталося на боці сервера. Коди залежать від конфігурації, але лог показує, яка сторона завершила сесію.
Рішення: Якщо домінує серверне завершення під час інциденту, зосередьтеся на стабільності апстриму, таймаутах і тиску ресурсів.
Завдання 11: Перевірте вичерпання conntrack (симптом посередника на Linux NAT)
cr0x@server:~$ sudo sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max
net.netfilter.nf_conntrack_count = 262041
net.netfilter.nf_conntrack_max = 262144
Значення: Ви майже витратили всі записи conntrack. У такому разі нові з’єднання відкидаються або обробляються неправильно. Деякі середовища показуватимуть reset-и; інші — таймаути.
Рішення: Підвищте nf_conntrack_max (з урахуванням пам’яті), скоротіть часи життя conntrack там, де безпечно, і зменшіть шум підключень (keep-alive, pooling).
Завдання 12: Перевірте nftables на предмет «reject with tcp reset»
cr0x@server:~$ sudo nft list ruleset | sed -n '1,120p'
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
ct state established,related accept
iif "lo" accept
tcp dport 22 accept
tcp dport 443 reject with tcp reset
}
}
Значення: Хостовий фаєрвол явно відправляє TCP reset на порт 443. Це проявиться як «connection reset by peer» для клієнтів.
Рішення: Виправте правило. Замініть reject на accept для дозволених джерел, або використайте контрольоване відкидання, якщо ви віддаєте перевагу таймаутам (але будьте готові до повільних відмов).
Завдання 13: Виявлення невідповідностей MTU і offload (тихо підступні)
cr0x@server:~$ ip link show dev ens5
2: ens5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
link/ether 02:ab:cd:ef:12:34 brd ff:ff:ff:ff:ff:ff
Значення: MTU 1500 на цьому інтерфейсі. Якщо інші сегменти використовують jumbo frames або тунелі зменшують MTU, проблеми з фрагментацією/PMTUD можуть з’явитися під час TLS.
Рішення: Якщо підозрюєте MTU, виконайте цілеспрямований ping з DF між тими ж кінцевими точками та відрегулюйте MTU/MSS на потрібному пристрої.
Завдання 14: Доведіть, що PMTUD працює (або ні) за допомогою DF ping-ів
cr0x@server:~$ ping -M do -s 1472 10.20.30.40 -c 3
PING 10.20.30.40 (10.20.30.40) 1472(1500) bytes of data.
ping: local error: message too long, mtu=1450
ping: local error: message too long, mtu=1450
ping: local error: message too long, mtu=1450
--- 10.20.30.40 ping statistics ---
3 packets transmitted, 0 received, +3 errors, 100% packet loss, time 2040ms
Значення: Ваша ефективна Path MTU — 1450 (поширено з VXLAN/GRE/хмарною інкапсуляцією). Якщо стек/проксі не обтинає MSS належним чином, ви можете бачити дивні збої handshake-ів і інколи reset-и.
Рішення: Обмежте MSS на краю (або налаштуйте MTU на інтерфейсах), потім повторно протестуйте TLS.
Завдання 15: Перевірте journalctl навколо точного часу збоїв
cr0x@server:~$ sudo journalctl -u nginx --since "2025-12-30 14:20:00" --until "2025-12-30 14:25:00" | tail -n 8
Dec 30 14:22:09 api1 nginx[1421]: 2025/12/30 14:22:09 [warn] 1421#1421: *439 upstream timed out (110: Connection timed out) while reading response header from upstream
Dec 30 14:22:10 api1 nginx[1421]: 2025/12/30 14:22:10 [error] 1421#1421: *441 recv() failed (104: Connection reset by peer) while reading response header from upstream
Dec 30 14:22:11 api1 systemd[1]: nginx.service: Main process exited, code=killed, status=9/KILL
Dec 30 14:22:11 api1 systemd[1]: nginx.service: Failed with result 'signal'.
Dec 30 14:22:12 api1 systemd[1]: nginx.service: Scheduled restart job, restart counter is at 1.
Значення: Тепер у нас є наратив: таймаути апстриму, потім reset-и, потім Nginx вбивають і перезапускають. Це не «випадкова мережна глюка». Хтось вбиває Nginx (OOM killer? деплой? watchdog?).
Рішення: Перевірте dmesg на OOM і перевірте автоматизацію деплоїв. Якщо Nginx перезапускається, він може скидати поточні з’єднання.
Завдання 16: Перевірте на OOM kills і скарги ядра
cr0x@server:~$ dmesg -T | tail -n 8
[Mon Dec 30 14:22:11 2025] Out of memory: Killed process 1421 (nginx) total-vm:512000kB, anon-rss:210000kB, file-rss:12000kB, shmem-rss:0kB, UID:0 pgtables:900kB oom_score_adj:0
[Mon Dec 30 14:22:11 2025] oom_reaper: reaped process 1421 (nginx), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
Значення: Ядро вбило Nginx. Це обов’язково спричинить reset-и і напіввідкриті дивні стани.
Рішення: Виправте проблеми з пам’яттю (ліміти, витоки, буферизація логів, занадто великі кеші). До тих пір, полювання на «мережу» — це хобі, а не інцидент-репонс.
Жарт №2: Якщо ви вгадуєте причину без пакетного захоплення, ви фактично робите інцидент-репонс за допомогою астрології.
Три корпоративні міні-історії (анонімізовано, правдоподібні, технічно точні)
1) Інцидент через хибне припущення: «Сервер нас скинув»
Симптом був ясний: мобільні клієнти в певному регіоні отримували «connection reset by peer» під час логіну. Команда API не бачила нічого очевидного. Дашборди балансувальника виглядали спокійно. Хтось вимовив небезпечну фразу: «Певно, додаток; він — єдине, що змінилося минулого тижня.»
Вони зробили rollback додатка. Ніяких змін. Зробили rollback міграції БД. Ніяких змін. Почали планувати регіональний failover. Тим часом підтримка клієнтів вигадувала нові й креативні способи сказати «будь ласка, спробуйте ще раз».
Зрештою SRE зробив непретензійну річ: tcpdump на вузлах балансувальника, відфільтрував по RST. RST-пакети виходили не від API-серверів. Вони виходили від пристрою безпеки вище за балансувальник, з IP цього апарату, і лише для конкретних ASN мобільного провайдера.
Root cause — оновлення threat-intel, яке помилково позначило спільний NAT-блок мобільного провайдера як ворожий. Пристрій почав інжектити reset-и для TLS handshake-ів, що відповідали евристикам. «Peer» у логах клієнта був VIP балансувальника, але відправник — ні LB, ні сервер — це коробка, яку ніхто не хотів визнавати.
Виправлення було простим і політичним: whitelist, потім змінили процес контролю змін, щоб security updates розглядалися як production деплої з rollback і моніторингом. Великий урок: припущення дорогі; пакети дешеві.
2) Оптимізація, що обернулася проти: агресивні таймаути бездіяльності
Платформна команда захотіла зменшити кількість з’єднань на внутрішньому API gateway. Вони сильно зменшили «idle timeout». Графіки виглядали відмінно: менше відкритих з’єднань, менше пам’яті, трохи кращий CPU. Люди похвалили одне одного на зустрічі. Для них це був весь постмортем у головах.
Через тиждень resets підскочили — але лише для кількох сервісів. Ці сервіси використовували HTTP keep-alive з довготривалими з’єднаннями і рідкісними сплесками. Клієнти були дисципліновані, повторно використовували з’єднання. Шлюз тепер таймаутував їх у тихі моменти.
Що сталося? Клієнт надсилав наступний запит по з’єднанню, яке вважав живим. Шлюз уже видалив стан і звільнив його. Шлюз відповідав RST (або іноді ядро це робило, залежно від того, як сокет закрився). Для клієнта — «peer reset». Для шлюзу — «я наказав йому бути неактивним».
Найгірше: ретраї робили це голосніше. Деякі клієнти відразу повторювали, множачи навантаження. Зміна, яка мала зменшити ресурси, збільшила churn і латентність та додала невпорядковані збої.
Виправлення — узгодити таймаути по всьому шляху: клієнтський keep-alive, gateway idle, апстрим idle, плюс джиттер. Додали також явне зливання з’єднань під час деплоїв. Оптимізація — це добре. Оптимізація без мислення про режими відмов — це перформанс-арт.
3) Нудна, але правильна практика, що врятувала день: кореляція за tuple
Фінансова компанія мала змішане середовище: Ubuntu сервери, керований балансувальник і сервіс-меш у Kubernetes. Reset-и з’являлися як періодичні 502, і команди вже готували улюблені наративи.
On-call lead зробив нудну річ: стандартизував збір доказів інцидентів. Для будь-якого повідомлення про reset потрібно було надати: timestamp (UTC), IP клієнта, IP сервера, порт призначення і, якщо можливо, ефермерний порт джерела. Це 5-tuple, без протоколу, бо він завжди TCP.
З цим вони зв’язали один і той самий проблемний запит між: логами клієнта, логами sidecar в mesh, логами балансувальника і tcpdump на одному вузлі. Захоплення довело, що RST походить від sidecar, а не від контейнера аплікації й не від апстриму. Sidecar застосував політику, яка на короткий час анулювала запис SAN у ланцюгу сертифікатів при ротації.
Виправлення — робити ротацію сертифікатів з перекриттям і перевіряти синтетичним трафіком перед застосуванням політик. Більше того — культура доказів стала м’язовою пам’яттю. Ніяких героїчних вчинків. Просто повторюване доведення.
Типові помилки: симптом → причина → виправлення
Це та секція, яку ви пролітаєте очима, поки хтось питає «Це наша вина?» на виклику.
1) Reset-и відбуваються негайно при підключенні
- Симптом: curl падає відразу після «Connected», або SYN отримує RST миттєво.
- Причина: Немає слухача на цьому IP:порті; VIP неправильно спрямований; фаєрвол відхиляє з tcp-reset.
- Виправлення: Перевірте слухач з
ss -ltnp; перевірте конфіг VIP; перевірте nftables на reject-правила; підтвердіть, що сервіс прив’язаний до правильної адреси.
2) Reset-и зростають під час деплоїв
- Симптом: короткі сплески ECONNRESET під час rollout, потім все нормалізується.
- Причина: in-flight з’єднання вбиваються при перезапуску процесу; немає graceful shutdown/draining; LB все ще шле трафік до завершуючихся подів.
- Виправлення: Додайте connection draining; збільшіть terminationGracePeriod; встановіть readiness=false перед SIGTERM; вирівняйте затримку дерегистрації LB з shutdown-ом аплікації.
3) Тільки довгі запити падають з reset-ами
- Симптом: малі endpoint-и працюють; великі завантаження/вивантаження або повільні апстрими скидаються посеред потоку.
- Причина: proxy read timeout; таймаути апстриму; ліміти буферів; L7-пристрій abort-ить великі payload-и.
- Виправлення: Перевірте таймаути proxy; налаштуйте таймаути апстриму; перевірте max body size; підтверджуйте, чи DLP/WAF не інжектить RST.
4) Reset-и виглядають «випадковими» по багатьох сервісах
- Симптом: багато несуміжних додатків бачать reset-и одночасно.
- Причина: спільна залежність: вичерпання conntrack на NAT, перевантажений LB, push політик фаєрволу, OOM ядра на спільному проксі-ноді.
- Виправлення: Перевірте conntrack count/max, стан LB, логи змін фаєрволу та OOM-події; стабілізуйте спільний шар спочатку.
5) Reset-и вражають переважно один тип клієнта (мобільні, один регіон, один ISP)
- Симптом: десктоп працює, мобільний лагає; або один регіон падає.
- Причина: відмінності MTU/шляху; поведінка carrier NAT; регіональний пристрій безпеки; гео-маршрутизація до пошкодженого POP.
- Виправлення: Порівняйте захоплення пакетів з різних мереж; виконайте DF-ping; перевірте політики маршрутизації; підтвердіть POP-специфічні налаштування проксі.
6) Апстримні reset-и звинувачують «мережу», але лічильники ядра показують listen drops
- Симптом: Nginx повідомляє apstrim reset; апстрим команда каже «не ми».
- Причина: переповнення accept queue або SYN backlog; процес апстриму занадто повільно виконує accept.
- Виправлення: Збільшіть backlog; налаштуйте
net.core.somaxconn, backlog сервісу; виправте CPU-starvation; додайте capacity або rate limiting.
Контрольні списки / покроковий план
Контрольний список A: «Потрібна відповідь за 15 хвилин»
- Відтворіть з curl з контрольованого хоста; зафіксуйте точний UTC час і IP/порт призначення.
- Визначте, чи є проксі/LB на шляху; з’ясуйте, де завершується TLS.
- Захопіть RST-пакети на проксі/LB (найкраще) або на сервері (друге найкраще) протягом 60–120 секунд під час відтворення.
- За захопленням визначте IP відправника RST. Якщо це IP клієнта — клієнтський abort. Якщо це VIP проксі — проксі. Якщо це IP апстриму — апстрим.
- Зкорелюйте з логами на відправнику: лог помилок проксі, лог додатка, журнал ядра, логи фаєрволу.
- Прийміть рішення: пом’якшення (rollback, вимкнення фічі, обхід WAF, підвищення таймаутів) під час подальшого розслідування.
Контрольний список B: «Доведіть це ретельно, щоб правильна команда виправила»
- Зберіть 5-tuple докази принаймні з двох точок спостереження (проксі + апстрим, або клієнт + проксі).
- Захопіть обидва напрямки; не фільтруйте лише «tcp-rst» без контексту. Зберігайте повний потік для неуспішного з’єднання.
- Підтвердіть синхронізацію часу (NTP) на всіх вузлах; розсинхрон годин зруйнує кореляцію.
- Перевірте лічильники ядра на підозрюваному відправнику (listen drops, aborts, retransmits).
- Перевірте вирівнювання таймаутів у конфігурації: клієнтський таймаут, idle timeout проксі, upstream keep-alive, серверні таймаути.
- Перевірте тиск ресурсів (CPU, пам’ять, FD) і примусові перезапуски.
- Перевірте правила фаєрволу/nftables, які можуть генерувати RST, і недавні зміни.
- Документуйте мінімальні докази: один pcap-фрагмент + один рядок логу + одна зміна лічильника, які всі вказують на того самого відправника.
Контрольний список C: «Запобігти наступному раунду»
- Стандартизуйте keep-alive і бюджети таймаутів між шарами; додайте джиттер.
- Реалізуйте graceful shutdown і connection draining для проксі та додатків.
- Моніторьте використання conntrack на NAT/proxy-ноди.
- Експонуйте лічильники, пов’язані зі скиданнями: TcpExtListenDrops, aborts, nginx 499/502, haproxy termination states.
- Запустіть синтетичні перевірки, які відокремлюють невдачі handshake від помилок додатка.
Поширені запитання
1) Чи означає «Connection reset by peer» завжди, що сервер впав?
Ні. Це означає, що отримано TCP RST. Відправником може бути процес сервера, ядро сервера, проксі, фаєрвол або навіть сам клієнт.
2) Як відрізнити клієнтський reset від серверного в Nginx?
Nginx часто логує клієнтські аборти як «client prematurely closed connection», а апстримні аборти як «recv() failed (104) while reading response header from upstream». Поєднуйте це з пакетним захопленням, щоб бути впевненими.
3) Чому я бачу reset-и тільки під час TLS handshake?
Поширені причини: проблеми з TLS-термінатором проксі, політики/сертифікати, інспекція TLS посередниками, проблеми MTU/PMTUD або сервіс фактично не слухає очікуваний порт за VIP.
4) Чи може фаєрвол відправляти reset замість відкидання?
Так. Багато наборів правил використовують «reject with tcp reset», щоб відмовляти швидко. Це добре для UX, коли зроблено умисно, і погано, коли помилково.
5) Я захопив пакети і бачу RST з IP проксі. Чи це доводить, що проксі винен?
Це доводить, що проксі відправив RST. Проксі може реагувати на помилку апстриму (таймаути, апстримні reset-и) або застосовувати політику (idle timeout, ліміти). Наступний крок — перевірити, чи апстрим показує попередню помилку, яка спричинила рішення проксі.
6) У чому різниця між FIN і RST?
FIN — це ввічливе закриття: «Я завершив передачу». RST — це abort: «Припиніть негайно; це з’єднання недійсне». FIN зазвичай дає чистий EOF в додатках; RST — породжує ECONNRESET.
7) Чому reset-и зростають при зменшенні keep-alive таймаутів?
Бо ви збільшуєте churn з’єднань і шанс, що одна сторона перевикористає з’єднання, яке інша вже дропнула. Невирівняні таймаути — фабрики reset-ів.
8) Чи може Linux сам згенерувати reset навіть якщо додаток у порядку?
Так. Відсутність слухача, переповнення backlog, локальні правила reject у фаєрволі та деякі раптові закриття сокетів можуть призвести до RST, спрямованих ядром.
9) Ми використовуємо Kubernetes. Хто тепер «peer»?
Можуть бути: Service VIP, kube-proxy/iptables/nft правила, Ingress controller, sidecar proxy або сам pod. Тому захоплюйте на вузлі та на рівні ingress, щоб знайти фактичного відправника RST.
10) Якщо я не можу запускати tcpdump у проді, що найкраще робити?
Логи проксі плюс лічильники ядра плюс строго корельована 5-tuple/час. Це слабший доказ, але все ще може показати напрямок. Наполягайте на контрольованих, обмежених у часі захопленнях як стандартному інструменті інциденту.
Висновок: наступні кроки, які реально зменшать кількість скидань
«Connection reset by peer» — це не діагноз. Це симптом з дуже специфічним фізичним артефактом: TCP RST. Якщо ви можете його захопити, ви можете припинити гадання. Якщо ви можете визначити відправника, ви можете припинити суперечки.
Практичні наступні кроки:
- Прийміть швидкий план: відтворити, захопити RST, ідентифікувати відправника, корелювати логи.
- Стандартизуйте докази: UTC timestamp + 5-tuple, щоразу. Зробіть це нудним.
- Вирівняйте таймаути end-to-end, і не «оптимізуйте» idle таймаути без тестів для довго-живих повторних з’єднань.
- Зміцніть спільні шари: розмір conntrack, пам’ять проксі, graceful shutdown і ревізії правил фаєрволу.
- Перетворіть це на runbook для on-call. Ваше майбутнє «я» заслуговує менше сюрпризів.