Heroku H10 App crashed: causes and fixes
THE SHORT ANSWER
Heroku logs H10 App crashed when a request reaches your app and every web dyno has crashed. H10 is the symptom, not the cause. The real error is in the app's own log lines just above the router line, and it is usually one of five things: the app didn't bind to $PORT within 60 seconds, the Procfile is wrong, a package was pruned with devDependencies, a config var or database is missing at boot, or the dyno ran out of memory.
What does H10 App crashed mean?
Heroku's error code reference says: “A crashed web dyno or a boot timeout on the web dyno will present this error.” The router couldn't hand the request to any web dyno, so the visitor sees Heroku's Application Error page and the router logs a line like this:
heroku[router]: at=error code=H10 desc="App crashed" method=GET path="/" host=example-app.herokuapp.com fwd=17.17.17.17 dyno= connect= service= status=503 bytes=
The empty dyno= is the giveaway: no dyno was available to take the request. The line tells you nothing about why. The crash itself is logged earlier, by your app and by the dyno manager. In Heroku's own example the cause is a missing gem, printed two seconds before the router line:
heroku[web.1]: State changed from down to starting app[web.1]: Starting process with command: `bundle exec rails server -p 22020` app[web.1]: Missing the Rails 2.3.5 gem. Please `gem install -v=2.3.5 rails` heroku[web.1]: Process exited heroku[router]: at=error code=H10 desc="App crashed" ... status=503
Only web dynos produce H10. A crashed worker dyno never does, because workers don't receive HTTP requests. The numbers in this post are for Heroku's Cedar generation (the Common Runtime most apps run on); Fir apps differ in a few places, noted below.
Quick checklist
| Log line above the H10 | Cause | Fix |
|---|---|---|
Error R10 (Boot timeout) -> Web process failed to bind to $PORT within 60 seconds of launch | The app listens on a fixed port, or does too much work before it starts listening | Read process.env.PORT, and move build steps out of the start command |
Error: Cannot find module '...' | The package is in devDependencies, which Heroku prunes after the build | Move it to dependencies |
Process exited with status 0 right after starting | The web command runs something that finishes, such as a migration or a build | Make the web: line start a server; put migrations in release: |
Python ModuleNotFoundError from gunicorn | The module path in the Procfile doesn't match your code | Use gunicorn module:app or gunicorn project.wsgi |
A connection error, or a KeyError / undefined for a setting | A config var or the database isn't available when the app boots | Check heroku config and the add-on's status |
Error R15 (Memory quota vastly exceeded) | The dyno was killed for using too much memory | Lower worker count or memory use, or use a bigger dyno |
How do I find the real error?
Start with the logs, then look at the dyno state:
# Stream logs, then load the page again to trigger a fresh boot heroku logs --tail # Only your app's own output, without router lines heroku logs --source app --tail # Dyno state: up, starting, crashed heroku ps # Force a restart so the boot error shows up again in the tail heroku ps:restart
On Cedar, heroku logs shows the last 100 lines by default and up to 1,500 with -n. If the crash scrolled past, restart the dyno with the tail open. Fir apps keep no log history, so heroku logs there is always a live tail.
Two more ways to see the crash directly:
heroku run bashopens a one-off dyno with your built app. Run the command from yourweb:line by hand and the error prints in your terminal. One-off dynos can't receive HTTP traffic, so this is for reading errors, not for testing requests.heroku local webruns the Procfile's web process on your machine, reading a local.envfile. It catches Procfile typos before you push.
If the crash started with your last deploy, heroku releases lists recent releases and heroku releases:rollback puts the previous code and config vars back while you fix it. A rollback doesn't touch add-ons or data stored in them, so a database migration stays applied.
Does your app bind to $PORT in time?
This is the most common cause. Heroku assigns each web dyno a port in the PORT environment variable. If the process hasn't bound to it within 60 seconds of launch, Heroku kills it with error R10, and every request after that gets H10. Java apps built with Gradle or Maven get 90 seconds.
// Node / Express const port = process.env.PORT || 3000 app.listen(port) # Procfile, Python (gunicorn binds 0.0.0.0:$PORT on its own when PORT is set) web: gunicorn myproject.wsgi # Procfile, uvicorn web: uvicorn main:app --host 0.0.0.0 --port $PORT
Calling app.listen(port) with no host is fine in Node: it listens on all interfaces. Passing "localhost" or "127.0.0.1" is not, because the router connects from outside the process. The full list of framework defaults is in 502 Bad Gateway after deploy.
If the app does bind to PORT and still hits R10, it is too slow to start. Heroku names two reasons: waiting on an external resource such as a database, and doing too much work during startup. A start script that runs webpack, a TypeScript compile or a Rails asset build is the usual example. Move that work into the build (a build or heroku-postbuild script for Node) so the start command only starts the server.
Is the Procfile right?
The web process type is the only one that receives HTTP traffic, so the web: line has to start a server that keeps running. Heroku's rules for the file:
- It is named
Procfile, with a capital P and no extension.Procfile.txtandprocfiledon't count. - It sits in the root of the repo, not in a subfolder.
- Each line is
<process type>: <command>.
web: gunicorn myproject.wsgi release: python manage.py migrate
Heroku counts a process that exits with status 0 as a crash too. A web: line like python manage.py migrate && gunicorn myproject.wsgi works, but web: python manage.py migrate alone runs, exits and crashes the dyno. Migrations belong in the release: line: if the release command fails, Heroku doesn't deploy that release and the current one keeps running.
Without a Procfile, a Node app runs its start script from package.json. Check that npm start runs the production server and not a dev server or a build.
Was a package pruned with devDependencies?
Heroku's Node.js build installs dependencies and devDependencies, runs your build script, then removes everything in devDependencies before the app starts. A package your server imports at runtime that is listed as a dev dependency works during the build and then crashes the dyno with Cannot find module.
npm install --save some-package # moves it to dependencies
Heroku also documents a switch to skip pruning, heroku config:set NPM_CONFIG_PRODUCTION=false, but moving the package is the real fix: it keeps the slug smaller and makes the dependency list say what the app needs.
While you're in package.json, check engines.node. Without it Heroku picks its default Node version (24.x today), which may not be the one you develop on.
Is a config var or the database missing at boot?
Config vars set on your laptop in a .env file don't exist on Heroku until you set them. An app that reads a missing variable at startup, or tries to open a database connection with it, exits before binding to PORT.
heroku config # what the dyno actually has heroku config:set SECRET_KEY=... # restarts the dynos with the new value
Setting or changing a config var restarts the dynos, so you'll see straight away whether it fixed the crash. If the database is the problem, check the add-on is provisioned and that the app reads DATABASE_URL rather than a host and password from your local setup.
Did the dyno run out of memory?
Look for R14 and R15 above the H10. R14 (Memory quota exceeded) means the dyno is swapping and slow but still running. R15 (Memory quota vastly exceeded) means Heroku killed it with SIGKILL, which is a crash. For Python, gunicorn reads WEB_CONCURRENCY, which Heroku sets from the dyno size; lowering it runs fewer workers and uses less memory. For Node, look for a large in-memory cache or a leak, or move to a dyno with more memory.
Why does H10 keep showing after I fixed it?
Heroku restarts a crashed dyno once straight away. If it crashes again it waits: up to 20 minutes before the next attempt, then up to 40, 60, 180 and 320 minutes. So an app can keep returning H10 for a while after the underlying problem (say, a database outage) has cleared.
The wait resets when the dyno starts correctly, when you push a new release, when you restart the app, or when you scale web dynos to 0 and back. After a fix, deploy or run heroku ps:restart instead of waiting. Private Spaces restart continuously with no cool-off, and Fir apps use a different backoff.
H10 vs H12, H13, H14 and H20
| Code | Meaning | Where to look |
|---|---|---|
| H10 App crashed | Web dynos exist but have crashed or failed to boot | The app's log lines before the crash |
| H12 Request timeout | A request took longer than 30 seconds | Slow endpoints; move long work to a worker |
| H13 Connection closed without response | The app accepted the connection and closed it without answering | Server timeouts shorter than 30 seconds, killed worker processes |
| H14 No web dynos running | There are no web dynos at all | heroku ps:scale web=1 |
| H20 App boot timeout | No web dyno reached the up state within 75 seconds | Slow startup; same fixes as R10 |
What happens on EthioDeploy?
The same mistakes crash an app on any platform, but on EthioDeploy a crash shows up during the deploy instead of in front of your visitors. Every deploy waits for a health check: a GET to / that has to return a 2xx or 3xx status within 30 seconds. If the new version crashes or never answers, the deploy fails and the previous version keeps serving traffic.
The deploy log then says what happened, with the exit code:
The app's process exited (code 1) and was restarted 3 times. Check the container logs above for the error.
EthioDeploy sets PORT and HOSTNAME=0.0.0.0 for you, reads the web line of a Procfile as the start command, and imports Heroku config vars with ethiodeploy env push .env. If you're moving off Heroku, see Heroku alternatives and EthioDeploy vs Heroku.
Questions
H10 means every web dyno has crashed or failed to boot. The common causes are not binding to $PORT within 60 seconds (error R10), a wrong or missing Procfile web line, a package removed with devDependencies, a missing config var or database at startup, and running out of memory (R15). The real error is in the app's log lines just before the H10.
Run heroku logs --tail, then reload the app or run heroku ps:restart so the dyno boots again. Read the lines from app[web.1] and heroku[web.1] just before the router's H10 line, fix that error, and deploy. A new release resets Heroku's restart cool-off, so the fix takes effect straight away.
No. R10 is a boot timeout: the web process didn't bind to $PORT within 60 seconds and Heroku killed it. H10 is what the router logs for requests after that, because no web dyno is running. R10 is one cause of H10.
No. H10 only happens when a web request reaches an app whose web dynos have all crashed. A crashed worker dyno shows up in heroku ps and in the logs, but never as H10.
Usually because something your laptop provides is missing on Heroku: a fixed port instead of $PORT, a package in devDependencies that Heroku prunes, a variable from a local .env file, or a local database. Run heroku local web to test the Procfile on your machine, and heroku config to see what the dyno actually has.
H10 means web dynos exist but have crashed. H14 means there are no web dynos at all, usually because they were scaled to 0. Fix H14 with heroku ps:scale web=1.
Sources
Checked on November 2, 2026:
- Heroku: Error codes (H10, H12, H13, H14, H20, R10, R14, R15)
- Heroku: Dyno startup behavior
- Heroku: Dyno restarts
- Heroku: Logging
- Heroku: CLI commands
- Heroku: One-off dynos
- Heroku: heroku local
- Heroku: Releases and rollback
- Heroku: The Procfile
- Heroku: Release phase
- Heroku: Node.js builds (devDependencies pruning)
- Heroku: Node.js support
- Heroku: Gunicorn on Heroku
- Heroku Help: Could H10 errors be produced by non-web dynos?
- Heroku Help: Why is my Node.js app crashing with an R10 error?
- Node.js: server.listen
- Gunicorn: bind setting
Join the EthioDeploy Telegram
Release notes as they ship, plus deploy help from the team and other developers.