Heroku Dynos Sleep After 30 Minutes — Here's How to Decide If That Matters
Heroku's cheaper dynos sleep after ~30 minutes idle, causing slow cold starts. Here's how keep-alive pings work, their limits, and when upgrading is the right call.
30 Jan 2026, 06:30 UTC

You deploy a small side project to Heroku, share the link, and the first person to click it waits eight seconds staring at a blank page while the app boots. Nothing is broken — your web dyno went to sleep. On Heroku's eco and basic plans (the successors to the old free and hobby tiers), a web dyno that receives no traffic for roughly 30 minutes shuts down. The next request triggers a cold start: the dyno boots, your app initializes, and only then does the response go out.
The useful takeaway: sleeping is only a problem if real users hit cold starts. For demos, personal tools, and low-traffic internal apps, you can live with it or work around it cheaply. For anything customer-facing, the fix is usually paying for an always-on dyno rather than engineering around the sleep behavior.
What actually happens when a dyno sleeps
Heroku routes all HTTP traffic through its router layer. When your web dyno is asleep, the router holds the incoming request while the dyno starts, then forwards it. From the user's perspective this looks like one very slow request — typically several seconds, depending on how fast your app boots. Subsequent requests are fast because the dyno stays awake as long as traffic keeps arriving.
Two details matter here. First, only web dynos sleep this way — background worker dynos on sleeping-capable plans also idle down, which surprises people running scheduled jobs. Second, a slow boot makes cold starts worse: an app that takes 10 seconds to initialize gives users a 10-second wait. Trimming your boot time (lazy-loading heavy dependencies, deferring cache warm-up) pays off double on sleeping plans.
The keep-alive workaround, and its honest limits
The classic workaround is pinging your own app on a schedule so it never idles for 30 minutes. An external cron service (or a free-tier scheduled job elsewhere) hitting a lightweight health endpoint every 20 minutes keeps the dyno awake during the hours you care about.
A minimal health endpoint looks like this (Express example):
app.get('/healthz', (req, res) => res.status(200).send('ok'));Point any uptime monitor or scheduled HTTP service at https://your-app.herokuapp.com/healthz on a 20-minute interval. Keep the endpoint cheap — no database queries — so the ping itself costs almost nothing.
Be honest about the limits, though. This keeps the dyno awake but doesn't fix the underlying capacity constraints of small dynos: a single web process means one slow request can delay others, and you're consuming your plan's monthly dyno-hour allowance faster by staying awake around the clock. If you only need the app responsive during business hours, schedule pings only for that window and let it sleep overnight — that stretches your hours considerably.
When to stop working around it and upgrade
The decision point is simple: if a cold start would embarrass you in front of a real user, pay for an always-on dyno. Standard-tier dynos don't sleep, and they also give you horizontal scaling and better metrics. The monthly cost of one standard web dyno is modest compared to the engineering time you'd spend babysitting keep-alive hacks, tuning boot time, and explaining slow first loads.
A practical middle path: run eco/basic while you're validating the project, with a keep-alive ping during demo hours, and treat the upgrade as the launch checklist item. That way you're not paying for always-on infrastructure before anyone is using the app.
Verify the behavior yourself
Don't take the 30-minute figure on faith — plans and policies change, so confirm against current Heroku documentation before relying on it. To observe sleeping directly, deploy, wait an hour without traffic, then run heroku logs --tail in one terminal (from your project directory, with the Heroku CLI authenticated) and heroku open in another. You'll see the router report the dyno starting, followed by your app's boot output, before the request is served. Timing that first request tells you your real cold-start cost — and whether your boot time or the dyno wake-up is the bigger problem.
The bottom line: dyno sleep is a feature of the price point, not a bug. Keep-alive pings are a reasonable bridge for low-stakes apps, but the moment reliability matters, the correct engineering decision is usually the boring one: upgrade the dyno and delete the cron job.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.