# 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.

Source: https://boringpush.com/blog/nextjs-oom-build-fix-1gb-vps

## `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.**

```bash
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.**

```bash
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.**

```bash
# 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

- [PM2 Reloads Nobody Feels: Next.js With Zero Downtime](/blog/pm2-zero-downtime-nextjs) — the reload config this memory headroom protects
- [git push production deploy: Skip the 9-Minute CI Queue](/blog/git-push-production-main-deploy) — the hook where the heap flag lives
- [Deploy Next.js on Hetzner $5 by Dinner Tonight](/blog/deploy-nextjs-hetzner-vps) — fresh-box setup, swap included from step one
