IL-0352 Heroku · 2025-07-18networking
Heroku Router 2.0 spread HTTP requests unevenly across dynos for four days, causing latency and timeouts
Router 2.0, the default router of Heroku's Common Runtime, uses entropy metadata to spread dyno selection. A code condition present since the Router 2.0 beta reduced the variance of that selection, and a router instance that hit it kept balancing badly for all later requests. Hitting it became more likely as request volume on Router 2.0 grew with the migration of about 94% of Common Runtime apps. From 22:00 UTC on 18 July 2025 more and more router instances hit the condition, and requests piled onto some dynos of affected apps, which caused latency and timeouts. The bug also showed in staging load tests, but their alerts watch availability and latency, not request distribution, so nothing fired, and Heroku learned of the problem only from customers on 20 July.
On 22 July Heroku declared an internal investigation at 14:35 UTC and a formal incident at 17:48 UTC, posted to the status site at 18:36 UTC, and advised customers to fall back to the legacy router. After seeing the same pattern in staging, engineers cycled the production router fleets at 19:42 UTC, which improved balancing, identified the broken entropy metadata at 20:05 UTC, and at 21:01 UTC reproduced the fault with a new load balancing benchmark test and prepared a fix that passed it. The fix was deployed to staging at 21:13 UTC, judged safe at 21:30 UTC, rolled out to production from 21:38 UTC in stages, and reached the last partition at 00:12 UTC on 23 July, which ended the incident.
- Severity
- –
- Scope
- HTTP request distribution across web dynos for apps using Router 2.0 on the Common Runtime, with higher latency, timeouts, reduced availability and Heroku error codes
- Users affected
- Some customer applications using Router 2.0 on Heroku's Common Runtime
- Region
- –
- Time to detect
- 42h 46m
- Time to resolve
- 98h 12m
Symptoms
From about 22:00 UTC on 18 July 2025, some apps on Heroku's Common Runtime that used Router 2.0 had their HTTP requests balanced unevenly across their web dynos. The dynos that got more requests were overloaded, and the apps saw higher HTTP request latency, timeouts, reduced availability and various Heroku error codes in application logs and dashboards.
The imbalance, measured as the percentage variance in HTTP requests per dyno, grew steadily over the following days. Once a router instance hit the faulty code condition, its load balancing stayed broken for every later request, and more router instances hit the condition over time.
Heroku Support received the first customer reports of uneven request distribution, with higher latency and timeouts, at 16:46 UTC on 20 July. An internal investigation was declared at 14:35 UTC on 22 July, and Support began advising affected customers to disable Router 2.0 and fall back to the legacy router. Engineers first suspected incomplete router dyno state and dropped that theory at 17:01 UTC. At 17:53 UTC they saw the same uneven distribution in the logs of the router's continuous load tests in staging, turned to the load balancing metadata, and found at 19:42 UTC that cycling router instances improved balancing.
The staging load tests had not raised an alert. They alert and block deployments on reduced routing availability and higher latency, but request distribution was not an alerting signal, and the uneven distribution in staging did not strain the test apps enough to move availability or latency.
Root cause
Root cause Software defect
- Trigger
- From 22:00 UTC on 18 July 2025 router instances began hitting a code condition that broke the entropy metadata Router 2.0 uses to choose dynos. The condition had been in the code since the Router 2.0 beta and became more likely as more HTTP requests went through the new router, with about 94% of Common Runtime apps migrated.
Detection
No alert fired. Heroku Support received the first customer reports of uneven request balancing at 16:46 UTC on 20 July 2025, and an internal investigation was declared on 22 July.
Remediation
- Advised affected customers to disable Router 2.0 and fall back to the legacy router
- Cycled the production router fleets, which improved load balancing for affected apps
- Deployed the router fix to staging, then rolled it out to all Common Runtime partitions by 00:12 UTC on 23 July
Action items
- Introduced gating and notifications so that load balancing behaviour regressions do not occur
- Communicate the timeline and send reminders to apps still on the legacy router before automated migrations resume
Lessons
–
Sources
- https://status.heroku.com/incidents/2863 · official-status · archived copy
References
- https://devcenter.heroku.com/articles/http-routing#legacy-router-and-router-2-0
- https://devcenter.heroku.com/articles/http-routing#request-distribution
- https://devcenter.heroku.com/articles/error-codes
- https://www.heroku.com/blog/router-2dot0-the-road-to-beta/#load-test-continuously
- https://devcenter.heroku.com/changelog-items/2682
- https://help.salesforce.com/s/articleView?id=005123907&type=1
Related cases
IL-0150A latent race condition in DynamoDB's DNS automation left an empty DNS record for dynamodb.us-east-1.amazonaws.com, and the DynamoDB and EC2 launch failures that followed caused EKS, ECS and Fargate container launch failures and cluster scaling delays in us-east-1Amazon Web Services · 2025same root cause · shared Cloud load balancer
IL-0131A new kind of GenerateContent request made Vertex AI prediction API servers segfault, and once enough of them restarted at the same time the serving fleet could not carry the loadGoogle Cloud · 2024same root cause · shared Cloud load balancer
IL-0529New Google Cloud VMs and other instances came up without network connectivity in all zones after VPC controllers stoppedGoogle Cloud · 2024same root cause · shared Cloud load balancer
IL-0253Firefox froze and could not load any web content for close to two hours, stuck in an HTTP/3 request to TelemetryMozilla · 2022same root cause · shared Cloud load balancer
IL-0243Google's global External Proxy Load Balancing returned 404 errors for most HTTP/S endpoints for 33 minutesGoogle Cloud · 2021same root cause · shared Cloud load balancer
IL-0106A malformed Traefik IngressRoute triggered a Traefik v2 bug once a node rollover restarted every Traefik pod, and all Kubernetes services returned 404DFDS · 2021same root cause · shared Cloud load balancer
IL-0493Netlify's Google Cloud TCP load balancer stopped serving about 5% of global traffic as all backends failed health checksNetlify · 2021same root cause · shared Cloud load balancer
IL-0345Many Render services in the Oregon region returned 503 errors for about four hoursRender · 2020same root cause · shared Cloud load balancer
IL-0030Most nodes of a cluster went NotReady together for 15 minutes while the API server sat behind an AWS ELBZalando · 2019same root cause · shared Cloud load balancer
IL-0338Google Cloud HTTP(S) and TCP/SSL Proxy load balancers returned 502s or reset connections on 33% to 87% of requestsGoogle Cloud · 2018same root cause · shared Cloud load balancer
IL-0395Google Cloud HTTP(S) Load Balancer gave 502s on 25% of requests for 22 minutes after reverting to a stale configurationGoogle Cloud · 2017same root cause · shared Cloud load balancer
IL-0396New or live-migrated GCE instances, VPN tunnels and network load balancers were unreachable on public IPs for over 2hGoogle Cloud · 2017same root cause · shared Cloud load balancer
Check your cluster for this
For AI tools and scripts
- This case as JSON: https://incidentlib-api.benchlet.com/cases/IL-0352
- The cases most like it: https://incidentlib-api.benchlet.com/cases/IL-0352/similar
- MCP server (Streamable HTTP, no key):
https://incidentlib-mcp.benchlet.com/mcp
The API needs no key. The MCP server offers search_incidents, get_incident, list_incidents, get_topic and vocab; both are explained on API and MCP.