deploy · tutorial · vps

Next.js 'Killed' on 1GB VPS? The 5-Minute Swapfile Fix

Next build dies with Killed on tiny boxes because linking eats 1.5GB RAM. A 2GB swapfile fixes it in five minutes. Commands inside.

A Studebaker car loaded far beyond capacity with trunks and baskets, Queensland, 1915
A Studebaker car loaded far beyond capacity with trunks and baskets, Queensland, 1915 · Photo: State Library of Queensland, no known copyright restrictions, via Wikimedia Commons

Killed with no other explanation

You push to the $5 box, the build spins through "Compiled successfully," starts "Collecting page data," and dies mid-word:

Killed

No stack trace. No error code. Just Killed — the kernel's OOM assassin ending your build because RAM ran out.

Locally on your 16GB Mac it never happens. On 1GB of VPS it happens every time past a dozen routes.

The nextjs oom fix vps playbook is three moves: confirm it is memory, add swap, cap the hungry processes. In that order, because guessing wastes afternoons.

Where 1GB actually goes

Consumer Idle RAM Build-time spike
OS + sshd + daemons ~150 MB ~150 MB
PM2 app serving traffic ~250 MB ~250 MB (still live!)
next build workers 0 600–900 MB at linking
Node default heap per worker — Up to 2 GB allowed
Total vs 1 GB box ~400 MB fine ~1.4 GB — OOM killer fires

Three details make it bite harder than the table suggests:

  1. The app stays up during the build. PM2 keeps serving users while next build runs beside it — noble, and 250MB you cannot borrow. The spike lands on top of live traffic, not an empty box.
  2. Linking parallelizes against you. Next spawns a worker per core. Two vCPUs each holding hundreds of MB of module graph peaks exactly at "Collecting page data" — the step where every log of this death ends.
  3. Bigger boxes mask it until the bill lands. The standard advice is "upgrade to 2GB," which doubles the server cost forever to cover a 90-second spike. Swap covers the spike for $0.

The kernel is not wrong to kill. Something had to die, and the build was the hungriest. Give it somewhere harmless to overflow.

Confirm, swap, cap

These are the same steps BoringPush's setup runs on every fresh CX22.

Step 1 — Confirm the killer, not a bug.

dmesg | tail -20 | grep -i "oom\|killed process"
free -h

You want a line like Out of memory: Killed process <pid> (node). If it names node during next build, this post is your fix. If it names your database or PM2 at idle, you have a leak instead — read pm2 monit first and fix the leak before adding swap.

Step 2 — Add the 2GB swapfile.

sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
sudo sysctl vm.swappiness=10

fallocate reserves the space instantly, 600 keeps the page contents private, fstab survives reboots, and swappiness=10 tells the kernel to prefer RAM and touch swap only for spikes — exactly our build burst. Verify with free -h: the Swap row reads 2.0G. Total time: under five minutes, no reboot.

Step 3 — Cap the heap so builds stay polite.

# in the deploy hook, before npm run build:
export NODE_OPTIONS="--max-old-space-size=1536"
npm run build

1.5GB of heap across 1GB RAM + 2GB swap leaves the live app untouched while linking peaks. Add output: "standalone" in next.config.js so the runtime server carries only traced files — smaller boot, smaller resident memory, faster reloads. The build gets slower by a minute (swap is disk, not RAM); the site never notices.

That's the play — even the smallest box builds clean.

Before and after on 1GB

Check Before swap After swap + cap
npm run build, 20 routes Killed at page data Completes in ~3 min
free -h during build 50 MB free, then death 800 MB swap used, alive
Live traffic during build Dead with the builder Served — PM2 untouched
Monthly cost delta $0 but broken $0 and green

The honest trade: swap is NVMe wearing per page-write, and a box that lives in swap (not spikes into it) is a box that needs RAM. Watch vmstat 5 during a normal hour — if si/so columns stay busy outside builds, upgrade to 2GB instead of leaning on disk.

Swap is a shock absorber, not a second engine. And when traffic truly outgrows the small box, the same repo moves up one plan with zero code changes.

FAQ

Is a nextjs oom fix vps swapfile safe for an SSD?

Yes for this pattern. Build spikes page a few hundred MB for ~90 seconds per deploy — trivial wear on modern NVMe rated for hundreds of terabytes written.

What kills SSDs is constant swap churn from an undersized box running hot 24/7. Spike-only swap is the intended use; chronic swap is the upgrade signal.

Why not just set NODE_OPTIONS and skip the swapfile?

The heap cap alone shrinks Node's appetite but the box still has only 1GB against a live app plus two build workers. Capping without swap turns Killed into slower Killed.

Swap without capping works but pages aggressively. Together they hold: the cap bounds the spike, swap absorbs it.

Does output: standalone change how I deploy?

Slightly, for the better: next build emits .next/standalone/server.js plus traced node_modules, and PM2 starts that file instead of the full install. Smaller boot image, less resident RAM, fewer native-module surprises. The deploy needs one path change, which the BoringPush deployment guide documents.

How do I know the OOM killer struck versus a normal crash?

dmesg names it explicitly: Killed process with oom_score context, timestamped to your build. Normal crashes leave JS stack traces; the OOM killer leaves one word and a kernel timestamp. Match the timestamp to your push and the diagnosis is certain in thirty seconds.

Will two PM2 instances plus a build fit on 1GB at all?

Yes, with this exact combination: 2GB swap, 1536MB heap cap, standalone output, builds scheduled off-peak. It is the tightest viable shape — comfortable, not roomy. The moment you add a second service (workers, search), take the 2GB plan.

BoringPush runs 2 instances on 1GB in dev and 2–4 on 2GB in prod for exactly this reason.

When should I stop fixing and just buy RAM?

When vmstat shows swap activity outside build windows, or reloads start timing out under normal load. RAM is $2–4/mo per GB at Hetzner-class pricing — cheaper than any hour you spend tuning. The swapfile buys you months of growth; it was never meant to buy years.

Keep reading

+ all the boring stuff (analytics, auth, SQLite ↔ Supabase, full marketplace, webhooks)

The Agent-First Shipping Kit

Command from your phone.

Built so autonomous AI agents build what you want without breaking what's already paying you.

$199

Get BoringPush

One-time payment · Lifetime updates · Unlimited projects.

Keep reading