{"id":"IL-0264","org":"Healthchecks.io","date":"2025-11-01","docs":[],"title":{"en":"Healthchecks.io requests slowed and timed out as HAProxy load balancers ran out of connection slots during a ping flood","ko":"Healthchecks.io 의 로드 밸런서가 연결 슬롯을 다 써서 웹사이트와 핑 엔드포인트의 요청이 느려지거나 타임아웃됐다"},"domain":["networking"],"impact":{"scope":"healthchecks.io website and hc-ping.com ping endpoints: some requests took several seconds or timed out; false monitoring alerts for users","users":"unknown","region":"unknown"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"The tarpit rate-limiting method only slows clients that make requests one after another; a client sending many requests in parallel is not slowed, and the sockets held during the wait can exhaust the load balancer's connection slots. The operator called it one more lesson learned and switched rate-limited requests to the \"Deny\" method.","ko":"HAProxy 의 속도 제한 방식은 저마다 장단점이 있다. tarpit 은 요청을 차례로 보내는 클라이언트는 늦출 수 있지만, 클라이언트가 요청을 대량으로 동시에 보내면 로드 밸런서를 오히려 무너뜨릴 수 있다. 운영자는 이번 일을 교훈으로 삼아 속도 제한에 걸린 요청을 deny 방식으로 거절하도록 바꿨다."},"related":[],"sources":[{"url":"https://blog.healthchecks.io/2025/11/post-mortem-outage-on-november-1/","kind":"blog","archived":"https://web.archive.org/web/20260807113738/https://blog.healthchecks.io/2025/11/post-mortem-outage-on-november-1/"}],"summary":{"en":"Healthchecks.io rate-limits in two layers to keep ping floods from overloading its database: HAProxy on the load balancer servers applies a lax first limit and passes good requests to NGINX on the application servers, which applies stricter rate limiting and geo-blocking. For clients over the limit HAProxy can deny (return HTTP 429), tarpit (wait, then return HTTP 429, holding the socket during the wait), silently drop or reject the connection. Healthchecks.io used tarpit with a 1-second wait for the spammiest clients. Tarpit slows clients that send requests one after another, but can overwhelm the load balancer when a client sends a very large number of requests in parallel.\n\nWhen a misconfigured client sent a sustained 300 requests per second for one UUID, the tarpit did not slow it down, and the HAProxy servers hit their connection slot limits and rejected legitimate requests. One load balancer was removed from DNS rotation at 23:39, shifting its load to the others. After being woken at 00:11, the operator identified the client's addresses, a single IPv4 address and a /64 IPv6 subnet, and at 01:06 changed the load balancer configuration to reject TCP requests from them, after which things went back to normal. At 08:40 the rate limiting was changed to return HTTP 429 immediately instead of using tarpit, and in the following days the operator added an HAProxy request timeout, raised maxconn, extended rate limiting to hchk.io, added a Netdata alert on HAProxy frontend sessions and changed the HAProxy log format.","ko":"2025년 11월 1일 23시 30분 UTC 부터 11월 2일 1시 10분 UTC 까지 healthchecks.io 웹사이트와 hc-ping.com 핑 엔드포인트에서 요청이 몇 초씩 걸리거나 타임아웃됐다. Healthchecks.io 는 로드 밸런서의 HAProxy 에서 느슨한 속도 제한을 걸고, 애플리케이션 서버의 NGINX 에서 더 엄격한 속도 제한과 지역 차단을 건다. HAProxy 는 요청이 특히 많은 클라이언트를 tarpit 방식으로 제한하도록 설정돼 있었다. tarpit 은 잠시 기다린 뒤에 HTTP 429 를 보내는 방식이라 그동안 네트워크 소켓 하나가 묶인다. 연결 슬롯이 모자랄 위험을 줄이려고 대기 시간은 1초로 두었다.\n\n11월 1일 23시 24분, 잘못 설정된 클라이언트가 UUID 하나로 보내는 핑을 초당 300건으로 늘렸다. 클라이언트는 tarpit 에도 느려지지 않았다. 요청이 많아 HAProxy 서버가 연결 슬롯 한도에 닿았고, 정상 요청까지 거절했다. 23시 39분에 로드 밸런서 하나가 연결을 거절하다 DNS 응답 목록에서 자동으로 빠지자, 남은 로드 밸런서에 부하가 몰리며 연쇄 반응이 일어났다. 운영자는 0시 11분에 알림을 받고 조사를 시작했다. 1시 6분에는 문제의 IPv4 주소 하나와 IPv6 /64 서브넷을 찾아 이 주소들의 TCP 요청을 거절하도록 로드 밸런서 설정을 바꿨다. 그러자 서비스가 정상으로 돌아왔다. 8시 40분에는 tarpit 대신 바로 HTTP 429 로 응답하도록 속도 제한 설정을 바꿨고, 11월 5일까지 HAProxy 설정과 알림을 더 손봤다."},"trigger":{"en":"A misconfigured client flooded the ping endpoint, raising requests for a single UUID from 10 to a sustained 300 requests per second.","ko":"잘못 설정된 클라이언트가 UUID 하나로 핑 요청을 초당 10건에서 초당 300건으로 늘려 계속 보냈다."},"severity":"unknown","symptoms":["intermittent-timeouts","high-latency","intermittent-connection-failures"],"timeline":{"detected_after":"41m","resolved_after":"1h 40m"},"date_kind":"incident","detection":{"en":"Monitoring notifications woke the operator at 00:11 UTC.","ko":"운영자가 11월 2일 0시 11분 UTC 에 요란한 알림에 잠에서 깨어 조사를 시작했다."},"components":["haproxy"],"root_cause":"capacity","remediation":[{"text":{"en":"Identified the offending client addresses, a single IPv4 address and a /64 IPv6 subnet","ko":"문제를 일으킨 클라이언트의 IP 주소를 찾았다. IPv4 주소 하나와 IPv6 /64 서브넷이었다."}},{"text":{"en":"Changed the load balancer configuration to reject TCP requests from those addresses","ko":"이 주소들에서 오는 TCP 요청을 거절하도록 로드 밸런서 설정을 바꿨고, 서비스가 정상으로 돌아왔다."}}],"action_items":[{"text":{"en":"Rate-limit with HTTP 429 returned immediately (the \"Deny\" method) instead of the \"tarpit\" method","ko":"비슷한 문제를 막으려고 로드 밸런서의 속도 제한 설정을 바꿔 tarpit 방식을 쓰지 않고 바로 HTTP 429 로 응답하게 했다."}},{"text":{"en":"Add the missing timeout http-request 5s to the HAProxy configuration to guard against Slowloris attacks","ko":"HAProxy 설정에 빠져 있던 timeout http-request 5s 를 넣어 Slowloris 공격을 막게 했다. Slowloris 공격도 연결 슬롯을 바닥낼 수 있고, 하기도 쉽기 때문이다."}},{"text":{"en":"Increase the maxconn parameter in the HAProxy configuration","ko":"로드 밸런서에 여유를 더 주려고 HAProxy 설정의 maxconn 값을 올렸다."}},{"text":{"en":"Apply the load balancer rate-limiting rules to the hchk.io endpoints as well as hc-ping.com","ko":"로드 밸런서의 속도 제한 규칙이 hc-ping.com 뿐 아니라 hchk.io 엔드포인트에도 걸리게 했다."}},{"text":{"en":"Add a Netdata alert when active HAProxy frontend sessions pass thresholds well below maxconn","ko":"HAProxy 프론트엔드의 활성 세션 수가 maxconn 보다 한참 낮은 기준값을 넘으면 알리도록 Netdata 에이전트에 알림 규칙을 더했다."}},{"text":{"en":"Change the HAProxy logging format to make the logs easier to use","ko":"로그를 쓰기 편하도록 HAProxy 로그 형식을 손봤다."}}],"contributing":["automation-amplified","no-alert-on-limit"],"recurrence_of":null,"symptoms_text":{"en":"Between 23:30 UTC on 1 November and 1:10 UTC on 2 November 2025, both the Healthchecks.io website (healthchecks.io) and the ping endpoints (hc-ping.com) had intermittent connectivity problems: some requests took several seconds to finish and others timed out entirely. The load balancers were running out of connection slots and rejecting legitimate requests, and Healthchecks.io users received false monitoring alerts.\n\nAt 23:24 UTC ping requests for one specific UUID rose from 10 requests per second to a sustained 300 requests per second from a misconfigured client. At 23:39 one of the load balancers began rejecting connections and was automatically removed from DNS rotation, which put more load on the remaining load balancers and started a chain reaction. The operator was woken by notifications at 00:11.\n\nHAProxy on the load balancers rate-limited particularly spammy clients with the \"tarpit\" action (http-request tarpit), which waits before returning HTTP 429 and keeps a network socket tied up while it waits. The wait was set to 1 second to limit the risk of running out of connection slots. The flooding client did not slow down, and at its request rate the waiting requests used up the HAProxy connection slots. The post-mortem shows a Netdata graph of the aggregate number of open IPv4 and IPv6 sockets on the load balancer servers.","ko":"Healthchecks.io 의 웹사이트 healthchecks.io 와 핑 엔드포인트 hc-ping.com 에 연결이 간헐적으로 안 됐다. 일부 요청은 끝나는 데 몇 초씩 걸렸고, 어떤 요청은 아예 타임아웃됐다. 운영자는 요란한 알림에 잠에서 깼다.\n\n11월 1일 23시 24분 UTC 부터 UUID 하나로 오는 핑 요청이 초당 10건에서 초당 300건으로 늘어 그대로 이어졌다. 23시 39분에 로드 밸런서 하나가 연결을 거절하기 시작해 DNS 응답 목록에서 자동으로 빠졌다. 그만큼 남은 로드 밸런서에 부하가 몰리면서 연쇄 반응이 시작됐다.\n\nHAProxy 가 이 클라이언트에 걸어 둔 tarpit 속도 제한은 1초를 기다린 뒤에 응답한다. 그런데 클라이언트는 느려지지 않았고, 기다리는 동안 소켓이 묶이면서 HAProxy 가 연결 슬롯 한도에 닿았다. 그 결과 로드 밸런서가 정상 요청까지 거절했다.\n\nHealthchecks.io 사용자에게는 이 사고로 잘못된 모니터링 알림이 갔다. 운영자는 보통 이런 증상이 Healthchecks.io 가 손쓸 수 없는 인프라 문제에서 온다고 봤지만, 이번에는 Healthchecks.io 쪽 문제였다."},"updated_at":"2026-11-14T12:33:51.554729Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0264","org_slug":"healthchecks-io"}