Fly.io Free Tier Cold Start Latency: Node.js API Benchmarks

Cold starts hit you at the worst possible moment. Your API sits quiet for six minutes, someone finally hits an endpoint, and the response time jumps from 80ms to 3.8 seconds. No error. No crash. Just infrastructure doing exactly what it was designed to do — and that behavior determines whether your free-tier deployment is actually usable for anything real.
Fly.io has been pulling serious developer attention since restructuring its free tier in late 2024. By Q1 2026, it’s become one of the most-discussed Docker hosting platforms among indie developers and small teams shipping Node.js APIs. But the marketing page and the reality of a suspended shared-cpu-1x machine are two different conversations. The benchmark data tells a more specific story.
Key Takeaways
- Fly.io suspends machines after roughly 5 minutes of inactivity, producing cold start latency between 1.8–4.2 seconds for Node.js APIs depending on image size and region.
- A minimal Express image (~180MB) cold-starts in approximately 1.9 seconds on
shared-cpu-1xwith 256MB RAM, while a larger NestJS image (~420MB) averages 3.7 seconds under equivalent conditions.- Fly.io’s
fly-proxyadds 12–18ms of routing overhead per warm request — competitive with Render’s free tier, but slower than Railway’s always-on hobby tier.- The free tier includes 3 shared-CPU VMs and 160GB outbound data transfer monthly — enough for low-traffic APIs, though machine suspension is non-negotiable without upgrading.
- Switching to lightweight base images (
node:20-alpine) and structuring Dockerfile layer caching correctly reduces effective cold start latency by 40–60%.
How Fly.io’s Free Tier Actually Works in 2026
Fly.io isn’t traditional serverless. It runs Docker containers as Firecracker microVMs — lightweight virtual machines that boot fast but still require real startup time. That distinction matters when you’re trying to figure out why a Node.js health check returns a 15-second timeout at 3am.
The free tier as of March 2026 includes:
- 3 shared-cpu-1x VMs (1 vCPU shared, 256MB RAM each)
- 3GB persistent storage across all volumes
- 160GB outbound data transfer monthly
- No credit card required for the base allocation
The catch: Fly.io suspends machines after roughly 5 minutes of inactivity — what Fly.io’s docs call “scale to zero.” When traffic hits a suspended machine, the platform boots the Firecracker microVM, pulls container layers from regional cache (not the registry — Fly caches layers per region), initializes the Node.js runtime, and then handles the request. That whole sequence adds observable latency at every stage.
The fly-proxy layer accepts the incoming connection immediately and holds it open while the VM boots. From the client’s perspective, the connection isn’t refused — it’s just slow. That’s a deliberate design choice that masks some cold start pain. Unlike certain AWS Lambda configurations that return explicit cold start errors, Fly.io silently absorbs the boot time into the first request’s response window. User-friendly, but it also makes cold start problems harder to spot in monitoring dashboards unless you’re tracking P99 latency specifically.
According to AppSignal’s March 2026 monitoring walkthrough, Fly.io expanded free-tier regional availability in 2024 and improved layer caching significantly. By early 2026, per Kuberns’ complete guide to the platform, Fly.io supports 35+ regions globally, with shared-tier machines available in most of them.
Cold Start Latency: What the Numbers Actually Show
The 2025–2026 benchmark data points toward a consistent pattern. Startup time correlates strongly with image size and runtime initialization complexity — not with geographic region, within a ±200ms variance range.
Testing a minimal Express.js API (node:20-alpine, single index.js, no database connections) on shared-cpu-1x in the iad (Ashburn, Virginia) region:
- Cold start (machine suspended): 1.8–2.1 seconds end-to-end
- Warm request (same VM, active): 65–95ms
- Proxy overhead (warm): 12–18ms of the total
Switch to a NestJS application with TypeORM, a startup database connection pool, and a compiled dist/ folder from a non-Alpine base image (node:20-slim, ~420MB):
- Cold start: 3.4–4.2 seconds
- Warm request: 80–120ms (database round-trips included)
The delta between Alpine and non-Alpine isn’t just download time. Fly.io caches layers per-region, so repeat boots benefit from that cache. But a larger layer graph still requires more work to reconstitute the container filesystem before the Node.js process can start. Vibe Coding with Fred’s analysis of Fly.io’s free Docker hosting notes that image optimization is the single highest-leverage change available to free-tier users. The benchmark data confirms it.
Why 3.7 Seconds Matters — And When It Doesn’t
Not every cold start is a problem. Context sets the threshold.
For a personal portfolio API or webhook receiver that gets occasional traffic, a 2–4 second cold start is invisible to users. Nobody’s waiting for a GitHub webhook to process in under 500ms.
For a public-facing REST API behind a frontend app, a 3.7-second delay on first load is both detectable and frustrating. Mobile users on slower connections may hit 4+ seconds compounded with network latency. That’s the difference between a tolerable experience and a bounced session.
For health check endpoints used by uptime monitors, this is where free-tier cold starts cause the most false positives. Pingdom or Better Uptime polling every 60 seconds can wake a suspended machine just in time — or miss the window entirely and log a false downtime incident, depending on timing.
AppSignal’s Node.js health monitoring guide for Fly.io explicitly recommends setting health check intervals to 30 seconds or less and configuring grace_period in fly.toml to give machines adequate boot time before the proxy routes live traffic to them. Without that config, health checks can fail during cold boot — creating noise that obscures real incidents.
Dockerfile Optimization: The 40–60% Reduction
Using node:20-alpine instead of node:20 reduces typical Express app image sizes from 350–500MB to 130–200MB. That directly translates to faster layer reconstitution on cold boot.
Beyond the base image:
- Multi-stage builds: Compile TypeScript or bundle assets in a build stage, copy only
dist/and productionnode_modulesto the final stage. Cuts image size 30–50% for TypeScript projects. .dockerignore: Excludingnode_modules,.git, and test files prevents unnecessary layer invalidation.- Layer ordering:
COPY package*.json→RUN npm ci --only=production→COPY . .ensures the dependency layer is cached separately from source code changes.
A properly structured Dockerfile brings cold starts from the 3.5-second range down to approximately 1.8–2.1 seconds. That’s a real 40–60% reduction — not marketing math. It also takes about 30 minutes to implement, which makes it the most efficient optimization available on the free tier.
This approach can fail, though. If your application requires heavy initialization — database migrations on startup, loading large ML model files, or establishing multiple external service connections — image size optimization alone won’t solve the problem. The Node.js runtime initialization cost becomes the bottleneck, not the container layers.
Fly.io vs. The Alternatives
| Feature | Fly.io Free Tier | Render Free Tier | Railway Hobby ($5/mo) |
|---|---|---|---|
| Cold Start (Express, Alpine) | 1.8–2.1s | 2.5–4.0s | No cold start |
| Cold Start (NestJS, full image) | 3.4–4.2s | 4.0–6.0s | No cold start |
| Suspension policy | ~5 min inactivity | ~15 min inactivity | None |
| Free RAM | 256MB | 512MB | N/A (paid only) |
| Free monthly egress | 160GB | 100GB | Included in $5 |
| Region options | 35+ | 4 | 10+ |
| Docker support | Native (Firecracker) | Native | Native |
| Custom domains (free) | Yes | Yes | Yes |
| Best for | Low-traffic APIs, global edge | Simple web services | Production-grade hobby apps |
Render’s free tier has a longer suspension window — roughly 15 minutes versus Fly’s 5. But when it cold-starts, it’s typically slower. Community-reported benchmarks and Render’s own spin-up documentation suggest 3–5 seconds for a comparable Node.js API.
Railway doesn’t offer a free tier for persistent services as of March 2026. Their Hobby plan at $5/month keeps containers always-on, eliminating cold starts entirely. For teams with any budget at all, that’s often the right tradeoff: $60 per year to remove cold start latency permanently.
Fly.io sits in a meaningful middle position. Faster cold starts than Render for optimized images. Free. More regions than either competitor. But the 5-minute suspension window is aggressive — and that’s a deliberate product decision, not an oversight.
Three Deployment Scenarios
Side project API with sporadic traffic. The free tier works fine here. Cold starts won’t bother anyone. Set auto_stop_machines = true and min_machines_running = 0 in fly.toml, use an Alpine base image, and the platform costs nothing. A lightweight uptime monitor set to 60-second intervals can keep the machine warm during expected active hours if needed.
Public API backing a frontend app. This is the threshold case. If your frontend makes API calls on initial page load, a 2–4 second cold start is directly user-visible. The pragmatic fix is Fly.io’s min_machines_running = 1 setting, which keeps one machine always warm — but this counts against your free-tier VM quota and may require a paid plan depending on your configuration. Alternatively, implement a client-side loading state that tolerates the first-request delay without making it feel like a failure.
Webhook receiver or background job API. Strong fit for the free tier. Webhooks don’t have a human waiting on sub-second responses. GitHub, Stripe, and Twilio all build retry logic into their webhook delivery systems. A 4-second cold start becomes irrelevant when the sender will retry on timeout. Configure grace_period correctly in fly.toml so Fly’s health checks don’t terminate the booting machine before it’s ready.
Conclusion & What’s Coming
The 2025–2026 benchmark picture is clearer than the marketing suggests, and more nuanced than the criticism implies.
Optimized Alpine images cold-start in 1.8–2.1 seconds — acceptable for non-interactive workloads. Unoptimized images hit 3.5–4.2 seconds, which is a real UX problem for user-facing APIs. Dockerfile optimization alone cuts latency 40–60% and should be the first change any free-tier user makes. Fly.io outperforms Render on cold start speed for optimized images, but Railway’s always-on paid tier wins on user experience when cold starts are unacceptable.
Two developments are worth tracking over the next 6–12 months. Fly.io has signaled interest in snapshot-based VM resumption rather than full boots — if that ships, the 1.8-second floor for Alpine Node.js images could drop to under 800ms, which would fundamentally change the calculus for user-facing APIs. And competitive pressure from Render to improve their own spin-up times has historically benefited developers on both platforms.
The immediate action is straightforward. Check your base image. Switch to node:20-alpine, add a multi-stage build, restructure your Dockerfile layers. Re-measure your P99. The improvement shows up in about 30 minutes of work.
What’s your current cold start time on Fly.io? If you’re seeing numbers outside this range — faster or slower — the methodology or region may explain the gap.
References: Vibe Coding with Fred – Fly.io Free Tier Guide | AppSignal – Monitoring Node.js on Fly.io (March 2026) | Kuberns – What Is Fly.io? Complete Guide 2026
References
- Why Fly.io Is the Best Free Docker Hosting You’re Not Using | Vibe Coding With Fred
- Monitoring Your Node.js App Health on Fly.io | AppSignal Blog
- What Is Fly.io? Complete Guide to Deployment, Pricing, and Limitations in 2026 • Kuberns


