Heroku Router 2.0 is now generally available, marking a significant step forward in our infrastructure modernization efforts. The new router delivers enhanced performance and introduces new features to improve your applications’ functionality. There are, of course, nuances to be aware of with any new system, and with Router 2.0 set to become the default router soon, we’d like to share some tips and tricks to ensure a smooth and seamless transition.
Start with a Staging Application
We recommend exploring the new router’s features and validating your specific use cases in a controlled environment. If you haven’t already, spin up a staging version of your app that mirrors your production set-up as closely as possible. Heroku provides helpful tools, like pipelines and review apps, for creating separate environments for your app. Once you have an application that you can test with, you can opt-in to Router 2.0 by running:
$ heroku features:enable http-routing-2-dot-0 -a <staging app name>
You may see a temporary rise in response times after migrating to the new router, due to the presence of connections on both routers. Using the Heroku CLI, run heroku ps:restart
to restart all web dynos. You can also accomplish this using the Heroku Dashboard, see Restart Dynos for details. This will force the closing of any connections from the legacy router. You can monitor your individual request response times via the service
field in your application’s logs or see accumulated response time metrics in the Heroku dashboard.
How to Determine if Your Traffic is Going Through Router 2.0
Once your staging app is live and you have enabled the http-routing-2-dot-0
Heroku Feature, you’ll want to confirm that traffic is actually being routed through Router 2.0. There are two easy ways to determine the router your app is using.
HTTP Headers
You can identify which router your application is using by inspecting the HTTP Headers. The Via
header, present in all HTTP responses from Heroku applications, is a code name for the Heroku router handling the request. Use the curl
command to display the response headers of a request or your preferred browser’s developer tool.
To see the headers using curl
, run:
curl --head https://your-domain.com
In Router 2.0 the Via
header value will be one of the following (depending on whether the protocol used is HTTP/2 or HTTP/1.1):
< server: Heroku
< via: 2.0 heroku-router
< Server: Heroku
< Via: 1.1 heroku-router
The Heroku legacy router code name for comparison, is:
< Server: Cowboy
< Via: 1.1 vegur
Note that per the HTTP/2 spec, RFC 7540 Section 8.1.2, headers are converted to lowercase prior to their encoding in HTTP/2.
To read more about Heroku Headers, see this article.
Logs
You will also see some subtle differences in your application’s system logs after migrating to Router 2.0. To fetch your app’s most recent system logs, use the heroku logs --source heroku
command:
2024-10-03T08:20:09.580640+00:00 heroku[router]: at=info method=GET path="/"
host=example-app-1234567890ab.heroku.com
request_id=2eab2d12-0b0b-c951-8e08-1e88f44f096b fwd="204.204.204.204"
dyno=web.1 connect=0ms service=0ms status=200 bytes=6742
protocol=http2.0 tls=true tls_version=tls1.3
2024-10-03T08:35:18.147192+00:00 heroku[router]: at=info method=GET path="/"
host=example-app-1234567890ab.heroku.com
request_id=edbea7f4-1c07-a533-93d3-99809b06a2be fwd="204.204.204.204"
dyno=web.1 connect=0ms service=0ms status=200 bytes=6742 protocol=http1.1 tls=false
In this example, the output shows two log lines for requests sent to an app’s custom domain, handled by Router 2.0 over both HTTPS and HTTP protocols. You can compare these to the equivalent router log lines handled by the legacy routing system:
2024-10-03T08:22:25.126581+00:00 heroku[router]: at=info method=GET path="/"
host=example-app-1234567890ab.heroku.com
request_id=1b77c2d3-6542-4c7a-b3db-0170d8c652b6 fwd="204.204.204.204"
dyno=web.1 connect=0ms service=1ms status=200 bytes=6911
protocol=https
2024-10-03T08:33:49.139436+00:00 heroku[router]: at=info method=GET path="/"
host=example-app-1234567890ab.heroku.com
request_id=057d3a4b-2f16-4375-ba74-f6b168b2fe3d fwd="204.204.204.204"
dyno=web.1 connect=1ms service=1ms status=200 bytes=6911 protocol=http
The key differences in the router logs are:
- In Router 2.0, the protocol field will display values like
http2.0
orhttp1.1
, unlike the legacy router which identifies the protocol withhttps
orhttp
. - In Router 2.0, you will see new fields
tls
andtls_version
(the latter will only be present if a request is sent over a TLS connection).
Here are some alternative ways to view your application's logs.
HTTP/2 is Now the Default
One of the most exciting changes in Router 2.0 is that HTTP/2 is now enabled by default. This new version of the protocol brings improvements in performance, especially for apps handling concurrent requests, as it allows multiplexing over a single connection and prioritizes resources efficiently.
Here are some considerations when using HTTP/2 on Router 2.0:
- HTTP/2 terminates at the Heroku router and we forward HTTP/1.1 from the router to your app.
- Router 2.0 supports HTTP/2 on custom domains, but not on the built-in
<app-name-cff7f1443a49>.herokuapp.com>
default domain. - A valid TLS certificate is required for HTTP/2. We recommend using Heroku Automated Certificate Management.
You can verify your app is receiving HTTP/2 requests by referencing the protocol value in your application’s logs or looking at the HTTP response headers for your request.
That said, not all applications are ready for HTTP/2 out-of-the-box. If you notice any issues during testing or if the older protocol is simply more suitable for your needs, you can disable HTTP/2 in Router 2.0, reverting to HTTP/1.1. Run the following command:
heroku labs:enable http-disable-http2 -a <app name>
Keepalives Always On
Another key enhancement in Router 2.0 is the improved handling of keepalives, setting it apart from our legacy router. Router 2.0 enables keepalives for all connections between itself and web dynos by default, unlike the legacy router which opens a new connection for every request to a web dyno and closes it upon receiving the response. Allowing keepalives can help optimize connection reuse and reduce the overhead of opening new TCP connections. This in turn lowers request latencies and allows higher throughput.
Unfortunately, this optimization is not 100% compatible with every app. Specifically, recent Puma versions have a connection-handling bug that results in significantly longer tail request latencies if keepalives are enabled. Thanks to one of our customers, we learned this during the Router 2.0 beta period. For more details, see the blog post on this topic. Their early adoption of our new router and timely feedback helped us pinpoint the issue and after extensive investigation, identify the problem with Puma and keepalives.
Just like with HTTP/2 we realize one size does not fit all, thus we have introduced a new labs feature that allows you to opt-out of keepalives. To disable keepalives in Router 2.0, you can run the following command:
heroku labs:enable http-disable-keepalive-to-dyno -a <app name>
Conclusion
Migrating to Router 2.0 represents a critical step in leveraging Heroku’s latest infrastructure improvements. The transition offers exciting new features like HTTP/2 support and enhanced connection handling. To facilitate a seamless transition we recommend you start testing the new router before we begin the Router 2.0 rollout to all customers in the coming months. By following these tips and confirming your app’s routing needs are met on Router 2.0, you will be well-prepared to take full advantage of the new router’s benefits.
Stay tuned for more updates as we continue to improve Router 2.0’s capabilities and gather feedback from the developer community!