Skip to content
For the complete Netlify documentation index, see llms.txt. Markdown versions of any documentation page are available by appending .md to its URL.

Lifecycle

Your server runs as instances, which are running copies of your app that Netlify starts, stops, and replaces for you. This page describes what happens to an instance from when it starts until it stops, and how deploys move traffic from one version of your server to the next.

Each instance serves many requests at the same time, and keeps memory, connections, and caches between them. Time a request spends waiting, such as on a database query or a streaming model response, doesn't block the other requests the instance is handling.

Netlify scales the number of instances automatically, based on demand. It starts more as traffic grows, up to a limit, sends each new request to the instance with the most room, and stops instances as traffic falls, down to none when nothing is using your server. How much room an instance has depends on how busy it is.

  1. A request arrives and one instance serves the traffic
  2. Traffic grows and a second instance starts
  3. A third instance starts as traffic keeps growing
  4. Traffic falls and the extra instances stop
  5. With no traffic, the last instance stops

In-memory state belongs to a single instance and is lost when that instance stops, and so is anything it writes to its local disk. Use them for connection pools, caches, warm clients, and temporary files, and use Netlify Blobs or Netlify Database for anything that must be durable.

An instance stops when it has gone a while without requests, when a newer deploy replaces it, and after it has been running for a while, even if it's busy. Refer to Limits for how long each of these takes.

More than one instance of your server can be running at a time, even if traffic never makes it scale up. During a deploy, the new deploy's instances start while the previous deploy's instances finish their work. Instances are also replaced at least every 8 hours, whether or not you deploy, so any instance can stop while another one takes over.

To make sure your server works through these changes:

  • Treat in-memory data as a cache rather than the source of truth. Anything that must last or be shared between instances belongs in Netlify Database or Netlify Blobs.
  • Don't assume your server is the only instance running. Work that should happen once, such as sending a daily report, can run on two instances at the same time, or on neither while one stops and the next starts. Use a scheduled function for scheduled work, and record what's been done in a database so another instance doesn't repeat it.
  • Don't assume an instance keeps running. Save the progress of long-running work so another instance can pick it up.

Netlify judges how busy each instance is with Node.js runtime metrics, such as event loop utilization, rather than only generic CPU usage. Your code runs on a single JavaScript thread, so an instance can be too busy to take more work while its CPU usage looks low, and the event loop shows that where CPU usage doesn't. Netlify also tracks each instance's memory.

Your code can add custom metrics for resources only it knows about, such as a database connection pool or a job queue. Report how busy they are with an onHealthCheck hook, and Netlify weighs them alongside its own.

An instance is as busy as its busiest resource. When that resource is close to its capacity, the instance stops receiving new requests while other instances can take them, and Netlify starts more instances when every one of them is busy. A busy instance keeps running and serving the requests it already has.

  1. New requests go to the instance with the most room
  2. One instance's event loop gets busy, so new requests go to the other
  3. A custom metric your code reports shows the other is busy too (onHealthCheck)
  4. Every instance is busy, so Netlify starts another
  5. As load falls, every instance takes new requests again
  6. Traffic eases, and the extra instance stops

A hook reports load, not whether your server is healthy, so an instance is never stopped or replaced because of what a hook reports.

Load-aware routing chooses between instances, so it takes effect whenever your server runs more than one.

When your server isn't receiving traffic, Netlify stops its instances, so nothing runs until the next request arrives. A server with occasional traffic doesn't keep an idle process running, which keeps it cost-efficient.

  1. A request arrives and an instance starts
  2. Start hooks run (onStart)
  3. The instance serves requests
  4. No requests for 5 minutes
  5. Shutdown hooks run (onShutdown)
  6. The instance stops until the next request

An instance stops after 5 minutes without requests. The timer starts over every time a request finishes, and doesn't run while the instance has any of the following:

  • Requests in progress.
  • Open WebSocket and Server-Sent Events connections.
  • Background work passed to waitUntil.

Work you start any other way, such as a timer created with setInterval, doesn't keep the instance running. Before the instance stops, it shuts down like any other, so your shutdown hooks run.

When a request arrives and no instance is running, Netlify starts a new one, and the request waits for your server to start. This includes loading your entry file and running your start hooks, so the request takes longer than one served by a running instance. The time it takes counts toward the 20 seconds your server has to send the response's headers.

To keep that wait short:

  • Limit your entry file's top-level code and your start hooks to what your server needs before its first request. Set up services or data that only some routes use when a request first needs them.
  • Keep your server's dependencies small, since they're loaded every time an instance starts.

A production deploy's server is already running when the deploy goes live, so visitors don't wait for it after you ship. Refer to Deploys.

When an instance starts, Netlify loads your entry file, which runs its top-level code. If that code uses top-level await, loading waits for it. Netlify then runs your start hooks, if you registered any, and finds the server to send requests to, depending on what your entry file exports. The instance receives no traffic until all of this has finished.

If any of it throws an error or exception, the instance fails to start.

When an instance is being stopped, for example because you deployed a new version or because traffic dropped, Netlify stops sending it new requests and runs the following steps, in order:

  1. Delivers a SIGTERM signal to the process, so that unmodified apps and libraries that handle the signal for graceful shutdown work without any changes.
  2. Stops your server, if your entry file exports one or starts one, from accepting new connections.
  3. Starts your shutdown hooks, which run in parallel.
  4. Waits for the requests in progress, including open WebSocket and Server-Sent Events connections, and for background work to finish, for up to 90 seconds.
  5. Waits for your shutdown hooks to finish, for up to 115 seconds from when they started.
  6. Closes any connections that are still open, and stops the instance.
  1. Two instances serve traffic
  2. One instance starts shutting down, and new requests go to the other (SIGTERM)
  3. Shutdown hooks run while requests in progress keep going (onShutdown)
  4. The requests finish, so the hooks close what they used (drained)
  5. The instance stops
  6. The other instance serves all traffic

Requests and background work that take longer than 90 seconds are cut off, so a client running a long request should be ready to retry it.

Your shutdown hooks run alongside that work. Use them to explicitly stop work that would otherwise keep running, such as open WebSocket connections, so the instance doesn't wait for it. Then await drained, which resolves once the work in progress has finished, before closing the resources it uses. This leaves your hooks at least 25 seconds after the work ends:

import { getWebSockets, onShutdown } from "@netlify/server";
onShutdown(async ({ drained }) => {
for (const socket of getWebSockets()) {
socket.close(1012, "Server restarting");
}
await drained;
await pool.end();
});

If your code handles SIGTERM and then exits the process, for example with server.close(() => process.exit(0)), background work and shutdown hooks that are still running are cut off. Netlify stops the instance once that work has finished, so we recommend that you don't call process.exit() yourself.

To do work after you respond, such as sending an email, recording analytics, or continuing an agent loop that runs for several minutes, pass its promise to waitUntil on the request's Context. The response goes out right away, and Netlify keeps the instance running until the promise settles, rather than stopping it for inactivity while the work is still going on. Background work isn't limited by the request duration.

netlify/server/index.ts
import type { Context } from "@netlify/server";
export default async (req: Request, context: Context) => {
const { pathname } = new URL(req.url);
if (req.method === "POST" && pathname === "/api/orders") {
const order = await saveOrder(await req.json());
context.waitUntil(sendConfirmationEmail(order));
return Response.json(order, { status: 201 });
}
return new Response("Not found", { status: 404 });
};

A handler or a Fetchable can also call waitUntil on the context it receives as its second argument, as in the Handler example. If the promise rejects, the error is logged, and your server keeps running.

Work you start without waitUntil is invisible to Netlify, so an instance with no requests in progress can be stopped while that work is still running.

When an instance is replaced, whether because of a new deploy or a scaling event, Netlify gives background work in progress up to 90 seconds to finish as part of its shutdown. For critical work where you expect longer background tasks, we recommend persisting it to Netlify Database or Netlify Blobs, so that another instance of your server can pick it up.

Every deploy runs the server that was deployed with it, and rolling back a deploy rolls back its server too.

When you publish a deploy to production, Netlify starts its server alongside the one that's currently serving, and waits until an instance of the new server is ready, including any start hooks. Only then does it publish the deploy and move traffic to it:

  • If the new server fails to start, the deploy isn't published, and production keeps running the previous version.
  • Once traffic has moved, the previous deploy's instances are stopped gracefully.
  • The new server is already running when the deploy goes live, so the first visitors after a deploy don't wait for it to start.

For a short time, instances of both deploys are running at once. Refer to Multiple instances.

Starts

  1. The current deploy's server serves traffic
  2. You publish a new deploy, and its server starts
  3. Once it's ready, traffic moves to the new server
  4. The previous server finishes its requests and shuts down
  5. The new deploy's server serves all traffic

Fails to start

  1. The current deploy's server serves traffic
  2. You publish a new deploy, and its server starts
  3. The new server fails to start
  4. The deploy isn't published, and the current server keeps serving traffic

If neither your server's code nor its configuration has changed since the previous deploy, publishing skips this step and keeps the running server.

Branch deploys and Deploy Previews start their server on the first request they receive, so your backend is part of every preview without affecting production.