Ubuntu 24.04 «Connection reset by peer»: доведіть, хто винен — клієнт, проксі чи сервер (випадок №74)

Було корисно?

«Connection reset by peer» — це мережевий еквівалент зводження плечима. Сокет був живий, потім його ніби й не було. Ваші логи звинувачують мережу, команда мережі звинувачує додаток, проксі заперечує свою причетність, а канал інцидентів заповнюється відчуттями замість доказів.

Цей матеріал про те, як перетворити те зведення плечима на підписане зізнання. В Ubuntu 24.04 ви можете довести, чи прийшов RST від клієнта, проксі чи сервера — аж до пакета, PID, таймауту та рівня, на якому прийнято рішення.

Що насправді означає «connection reset by peer» (і чого це не означає)

На рівні системних викликів «connection reset by peer» зазвичай відповідає ECONNRESET. Ваш процес намагався читати або писати в TCP-сокет, і ядро сказало: інша сторона знищила з’єднання.

У термінах TCP «знищення» зазвичай — це пакет з встановленим прапорцем RST. Такий RST може бути відправлений з кількох причин:

  • Пір явно скинув з’єднання (додаток закрився раптово або ядро визнало сокет некоректним).
  • Проміжний пристрій підробив reset (проксі, фаєрвол, балансувальник, NAT, IDS). Той, хто здається «піром», може виявитися брехуном у гарній формі.
  • Хост отримав трафік для з’єднання, якого він не впізнає (немає сокета, стан загубився, порт закритий) і відповів RST.

Чого це не означає:

  • Не таймаут. Таймаути зазвичай дають ETIMEDOUT або таймаут на рівні додатку (наприклад, HTTP-клієнт відмовився чекати). Reset — це миттєвий і явний акт.
  • Не акуратне закриття. Акуратне закриття — це FIN/ACK і дає EOF на читанні, а не ECONNRESET.
  • Не обов’язково «сервер впав». Іноді клієнт відмовився, інколи проксі застосував політику, іноді conntrack зробив фокус зникнення стану.

Два практичних правила, що працюють у продакшені:

  1. Якщо ви не захопили пакети, ви не знаєте, хто відправив RST. Логи можуть підказувати. Пакети доводять.
  2. Якщо є проксі, вважайте, що він причетний, поки не доведено протилежне. Проксі заробляють на тому, щоб втручатися.

Швидкий план діагностики (перший/другий/третій)

Це версія «я на чергуванні, зараз 02:13». Мета — швидко виявити вузьке місце (клієнт, проксі чи сервер), щоб зупинити витік.

Перший: визначте походження reset за точкою зору

  1. Виберіть один невдалий потік: IP клієнта, IP проксі, IP сервера, порт призначення і вікно часу (±30 секунд).
  2. Захопіть пакети одночасно на проксі та сервері (або максимально близько в часі). Якщо можете лише на одному — на проксі.
  3. Знайдіть перший RST у кожному захопленні і визначте, який інтерфейс побачив його першим і з якого IP/port кортежу.

Другий: корелюйте з процесом і логами

  1. На хості-відправнику зіставте 4‑кортеж із сокетом і процесом-власником за допомогою ss -tnpi.
  2. Перевірте логи проксі на предмет upstream resets vs client aborts vs timeouts (це різні формулювання; не зводьте їх докупи).
  3. Перевірте логи сервера на помилки accept, TLS alert, виключення додатка, перезапуски воркерів.

Третій: перевірте системні причини

  1. Виснаження conntrack (шлях NAT або фаєрвол) та виснаження ефермерних портів (на стороні клієнта) можуть створювати «випадкові» ресети.
  2. Проблеми MTU/PMTUD можуть виглядати як ресети, коли пристрої поводяться некоректно, особливо з тунелями/VPN.
  3. Зворотний тиск і таймаути: проксі з агресивними таймаутами скине «повільних» апстрімів. «Повільний» може бути через CPU, сховище, GC або конкуренцію за блокування.

Жарт №1: Найшвидший спосіб знайти винного — додати ще один проксі перед ним — тепер у вас два підозрюваних і запрошення на зустріч.

Модель доказу: як припинити гадання й почати встановлювати винних

Коли ви намагаєтеся довести, звідки прийшов RST, потрібен метод, який витримає політику, а не тільки фізику. Ось модель, яку я використовую в постмортемах:

1) Визначте потік і його «якорі істини»

Один запит може проходити так: клієнт → NAT → ingress proxy → service proxy → бекенд. Єдині речі, які можна вважати «якорями істини»:

  • Захоплення пакетів у кількох точках.
  • Стан сокетів ядра (ss, /proc), бажано на хості, що відправив RST.
  • Таймштамповані логи з correlation ID, які справді пройшли ланцюг.

2) Використовуйте таймлайн, а не відчуття

Ви шукаєте послідовність типу:

  • SYN/SYN-ACK/ACK встановлює з’єднання.
  • Дані йдуть (або ні).
  • Одна сторона відправляє RST (або пристрій його інжектує).

«Хто» — це той хоп, який першим випустив RST, а не той хост, що першим зафіксував виключення в логах.

3) Доведіть напрямок за допомогою 4‑кортежу і ACK-номерів

RST-пакети — це не просто прапорці. Вони містять sequence/acknowledgment номери. Коли ви захоплюєте з обох кінців, часто можна сказати, чи був reset:

  • Локально згенерований хостом, де ви сниффите (виходить через інтерфейс з правильним маршрутом, правильним MAC, очікуваними TTL-патернами).
  • Переправлений (помічений вхідним на проксі від апстріма, потім окремо вихідним до клієнта, іноді з іншим порт-мапінгом).
  • Інжектований (дивний TTL, неочікуваний MAC/vendor OUI, або з’являється лише з одного боку).

4) Визначте, що означає «peer» у вашій архітектурі

Якщо клієнт говорить з проксі, «peer» з точки зору клієнта — це проксі. Бекенд може бути невинним, поки проксі робить ресет. Це не семантика; це змінює, яка команда це виправляє.

5) Ставтеся до таймаутів як до політики

Багато ресетів — це не «помилки», а виконання політики. Наприклад: проксі бачить апстрім простою занадто довго і шле RST клієнту, щоб звільнити ресурси. Це продуктове рішення у мережевому костюмі.

Цікаві факти та історичний контекст (корисний тип)

  • Факт 1: Поведінка TCP RST була формалізована рано; ресети потрібні, щоб швидко вбивати невірні з’єднання замість очікування таймаутів. Ось чому ресети такі грубі.
  • Факт 2: Багато класичних інцидентів «connection reset» у 2000-х фактично були NAT таймаутами на stateful фаєрволах. NAT забуває вас; ваші пакети стають «із нізвідки».
  • Факт 3: Linux може послати RST, коли пакет надходить на порт без слухаючого сокета — часто під час деплоїв, коли процеси перезапускаються і втрачають стан accept.
  • Факт 4: Проксі ввели розрізнення «client abort» vs «upstream reset» як різні категорії; сучасні Nginx/HAProxy/Envoy логи зазвичай можуть сказати, який напрямок перервався першим.
  • Факт 5: «RST інжекція» використовувалася історично для цензури та втручання в трафік. В корпоративних мережах менш драматичний родич — «пристрій безпеки, що завершує потоки».
  • Факт 6: HTTP/2 і HTTP/3 змінили деякі режими відмов: reset у HTTP/2 вбиває багато стрімів одночасно, підсилюючи вплив.
  • Факт 7: Ubuntu 24.04 постачається з новішим ядром і userland; зміни в значеннях за замовчуванням (наприклад, congestion control, TLS-бібліотеки, systemd-сервіси) можуть зсунути межу, де з’являється reset, навіть якщо ваш додаток не змінився.
  • Факт 8: Keepalive були створені, щоб не дозволяти напіввідкритим з’єднанням вічно висіти, але агресивні keepalive + короткий NAT таймаут все одно можуть створити «працювало вчора» тип reset’у.

Практичні завдання: команди, виводи та рішення (12+)

Ці завдання написані для запуску на хостах Ubuntu 24.04. Кожне містить: команду, що означає вивід і яке рішення приймати.

Завдання 1: Підтвердити, що помилка дійсно ECONNRESET і де вона з’являється

cr0x@server:~$ journalctl -u myapp --since "10 min ago" | tail -n 30
Dec 31 01:58:12 api-1 myapp[21450]: error: read ECONNRESET from 10.20.4.18:443
Dec 31 01:58:12 api-1 myapp[21450]: request_id=9b2a... upstream=payments duration_ms=212

Що це означає: Додаток побачив ресет під час читання. Це вказує, що пір (з точки зору цього хоста) надіслав RST або проміжний пристрій це зробив.

Рішення: Визначте, хто в цьому хопі є «peer». Якщо 10.20.4.18:443 — VIP проксі, починайте з проксі.

Завдання 2: Розв’язати мережний шлях: хто насправді пір?

cr0x@server:~$ ip route get 10.20.4.18
10.20.4.18 via 10.20.1.1 dev ens5 src 10.20.2.17 uid 0
    cache

Що це означає: Трафік до піра виходить через ens5 і gateway 10.20.1.1. Ніяких несподіваних оверлеїв.

Рішення: Захоплюйте на ens5. Якщо маршрут проходить через тунельний пристрій, підозрюйте MTU і PMTUD.

Завдання 3: Знімок живого стану TCP (сервер або проксі)

cr0x@server:~$ ss -s
Total: 1218
TCP:   842 (estab 311, closed 377, orphaned 2, timewait 355)

Transport Total     IP        IPv6
RAW       0         0         0
UDP       14        12        2
TCP       465       419       46
INET      479       431       48
FRAG      0         0         0

Що це означає: Багато TIME_WAIT — нормально для завантаженого HTTP; «orphaned» низьке — добре. Велике значення «orphaned» може вказувати на різкі закриття й тиск на ядро.

Рішення: Якщо TIME_WAIT стрімко зростає на стороні клієнта і ви близькі до ліміту ефермерних портів, розгляньте повторне використання з’єднань і перевірку діапазону портів (див. пізніші завдання).

Завдання 4: Знайти точний сокет і процес-власник для невдалого з’єднання

cr0x@server:~$ ss -tnpi dst 10.20.4.18:443 | head
ESTAB 0 0 10.20.2.17:51644 10.20.4.18:443 users:(("myapp",pid=21450,fd=78)) timer:(keepalive,116min,0)

Що це означає: З’єднання існує і належить myapp. Таймер keepalive встановлено; додаток повторно використовує з’єднання.

Рішення: Якщо ресети відбуваються на повторно використовуваних з’єднаннях, підозрюйте idle таймаути на проксі/NAT. Перевірте налаштування таймаутів проксі та таймаути NAT.

Завдання 5: Захоплення пакетів на хості, де підозрюєте RST

cr0x@server:~$ sudo tcpdump -i ens5 -nn -s 0 -vvv 'host 10.20.4.18 and tcp port 443' -c 30
tcpdump: listening on ens5, link-type EN10MB (Ethernet), snapshot length 262144 bytes
01:58:12.041223 IP 10.20.2.17.51644 > 10.20.4.18.443: Flags [P.], seq 182:496, ack 911, win 501, length 314
01:58:12.042007 IP 10.20.4.18.443 > 10.20.2.17.51644: Flags [R.], seq 911, ack 496, win 0, length 0
30 packets captured

Що це означає: Пір (10.20.4.18) надіслав RST відразу після отримання даних. Це не таймаут; це активне відхилення посеред потоку.

Рішення: Зосередьтеся на логах і процесі піра. Якщо 10.20.4.18 — VIP проксі, захопіть на хості проксі, щоб побачити, чи отримував він upstream RST або сам його згенерував.

Завдання 6: Захоплення на проксі: доведіть, чи upstream чи клієнт-сторона

cr0x@server:~$ sudo tcpdump -i ens5 -nn -s 0 -vvv '(host 10.20.2.17 and tcp port 443) or (host 10.30.7.55 and tcp port 8443)' -c 50
tcpdump: listening on ens5, link-type EN10MB (Ethernet), snapshot length 262144 bytes
01:58:12.041980 IP 10.20.2.17.51644 > 10.20.4.18.443: Flags [P.], seq 182:496, ack 911, length 314
01:58:12.042001 IP 10.20.4.18.443 > 10.20.2.17.51644: Flags [R.], seq 911, ack 496, length 0
01:58:12.041700 IP 10.20.4.18.41210 > 10.30.7.55.8443: Flags [P.], seq 991:1305, ack 4402, length 314
01:58:12.041750 IP 10.30.7.55.8443 > 10.20.4.18.41210: Flags [P.], seq 4402:4520, ack 1305, length 118

Що це означає: Проксі скинув клієнтське з’єднання, поки апстрім ще охоче відповідав. Це ресет, згенерований проксі.

Рішення: Припиніть звинувачувати бекенд. Аудитуйте таймаути проксі, ліміти розміру запитів, TLS-настройки та стан воркерів. Проксі приймає рішення.

Завдання 7: Перевірити логи Nginx на предмет client abort vs upstream reset (якщо Nginx)

cr0x@server:~$ sudo tail -n 20 /var/log/nginx/error.log
2025/12/31 01:58:12 [info] 1192#1192: *884 client prematurely closed connection while reading client request body, client: 10.20.2.17, server: api.example, request: "POST /v1/payments HTTP/1.1"
2025/12/31 01:58:12 [error] 1192#1192: *885 upstream prematurely closed connection while reading response header from upstream, client: 10.20.2.17, server: api.example, request: "GET /v1/status HTTP/1.1", upstream: "http://10.30.7.55:8443/v1/status"

Що це означає: Дві різні проблеми. «Client prematurely closed» вказує на abort клієнта. «Upstream prematurely closed» вказує на те, що бекенд закрив раніше (може бути крах, невідповідність keepalive, закриття додатком або таймаут апстріма).

Рішення: Розділяйте інциденти за формулюванням у логах. Розглядайте їх як різні дерева корінних причин, а не одне велике «мережеве» явище.

Завдання 8: Перевірити стан термінації в HAProxy (якщо HAProxy)

cr0x@server:~$ sudo tail -n 5 /var/log/haproxy.log
Dec 31 01:58:12 lb-1 haproxy[2034]: 10.20.2.17:51644 [31/Dec/2025:01:58:12.041] fe_https be_api/api-3 0/0/1/2/3 200 512 - - ---- 12/12/0/0/0 0/0 "GET /v1/status HTTP/1.1"
Dec 31 01:58:12 lb-1 haproxy[2034]: 10.20.2.17:51645 [31/Dec/2025:01:58:12.050] fe_https be_api/api-2 0/0/0/0/1 0 0 - - cD-- 3/3/0/0/0 0/0 "POST /v1/payments HTTP/1.1"

Що це означає: Флаги термінації, як-от cD--, сильно вказують на abort зі сторони клієнта («client» + «data»). Запит ніколи не завершився.

Рішення: Якщо HAProxy фіксує client abort, але клієнти заперечують, перевірте клієнтські таймаути й проміжні пристрої (мобільні мережі, корпоративні проксі, дефолтні налаштування SDK).

Завдання 9: Перевірити Envoy на предмет причин upstream reset (якщо Envoy)

cr0x@server:~$ sudo journalctl -u envoy --since "10 min ago" | tail -n 10
Dec 31 01:58:12 edge-1 envoy[1640]: [debug] upstream reset: reset reason: connection termination, transport failure reason: delayed connect error: 111
Dec 31 01:58:12 edge-1 envoy[1640]: [debug] downstream connection termination

Що це означає: Envoy дає вам причину upstream reset і часто причину збою транспорту. «connect error: 111» — це connection refused (RST від апстріма через відсутність слухачів).

Рішення: Трактуйте «connection refused» як проблему доступності бекенду (сервіс не слухає, невірний порт, проблемний деплой), а не як випадкові ресети.

Завдання 10: Перевірити лічильники ядра на предмет проблем з listen/backlog

cr0x@server:~$ netstat -s | egrep -i 'listen|overflow|reset' | head -n 20
    14 times the listen queue of a socket overflowed
    14 SYNs to LISTEN sockets ignored
    2318 connection resets received
    1875 connections reset sent

Що це означає: Переповнення черги accept може спричинити, що клієнти бачать ресети або помилки під час з’єднання під час сплесків. «reset sent» означає, що цей хост активно скидає піри.

Рішення: Якщо переповнення listen зростає під час інцидентів, налаштуйте backlog (somaxconn, accept backlog у додатку) або масштабуйтеся. Не чіпайте таймаути спочатку; ви лише сховаєте симптом.

Завдання 11: Перевірити виснаження conntrack (поширено на NAT/proxy)

cr0x@server:~$ sudo sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max
net.netfilter.nf_conntrack_count = 262131
net.netfilter.nf_conntrack_max = 262144

Що це означає: Ви майже повні. Коли conntrack насичений, нові потоки відкидаються або ушкоджуються. Результуючі помилки додатка можуть включати ресети, таймаути і дивні неповні рукостискання.

Рішення: Збільшіть nf_conntrack_max, якщо дозволяє пам’ять; скоротіть таймаути для мертвих потоків і зменшіть churn підключень (keepalive, pooling). Знайдіть джерело вибуху з’єднань.

Завдання 12: Перевірити діапазон ефермерних портів клієнта і тиск TIME_WAIT

cr0x@server:~$ sysctl net.ipv4.ip_local_port_range
net.ipv4.ip_local_port_range = 32768	60999

Що це означає: Близько 28k портів доступно на один IP-адрес джерела. При великій кількості короткоживучих з’єднань ви можете вичерпати порти і отримати помилки з’єднання, що проявляються як ресети upstream.

Рішення: Віддавайте перевагу keepalive/pooling; якщо потрібно, розширюйте діапазон портів і розгляньте кілька вихідних IP (SNAT-пули) для клієнтів з великим обсягом трафіку.

Завдання 13: Перевірити проблеми MTU та PMTUD

cr0x@server:~$ ping -M do -s 1472 10.20.4.18 -c 3
PING 10.20.4.18 (10.20.4.18) 1472(1500) bytes of data.
From 10.20.2.17 icmp_seq=1 Frag needed and DF set (mtu = 1450)
From 10.20.2.17 icmp_seq=2 Frag needed and DF set (mtu = 1450)
From 10.20.2.17 icmp_seq=3 Frag needed and DF set (mtu = 1450)

--- 10.20.4.18 ping statistics ---
3 packets transmitted, 0 received, +3 errors, 100% packet loss, time 2025ms

Що це означає: Path MTU = 1450, а не 1500. Якщо щось блокує ICMP «frag needed», деякі TCP-сесії зависають, а потім пристрої їх вбивають, іноді через ресети.

Рішення: Вирівняйте MTU (тунелі, VXLAN), дозволіть ICMP для PMTUD або обмежте MSS на краях.

Завдання 14: Підтвердити, чи TLS handshake скидається

cr0x@server:~$ openssl s_client -connect 10.20.4.18:443 -servername api.example -tls1_2 
CONNECTED(00000003)
write:errno=104

Що це означає: Errno 104 — це ECONNRESET. Ресет під час handshake часто вказує на політику TLS проксі (SNI обов’язкове, шифри, ALPN), rate limiting або бекенд, що не говорить TLS на цьому порту.

Рішення: Порівняйте з відомим робочим клієнтом, перевірте SNI, інспектуйте TLS-конфіг проксі і захопіть пакети, щоб визначити, хто відправив RST.

Завдання 15: Перевірити поточні дропи і помилки на інтерфейсі

cr0x@server:~$ ip -s 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 06:3a:9e:11:22:33 brd ff:ff:ff:ff:ff:ff
    RX:  bytes packets errors dropped  missed   mcast
    1287349832 1298374      0     412       0       0
    TX:  bytes packets errors dropped carrier collsns
    983748233  992331      0       9       0       0

Що це означає: RX drops ненульові. Дропи можуть спричиняти ретрансляції і викликати політики таймаутів, що закінчуються ресетами.

Рішення: Якщо дропи збігаються з часом інциденту, дослідіть черги NIC, навантаження CPU хоста та загальну заторність. Не ганяйтеся за привидами додатка, поки шлях пакетів нестабільний.

Завдання 16: Прив’язати ресет до локального фаєрвол-правила (nftables/ufw)

cr0x@server:~$ sudo nft list ruleset | sed -n '1,120p'
table inet filter {
  chain input {
    type filter hook input priority 0; policy accept;
    tcp dport 443 ct state new limit rate 50/second accept
    tcp dport 443 ct state new reject with tcp reset
  }
}

Що це означає: Ви явно надсилаєте TCP resets, коли перевищується rate limit. Це не «мережа». Це політика.

Рішення: Якщо ресети сплескуються під час піків, підкоригуйте rate limits, додайте буферизацію/черги або масштабуйтеся. Також: задокументуйте правило, щоб наступний на чергуванні не відкривав його о 3 ранку.

Сценарії відмов проксі (Nginx, HAProxy, Envoy)

Якщо ви запускаєте будь-який тип проксі, ви в бізнесі «connection reset by peer», подобається вам це чи ні. Ось шаблони, які мають значення.

Проксі скидає клієнту, бо апстрім повільний

Типово коли:

  • Апстрім відповідає заголовками занадто довго (proxy_read_timeout в Nginx; таймаути в HAProxy/Envoy).
  • Буфери проксі заповнені; він вирішує вбити з’єднання, щоб не тонути разом із кораблем.

Як довести: Захоплення на проксі показує клієнтський RST без відповідного upstream RST у той момент. Логи проксі показують таймаут/504/термінацію.

Виправлення: Вирішіть, чи підвищити таймаути, чи виправити затримки апстріма. Підвищення таймаутів — ставка, що повільність «нормальна». У продакшені повільність рідко є нормальною; це симптом.

Проксі скидає через обмеження тіла запиту або заголовків

Проксі може агресивно закривати з’єднання при надвеликих заголовках/тілах. Залежно від конфігурації та моменту часу клієнт може побачити ресет замість акуратної 413/431.

Як довести: Логи проксі згадують «client sent too large request» або помилки парсингу заголовків. Захоплення показує RST незабаром після того, як клієнт відправляє заголовки/тіло.

Виправлення: Підвищуйте ліміти обережно і цілеспрямовано. Якщо підвищити глобально — ви підвищуєте площу ураження для зловживань і пам’ятевого тиску.

Несумісність keepalive між проксі та апстрімом

Одна з найпоширеніших продакшн-лопух: проксі повторно використовує апстрім-з’єднання довше, ніж апстрім хоче. Апстрім закриває прості з’єднання. Проксі намагається писати в мертвий сокет. Ресет трапляється десь, а помилка вказує не туди, куди треба.

Як довести: На проксі: upstream connection в ss показує keepalive таймери; логи апстріма показують idle timeout закриття; захоплення пакетів показує upstream FIN/RST на простому з’єднанні, а потім проксі намагається писати.

Виправлення: Узгодьте keepalive таймаути: клієнт SDK, edge proxy, service proxy, backend server і NAT. Визначте ієрархію (edge найменший або backend найменший) і документуйте.

Політика TLS (SNI/ALPN/ciphers), що виглядає як випадкові ресети

Режими відмов TLS часто погано логуються додатками і іноді проксі закривають з’єднання жорстко. Клієнт без SNI може отримати ресет. Старий клієнт з слабкими шифрами може отримати ресет. Переговори HTTP/2 ускладнюють це, коли ALPN задіяний.

Як довести: openssl s_client показує ресет під час handshake; логи проксі показують помилки handshake; захоплення показує RST відразу після ClientHello.

Виправлення: Застосовуйте TLS-політи, але робіть їх спостережуваними. Якщо ви маєте намір робити ресет, логувати чому — обов’язково.

Причини на боці сервера: додаток, ядро, TLS, сховище (так, сховище)

Інколи ресети — це провина сервера у простому вигляді: сервер перевантажений, неправильно налаштований або перезапускається, і ядро робить те, що робить ядро — погіршує день вашому додатку.

Перезапуски додатка і churn з’єднань

Якщо бекенд перезапускається під навантаженням (OOM, crash loop, деплой), існуючі з’єднання можуть бути порвані. Клієнти бачать ресети, якщо процес помирає або сайдкар/проксі жорстко розриває сокети.

Доказ: Кореляція часу ресетів з перезапусками сервісу в journalctl і подіями оркестратора. Захоплення пакетів показує SYN-и до відсутнього слухача.

Переповнення listen backlog

Сервер може бути «вверх» і одночасно не приймати з’єднання. Якщо accept-черга переповнена, SYN-и відкидаються або ігноруються; клієнти повторюють спроби; проміжні реагують; врешті-решт хтось робить ресет.

Доказ: netstat -s показує переповнення listen; CPU сервера може бути завантажений; логи додатка показують повільний accept.

Виправлення: Налаштуйте backlog, масштабуйтеся і зменшіть накладні витрати на підключення. Не ховайте проблему довшими таймаутами.

Затримки сховища, які маскуються під мережеві ресети

Ось де інженери сховища тягнуть за собою у «мережеві проблеми». Якщо потоки сервера блокуються на диску (fsync-шторм, насичення логів, глюки EBS, синхронні записи ZFS), затримки запитів зростають. Проксі потрапляють під таймаути і скидають клієнтів. Клієнти звинувачують сервер. Сервер звинувачує мережу. Ніхто не звинувачує диск, бо диск «зелений».

Доказ: Спайки латентності в метриках додатка збігаються з таймаутами проксі і ресетами клієнтів. На сервері iostat і pidstat показують IO wait у вікні інциденту.

Виправлення: Вважайте латентність сховища частиною шляху запиту. Встановіть SLO для неї. Якщо ви не можете її виміряти — не зможете її виправдати.

Плутанина з TLS offload

Бекенди, що випадково говорять HTTP на порту, де проксі очікує TLS (або навпаки), можуть породжувати ресети, що виглядають як нестабільність handshake. Це класична конфіг-дріфт ситуація.

Доказ: Захоплення показує plaintext там, де мав бути TLS, або миттєвий ресет після ClientHello. Логи проксі показують «wrong version number» або помилку handshake.

Виправлення: Зробіть контракти порт/протокол явними. «Так завжди було» — це баг, а не причина.

Причини на боці клієнта: abort, NAT, MTU та «допоміжні» бібліотеки

Клієнти спричиняють шокуючу кількість ресетів. Не зі зла — з нетерпіння, дефолтних таймаутів і проміжних пристроїв, що агресивно звільняють стан.

Abort клієнта через локальний таймаут

SDK часто мають за замовчуванням короткі таймаути. Мобільні клієнти ще гірші, бо мережі там нестійкі. Корпоративні десктоп-клієнти можуть бути найгіршими через креативність проксі безпеки.

Доказ: Логи проксі кажуть «client prematurely closed connection» або стани термінації HAProxy вказують на abort клієнта. Захоплення на проксі показує FIN/RST від клієнта першим.

Виправлення: Робіть клієнтські таймаути явними і узгодженими з поведінкою сервера/проксі. Якщо вам потрібні 30 секунд для запиту — не відправляйте клієнт із 5-секундним таймаутом і не звинувачуйте сервер.

NAT idle таймаути вбивають довгоживучі прості з’єднання

Клієнт за NAT може тримати TCP-з’єднання просто довше, ніж таймаут NAT. NAT забуває відображення. Наступний пакет виходить, зворотний трафік не може співставити стан, і якийсь пристрій відповідає RST або відкидає.

Доказ: Ресети відбуваються після періодів простою; зміна інтервалу keepalive змінює частоту збоїв. Таблиці conntrack на NAT показують короткі таймаути для established flows.

Виправлення: Keepalive з інтервалом коротшим за NAT таймаут, або уникайте довгоживучих простих з’єднань у недружніх мережах.

MTU mismatch і чорні діри

Проблеми MTU не повинні спричиняти ресети. В ідеальному світі вони викликають фрагментацію або коригування PMTUD. У реальному світі деякі пристрої блокують ICMP, інші «допомагають», убиваючи потоки.

Доказ: ping -M do показує path MTU меншу, ніж очікувано; захоплення показує ретрансляції, а потім ресети проксі після таймаутів.

Виправлення: Вирівняйте шлях MTU або обмежте MSS на краю тунелів.

Жарт №2: «Напевно, це клієнт» — не діагноз; це механізм справляння зі стресом з номером тікета.

Три міні-історії з корпоративного світу

Міні-історія 1: Інцидент через невірне припущення

У них була проста історія: «База даних скидає підключення». Помилка була ECONNRESET, стек-трейс вказував на драйвер БД, сервіс таймаутив під навантаженням. Тому команда БД знову отримала виклик, і всі готувалися до звичного танцю.

Старший інженер поставив грубе, але необхідне питання: «Звідки саме прийшов ресет?» Ніхто не знав. Була телеметрія, дашборди і воєнна кімната; не було захоплення пакетів.

Вони захопили на двох точках: хост додатку і шар HAProxy. На хості додатку RST, здавалося, йшов від VIP HAProxy. На HAProxy не було upstream RST від бази даних. Натомість HAProxy видавав ресети, бо його клієнтський таймаут був коротшим за найповільніший процентиль запитів до БД під час IO wait.

Невірне припущення було в тому, що «peer» у помилці — це база даних. Насправді peer був проксі. БД була повільною, так, але вона не скидала з’єднання; проксі реалізовував політику.

Виправлення полягало не в «підвищенні всіх таймаутів назавжди». Збільшили таймаут проксі трохи, але основна робота була зменшення IO-затримок — перенесення індексного обслуговування, кращі плани запитів і вивантаження логування на інший том. Після цього проксі перестав «допомагати».

Міні-історія 2: Оптимізація, що зіграла злий жарт

Команда хотіла зменшити затримки та CPU, збільшивши повторне використання keepalive скрізь. Менше рукопожатій, менше TCP-установок, менше TLS-накладних витрат. На папері — красиво.

Ролл-аут почали з edge proxy, дозволивши апстрім-з’єднанням жити значно довше. За день клієнти почали повідомляти про випадкові ресети на звичайних запитах. Логи були бісівські: деякі запити виконувалися миттєво, інші вмирали посеред запиту з «connection reset by peer».

Корінь проблеми — невідповідність: upstream server мав агресивний idle timeout і іноді перезапускав воркерів. Проксі, що тримав apстріми довше, повторно використовував сокети, які були тихо вбиті. Перший запис у напівмертвий сокет викликав ресет. Під високою конкуренцією це виглядало як випадковість.

Захоплення пакетів зробило все очевидним: апстрім відправив FIN на idle; проксі не помітив вчасно; проксі написав; ядро відповіло RST. «Оптимізація» підвищила ймовірність попасти на застарілі сокети апстріма.

Виправлення було банальним: узгодити таймаути, ввімкнути активні health checks, які реально перевіряють запити, і виставити upstream keepalive менше. Продуктивність покращилася після того, як вони перестали хитрувати.

Міні-історія 3: Нудна, але правильна практика, що врятувала ситуацію

Платформна команда мала правило: кожен інцидент, пов’язаний із мережею, вимагає «двостороннього захоплення» пакетів. Люди скаржилися, що це повільно і процедурно. Воно й було таким, і в цьому сенс.

Одного дня внутрішній сервіс почав падати з ресетами. Команда додатку наполягала, що це проксі. Команда проксі наполягала, що це додаток. Мережники наполягали, що це «апстрім». Канал інцидентів почав пахнути комітетом.

Вони дотрималися правила. Захопили на клієнтському інтерфейсі проксі і на upstream інтерфейсі. Ресет був помітний, що йшов від фаєрволу між проксі та бекендом, тільки на upstream боці. Проксі просто передавав біль далі.

Оскільки вони мали докази рано, уникли години конфіг-переробок. Вікно змін у фаєрволі показало нещодавню корекцію правила, що відкидала певні нові потоки з TCP reset після досягнення ліміту швидкості. Це не було злим наміром; воно було «захисним».

Виправлення — підлаштувати правило фаєрволу під легітимний профіль сплесків сервісу і додати явний ексепшен для підмережі проксі. Частота ресетів впала одразу. Нікому не довелося вдавати, що «відчував» проблему.

Типові помилки: симптом → корінна причина → виправлення

  • Симптом: Ресети тільки на великих POST-запитах.
    Корінна причина: Обмеження розміру тіла запиту або буферизації на проксі; іноді WAF перериває потоки.
    Виправлення: Підвищити конкретні ліміти (client_max_body_size), налаштувати буферизацію, перевірити політику WAF; підтвердити за допомогою захоплення пакетів і логів проксі.
  • Симптом: Ресети відбуваються приблизно через ~60 секунд простою на повторно використовуваних з’єднаннях.
    Корінна причина: NAT/proxy idle timeout коротший за період reuse keepalive клієнта.
    Виправлення: Вирівняти keepalive/idle таймаути; розглянути TCP keepalive tuning; зменшити повторне використання idle сокетів.
  • Симптом: Сплески трафіку викликають миттєві ресети/connection refused.
    Корінна причина: Переповнення listen backlog або rate limiting, що відкидає з TCP reset.
    Виправлення: Налаштувати backlog, масштабувати або підкоригувати rate limiting; підтвердити через netstat -s і правила фаєрволу.
  • Симптом: Тільки деякі клієнти (старі SDK) бачать ресети під час TLS handshake.
    Корінна причина: Застосування TLS-політик: відсутнє SNI, непідтримувані шифри, ALPN mismatch; проксі жорстко закриває.
    Виправлення: Робіть TLS-вимоги явними; покращуйте звітність про помилки; тестуйте з openssl s_client зі налаштуваннями, схожими на клієнта.
  • Симптом: Ресети корелюють з деплоями, але падає лише частка трафіку.
    Корінна причина: Не налаштовано draining; проксі відправляє трафік на рестартуючі інстанси; застарілі upstream keepalive сокети.
    Виправлення: Додати graceful shutdown, readiness gates, draining і коротші upstream keepalive.
  • Симптом: «Випадкові» ресети під високим churn підключень; NAT/proxy хости поводяться дивно.
    Корінна причина: Conntrack таблиця майже повна; дропи/евікції створюють втрату стану.
    Виправлення: Збільшити conntrack max, зменшити churn, налаштувати таймаути, ідентифікувати топ-токерів.
  • Симптом: Ресети збігаються з дропами пакетів і ретрансляціями.
    Корінна причина: Дропи інтерфейсу, переповнення черг, насичення CPU або заторення шляху, що приводить до політичних таймаутів і ресетів.
    Виправлення: Спочатку виправте втрачені пакети; потім налаштуйте таймаути. Використовуйте ip -s link і захоплення пакетів для підтвердження.
  • Симптом: Ресети з’являються тільки через VPN/тунельні шляхи.
    Корінна причина: MTU mismatch або блоковані ICMP, що призводить до PMTUD провалу; проксі переривають повільні/завислі потоки.
    Виправлення: Обмежте MSS, вирівняйте MTU, дозволіть ICMP «frag needed», перевірте за допомогою DF ping.

Контрольні списки / покроковий план

Покроковий план: доведіть, клієнт це, проксі чи сервер за годину

  1. Виберіть один невдалий запит: таймштамп, IP клієнта, VIP призначення, шлях запиту, correlation ID якщо доступний.
  2. Визначте перший проксі-хоп (ingress/load balancer). У сучасних стеках це зазвичай справжній «peer» для клієнта.
  3. Захопіть пакети у двох точках:
    • На проксі: інтерфейс, що з’єднується з клієнтом.
    • На проксі: інтерфейс, що з’єднується з апстрімом (або на хості бекенда).
  4. Знайдіть перший RST і зафіксуйте:
    • IP:port джерела і призначення
    • Точний час
    • Чи це [R.] чи [R] і чи відповідає він на дані
  5. Корелюйте з логами на хості, що міг відправити RST:
    • Фрази з error log Nginx
    • HAProxy termination flags
    • Envoy upstream reset reasons
    • Лічильники ядра (netstat -s)
  6. Перевірте системні обмеження (швидко):
    • conntrack count vs max
    • дропи інтерфейсу
    • навантаження CPU / IO wait
  7. Прийміть рішення:
    • Якщо проксі згенерував RST: виправте конфіг/таймаути/політику проксі.
    • Якщо бекенд згенерував RST: виправте життєвий цикл додатка, слухач, TLS або перевантаження.
    • Якщо клієнт згенерував RST: виправте логіку таймаутів/ретраїв клієнта або мережевий шлях клієнта.
    • Якщо проміжний пристрій інжектував: виправте правила фаєрволу/WAF/NAT або стан таблиць.

Операційний чекліст: що записати для оборонного звіту по інциденту

  • зріз pcap(ів) що показує RST з двох сторін
  • 4‑кортеж(и), що брали участь, і будь-які NAT-мапінги якщо відомі
  • рядки логів проксі для того ж запиту
  • докази перезапусків або перевантаження сервера (journald, метрики)
  • стан conntrack і діапазон портів якщо релевантно
  • точні налаштування, що залучені (таймаути, ліміти, rate limits)

FAQ

1) Чи означає «connection reset by peer» завжди, що віддалений сервер впав?

Ні. Це означає, що хтось на іншому кінці цього TCP-хопу послав ресет — або проміжний пристрій прикидався ними. У проксійованих архітектурах «peer» часто — це проксі.

2) Як довести, що ресет надіслав проксі?

Захопіть пакети на проксі. Якщо проксі надсилає RST клієнту, в той час як upstream-сторона не має відповідного RST (а іноді навіть відповідає даними), проксі — джерело.

3) Чи може фаєрвол викликати ресети замість дропів?

Так. Фаєрволи і WAF часто «reject with tcp reset», щоб швидко відмовити. Це поширено при rate limits, невірному стані або порушенні політики.

4) Чому я бачу це тільки на keepalive-з’єднаннях?

Тому що повторно використовувані з’єднання можуть бути застарілими. NAT idle таймаути, idle таймаути проксі та бекенду не вирівняні автоматично. Перший запис після простою — момент, коли ви виявляєте, що говорили з привидом.

5) Чи справді потрібне захоплення пакетів? Можна обійтися логами?

Логи можуть підказати; пакети доводять. Коли в інциденті задіяно кілька команд, «підказка» купує вам наради. Доказ купує вам виправлення.

6) У чому практична різниця між FIN і RST?

FIN — ввічливе закриття: читання повертає EOF. RST — жорсткий abort: read/write дає ECONNRESET. Багато проксі й політик обирають RST, щоб швидко звільнити ресурси.

7) Чи може проблема зі сховищем справді призвести до ресетів?

Опосередковано — так. Латентність сховища може зробити бекенди повільними, що викликає таймаути проксі; проксі тоді скидає клієнта. Ресет — симптом; корінь проблеми може бути в IO wait.

8) Як відрізнити client aborts від server aborts?

Логи проксі зазвичай найшвидший натяк (client prematurely closed vs upstream closed). Для доказу — захоплення пакетів показує, хто першим послав FIN/RST.

9) Що робити, якщо я бачу ресети, але ніхто нічого не логить?

Тоді у вас бракує спостережуваності на рівні, що прийняв рішення. Додайте структуроване логування в проксі (причини термінації), робіть короткі pcap під час інцидентів і записуйте стан сокетів.

Висновок: практичні наступні кроки

Ви не «виправляєте» лише «connection reset by peer». Ви виправляєте компонент, який вирішив надіслати ресет — або умову, що змусила його це зробити.

Наступні кроки, що окупаються:

  1. Прийміть правило двостороннього захоплення для мережевих інцидентів. Воно швидко припиняє сперечання.
  2. Нормалізуйте бюджети таймаутів між клієнтами, проксі і серверами. Запишіть їх. Нехай це буде частиною code/change review.
  3. Зробіть ресети спостережуваними: причини термінації проксі, лічильники відмов фаєрволу, алерти на насичення conntrack, алерти на переповнення backlog.
  4. Запускайте наведені вище завдання під час інциденту і вставляйте виводи у тікет. Майбутній ви подякує минулому вам.

Парафразована ідея (атрибутовано): Werner Vogels наголошував, що треба «будувати системи, що передбачають відмови і швидко відновлюються», бо відмови — нормальна частина розподілених систем.

← Попередня
Proxmox local-lvm на 100%: знайдіть, що з’їло ваш thin pool і виправте це
Наступна →
Fail2ban для пошти: правила, що насправді ловлять атаки

Залишити коментар