Should I bind my Heroku app to process.env.PORT or a fixed port for local development?
0 reputation · 19 Mar 2021, 15:31 UTC
0 reputation · 19 Mar 2021, 15:31 UTC
When developing a Heroku‑deployed application, I want the server to start successfully both on my local machine and when pushed to Heroku’s dynos.
The platform assigns a dynamic TCP port through the $PORT environment variable, while local runs often lack this variable, leading developers to either hardcode a port (e.g., 3000) or add a fallback.
What is the recommended approach for handling the missing $PORT in a local environment?
How can I verify that the fallback port works identically to the Heroku‑assigned port when testing with heroku local?
Are there any drawbacks to choosing a specific fallback port (such as 5000) that might interfere with other processes on the dyno?
35275 reputation · 19 Mar 2021, 18:44 UTC
Read process.env.PORT first, with a local fallback. Never hardcode a fixed port as the only option.
const port = process.env.PORT || 3000;
app.listen(port, () => console.log(`Listening on ${port}`));On a dyno, Heroku always sets $PORT, and its router sends traffic only to that port. If your process binds to anything else — or fails to bind within the startup window — the dyno is killed with an R10 boot timeout error. Locally, $PORT is usually unset, so the fallback keeps the same code starting cleanly on your machine.
Confirmed: the dyno's assigned port is dynamic and can change between restarts and deploys, so no fixed value will ever match it. The fallback branch only ever executes off-dyno.
Likely but worth verifying in your case: if your app boots locally but crashes on every deploy, the cause is almost always either ignoring $PORT or binding to 127.0.0.1. Heroku's router must reach your process from outside the container, so bind to 0.0.0.0 (the default in most frameworks when you omit the host). Localhost-only binding works on your laptop and fails silently on dynos.
PORT unset, start the app and confirm it lands on your fallback: curl http://localhost:3000.PORT=5000 node server.js, then curl port 5000.heroku local. It reads your Procfile and .env; adding PORT=5000 to .env makes the app see the same variable it would on a dyno, exercising the identical code path.heroku logs --tail for a clean startup with no R10, then hit the herokuapp.com URL.On the dyno: none. Each dyno is an isolated container, $PORT is always set by the platform, and your fallback value never executes there — it cannot interfere with other dyno processes.
Locally: 5000 is the conventional default for heroku local and foreman-style runners, but on macOS it collides with the AirPlay Receiver service, producing confusing "address in use" errors. If you hit that, either disable AirPlay Receiver (System Settings > General > AirDrop & Handoff) or pick a different fallback such as 3000. Also avoid committing a .env containing PORT if teammates use different local ports — .env is local-only config and is typically gitignored.
One diagnostic detail that would change this advice: if your app still fails on deploy despite reading $PORT, share whether you pass an explicit host to listen() — a 127.0.0.1 bind is the next most common culprit.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.