Helm7

Self-hosting Next.js: what works and what needs setting up

Almost everything in Next.js works when you self-host with next start: ISR, on-demand revalidation, image optimisation, Proxy, streaming and after() all work on a single instance with no configuration. The work starts when you run more than one instance, where the cache, tag invalidation, the server actions key and the deployment id all have to be shared.

Checked against current documentation on 30 September 2026.

On one instance, it just works

Self-hosted feature support on a single next start instance, per the Next.js 16.3.7 self-hosting guide, read on 30 September 2026
FeatureWorks self-hostedNotes
ISR and revalidatePathYesThe cache is kept on local disk
Image optimisationYesMay need memory tuning on glibc Linux
Proxy (middleware)YesNot with a static export
Streaming and SuspenseYesEvery proxy and load balancer in front must not buffer
after()YesNeeds a graceful shutdown to finish
Cache ComponentsYesWorks with next start and in a Docker container

ISR is the one people worry about, and on a single server it needs nothing. Caching and revalidating pages use the same Next.js server cache, which is stored on the local file system of each instance by default. Pages are regenerated in the background and written there. If that disk is thrown away on each deploy, as it is in a container without a volume, pages are simply rebuilt on their next request. ISR explained for self-hosters walks through this.

The docs add a warning: if you run multiple instances, use ephemeral compute, or put a CDN or reverse proxy in front, you should also read the sections on configuring caching and on multi-instance coordination. The rest of this guide is those sections, in order.

Reverse proxy and image optimisation

The docs recommend a reverse proxy such as nginx in front of the Next.js server, not the server on the open internet. The proxy takes malformed requests, slow-connection attacks, payload limits and rate limiting, so Next.js can spend its time rendering.

Image optimisation through next/image works with no configuration under next start. On glibc-based Linux the sharp library may need a different memory allocator, or memory use climbs. If you would rather use another service, set an image loader, or turn optimisation off with the unoptimized prop and keep the other benefits of next/image.

Proxy needs no setup either. If you need every Node.js API in it, the docs suggest moving the logic into a layout as a Server Component, or using redirects and rewrites in next.config.

Streaming needs every hop to pass it through

Streamed responses arrive as chunks. If a reverse proxy, load balancer or CDN buffers the response, the reader gets it all at once at the end, which looks like a slow page rather than an error. The Next.js docs show nginx being told to stop buffering by having Next.js send an X-Accel-Buffering header set to no.

// next.config.js
module.exports = {
  async headers() {
    return [
      { source: '/:path*{/}?',
        headers: [{ key: 'X-Accel-Buffering', value: 'no' }] },
    ]
  },
}
Tell nginx not to buffer Next.js responses, as shown in the Next.js self-hosting guide

Then check every layer. The docs list load balancers, which must support chunked transfer encoding or HTTP/2 streaming, and reverse proxies between the balancer and Next.js. They note that some cloud load balancers buffer by default. If you use Partial Prerendering, streaming is required, or the static shell and dynamic content arrive together and the speed-up is lost.

Shut down gracefully

When a deploy replaces the server, send SIGTERM or SIGINT and wait. Next.js finishes requests in flight and runs pending after() callbacks before it exits. The docs recommend a drain period of 10 to 30 seconds. Killing it sooner drops work a user was already told had succeeded.

Two things to check. Start Next.js directly, not through a shell script that does not pass signals on, or it never sees SIGTERM. And look at your container tool's stop timeout, and set it to at least the drain period you want.

More than one instance

“Other instances continue serving stale content until they independently discover the invalidation.”
Next.js documentation, Self-hosting guide, version 16.3.7

Each instance keeps its own cache by default, so two instances can serve two different versions of the same page, and calling revalidateTag on one leaves the others stale. A multi-instance deployment needs these things.

  1. 1A shared cache handlerSet cacheHandler in next.config to a handler backed by shared storage such as Redis or S3, and set cacheMaxMemorySize to 0 to turn off the in-memory cache. Next.js publishes a Redis example.
  2. 2cacheHandlers for use cacheThe use cache directive has its own, separate setting, cacheHandlers, plural. For shared data the docs point to use cache: remote with a handler that stores data in external storage. Setting one does not configure the other.
  3. 3Tag coordinationImplement refreshTags in your cacheHandlers handler. Next.js calls it before each request, and it should read tag state from shared storage so a revalidateTag on one instance reaches the rest.
  4. 4One server actions keySet NEXT_SERVER_ACTIONS_ENCRYPTION_KEY to the same base64 value for every instance. It must be a valid AES length of 16, 24 or 32 bytes.
  5. 5A deployment idSet deploymentId in next.config so a browser holding an old page is detected during a rolling deploy and does a full reload instead of a broken client-side navigation.
# Make one key, keep it in your secrets store, use it at build time
export NEXT_SERVER_ACTIONS_ENCRYPTION_KEY=$(openssl rand -base64 32)
npm run build
A 32-byte base64 key. openssl is one way to make it; any generator that gives valid base64 of the right length works.

Edge case: the key is embedded in the build output, and the docs show it set when you run next build, so setting it only when you start the servers is not the documented route. Build once and start every instance from that same build. If you rebuild for each environment, also set generateBuildId to something stable such as a git hash, so all containers agree on the build id. When deploymentId is set, Next.js uses a constant build id and generateBuildId has no effect.

What goes wrong without each setting, from the Next.js 16.3.7 self-hosting guide
Missing settingWhat you will see
Shared cacheHandlerTwo instances serve different versions of one page
refreshTagsrevalidateTag on one instance leaves the others stale
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY"Failed to find Server Action" errors when a request lands on another instance
deploymentIdMissing assets, server function mismatches and failed navigation during rolling deploys

Checking it before you rely on it

  1. 1Look at the headersRun curl -sI on a static page and a dynamic one. The static page should say Cache-Control public and the dynamic one private, no-store.
  2. 2Watch a stream arriveRun curl -N on a route with a Suspense boundary. The first chunk should arrive well before the last. If everything lands together, something is buffering.
  3. 3Revalidate on one instanceWith two instances, call revalidateTag once and request the page from each. If one still shows old content, tag coordination is missing.
  4. 4Restart under loadSend SIGTERM while a slow request is running. It should finish, and an after() callback should still run. See the Docker hosting explainer for how containers stop.

Putting a CDN in front

A CDN works, with rules. Pages that use dynamic APIs are sent with Cache-Control private, so the CDN will not store them. Fully static pages are sent with Cache-Control public. Immutable, hashed assets get a one-year immutable header that cannot be overridden. ISR pages use s-maxage with stale-while-revalidate, so your CDN or proxy must respect those directives and vary its cache on the right keys, or you can get stale or mismatched pages during client-side navigation.

To serve assets from another domain, set assetPrefix. The cost is extra DNS and TLS time for the second host.

How Helm7 runs Next.js

Helm7 runs each environment as one instance, which is the case where everything above works with no configuration. It builds the repository in a container, starts the new version, waits until it answers, moves traffic to it, and gives the old one 30 seconds to finish before stopping it. Multi-instance deployments with a shared cache are planned and not offered yet. If you need that today, a Docker host you control, or another platform, is the honest answer. The platform overview shows what is included, and the CLI docs show how to deploy.

Common questions

  • Do I need Vercel for ISR?

    No. ISR works with next start on any server. It needs extra configuration only when several instances must share one cache.

  • Why is my streamed page arriving all at once?

    Something between the browser and Next.js is buffering the response: usually Nginx, a load balancer or a CDN. Each layer has to pass chunked responses through. For nginx, send an X-Accel-Buffering header set to no.

  • What is NEXT_SERVER_ACTIONS_ENCRYPTION_KEY for?

    Next.js encrypts server function closure variables with a key it generates for each build. When several instances serve one app they need the same key, set through this variable at build time, or actions fail with a "Failed to find Server Action" error when a request lands on a different instance.

  • How long should I wait before stopping the old server?

    The Next.js docs recommend a drain period of 10 to 30 seconds. Send SIGTERM or SIGINT and wait, so in-flight requests and pending after() callbacks can finish.

  • Does the ISR cache survive a redeploy?

    Only if its disk does. The cache is on local disk by default, so a container without a volume starts empty and rebuilds pages as they are requested.

  • What are cacheHandler and cacheHandlers?

    cacheHandler configures the cache used for ISR and route caching. cacheHandlers, plural, configures backends for the use cache directive. They are separate settings, and a multi-instance app may need both.

Run your entire app stack, end to end, from $12 a month.

App, Postgres, auth, storage, jobs and domains on one platform, planned and priced before anything changes.

More guides