Stop n8n From Hogging Your Shared VPS: Docker Limits Guide

If n8n shares a VPS with WordPress, a database, or anything else you care about, give the n8n container a hard memory ceiling and a fractional CPU cap in your Compose file, then leave at least 20% of the server's RAM free for the host. On a 4 GB VPS running one other busy site, mem_limit: 1.5g plus cpus: '1.0' on n8n is a sensible starting point, paired with NODE_OPTIONS=--max-old-space-size=1024 inside the container so Node's heap stays below the Docker limit and n8n throws a clean error instead of getting killed by the kernel. Without a limit, Docker lets a container use every byte the host has, so one workflow pulling a 200 MB API response can trigger the Linux OOM killer, which may take out MySQL rather than n8n.

The fix takes about 15 minutes and one container restart.

Time needed: 15 to 25 minutes. Difficulty: intermediate. You'll need SSH access, Docker with the Compose plugin, and permission to restart your n8n container.

Why an unlimited n8n container is the biggest risk on a shared VPS

By default, a Docker container has no memory limit and no CPU limit. It competes with every other process on the box as an equal, and n8n is not a polite neighbour under load.

The reason is how n8n moves data. Every item between nodes lives in memory as JSON, and unless you tell it otherwise, binary data from HTTP requests, downloads and email attachments also sits in RAM. A single workflow that fetches 5,000 rows, loops over them, and attaches a PDF to each can climb from 180 MB to well over 1 GB in a few seconds.

That spike is short. It's still long enough to matter.

When Linux runs out of memory, the kernel OOM killer picks a process to kill based on its own scoring, not on what you'd prefer to lose. On a box running n8n next to a WordPress stack, MySQL is a frequent casualty because it holds a large resident memory footprint. Your automation survives.

Your site returns a database connection error. That mismatch is the whole argument for setting limits: you decide which service degrades.

CPU is the quieter problem. n8n is single-threaded per execution, but a Code node with a bad loop, or ten webhooks firing at once, will happily eat every core. Nothing crashes. Everything gets slow at the same time, and you spend an hour reading logs before you notice the pattern.

If you've ever chased down a site that crawls for no clear reason, an unlimited container on the same host is worth ruling out early.

With a limit in place, the behaviour changes in a useful way. Docker kills only the container that broke its ceiling. The exit code is 137, docker inspect reports OOMKilled: true, and a restart policy brings n8n back within seconds.

Everything else on the VPS never notices.

Note: memory limits protect the host from the container. They don't protect the container from itself. A limit that's too tight turns occasional slowdowns into constant restart loops, which is worse for reliability than no limit at all.

How much CPU and RAM n8n actually needs before you set a ceiling

Base n8n with SQLite, sitting idle, typically holds somewhere around 150 to 400 MB of RAM depending on version and how many workflows are loaded. Executions are what push it up. The numbers below are practical starting points for a shared VPS, not vendor minimums, and you should adjust them after watching real usage for a week.

Your setup n8n memory limit n8n CPU limit Free RAM to leave for host
2 GB VPS, n8n plus one small site 768m to 1g 0.75 ~500 MB
4 GB VPS, n8n plus WordPress and MySQL 1.5g 1.0 ~800 MB
8 GB VPS, n8n plus Postgres and two sites 2.5g to 3g 1.5 ~1.5 GB
8 GB VPS, n8n in queue mode (main + 2 workers) 1g main, 1.5g per worker 0.5 main, 1.0 per worker ~1.5 GB

Three things drive the number more than workflow count does. Payload size comes first: a workflow handling 50 items behaves nothing like one handling 50,000. Concurrency comes second, because two parallel executions roughly double peak memory.

Binary data comes third, and it's the one that catches people out, since a 40 MB file pulled through three nodes can be held three times over.

Can you run n8n on a 1 GB VPS?

You can, for light personal use: a handful of scheduled workflows, small payloads, SQLite, no queue mode. Set the limit to about 700m, add a 1 GB swap file, and expect occasional slow executions. What you can't reliably do on 1 GB is run n8n alongside a WordPress site and MySQL.

There isn't enough headroom for either to spike, and both will restart on you. If that's your situation, the honest answer is a bigger plan, and the signals that a shared plan has run out of room apply just as much to an undersized VPS.

Does the CPU limit need to match core count?

No. The cpus value is a fraction of total CPU time, not a core count you must own. cpus: '1.5' on a 2 vCPU box means n8n can use up to 150% of one core's worth of time, spread across both. Setting it below your total core count is the point: it guarantees the other services keep a slice.

How to apply Docker resource limits to n8n step by step

These steps use Docker Compose, since that's how most self-hosted n8n installs run. Back up your data volume before you touch anything.

  1. SSH into the VPS and check current usage first. Run docker stats --no-stream. Note the MEM USAGE / LIMIT column for n8n. If the limit shows your total host RAM, the container has no cap and you're working blind.

  2. Watch a real execution before choosing a number. Run docker stats n8n in one terminal and trigger your heaviest workflow manually. Note the peak. Your limit should be roughly 1.5 times that peak, never equal to it.

  3. Open your Compose file. It's usually docker-compose.yml in the directory you started n8n from. Copy it first: cp docker-compose.yml docker-compose.yml.bak.

  4. Add the memory and CPU keys to the n8n service. Put mem_limit, mem_reservation, cpus and pids_limit at the same indent level as image and ports. The full example is in the next section.

  5. Set memswap_limit to the same value as mem_limit. This stops the container from spilling into host swap. Swapping n8n to disk turns a fast failure into a slow one that drags the whole VPS down with it.

  6. Add a Node heap cap in the environment block. Set NODE_OPTIONS=--max-old-space-size=1024 for a 1.5 GB limit, or roughly 65 to 70% of your container limit in MB. This matters more than the Docker limit alone, because Node will then garbage collect hard and throw a JavaScript heap error rather than being SIGKILLed mid-write.

  7. Set a restart policy and log rotation. Add restart: unless-stopped so a killed container comes back, and cap the JSON log driver so it can't fill the disk. Warning: without max-size on the logging driver, a crash loop writes gigabytes of identical error lines and fills the root partition, which breaks every service on the VPS including the ones you were protecting.

  8. Recreate the container. Run docker compose up -d. Compose detects the changed config and recreates n8n. Resource limits can't be applied to a running container through Compose, so a brief restart is unavoidable; expect 10 to 30 seconds of downtime, and any execution in flight will fail.

  9. Re-run your heaviest workflow. If it completes and peak memory sits comfortably under the ceiling, you're done. If it dies at exactly your limit, raise the limit by 25% rather than removing it.

If you need to change a limit later without editing files, docker update --memory 2g --memory-swap 2g n8n works on a running container. Keep the Compose file in sync afterwards, or your next docker compose up silently reverts it.

What the Compose file looks like for a single n8n container and for queue mode

Two setups, two different allocation strategies. Pick the one that matches how you run n8n.

Single container on a 4 GB VPS shared with a website

services:
  n8n:
    image: docker.n8n.io/n8nio/n8n
    restart: unless-stopped
    ports:
      - "5678:5678"
    mem_limit: 1.5g
    mem_reservation: 512m
    memswap_limit: 1.5g
    cpus: '1.0'
    pids_limit: 256
    environment:
      - NODE_OPTIONS=--max-old-space-size=1024
      - N8N_DEFAULT_BINARY_DATA_MODE=filesystem
      - N8N_CONCURRENCY_PRODUCTION_LIMIT=5
      - GENERIC_TIMEZONE=America/New_York
    volumes:
      - n8n_data:/home/node/.n8n
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

volumes:
  n8n_data:

mem_reservation is a soft floor. Docker tries to keep 512 MB available to n8n when the host is under pressure, but it makes no guarantee. Treat it as a hint, not a contract.

If you prefer the deploy block syntax you'll see in n8n community threads, Compose v2 honours deploy.resources.limits outside Swarm as well:

    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 1.5G
        reservations:
          memory: 512M

Both work. Don't use both at once in the same service, because it gets confusing to audit later, and mixed syntax is exactly the kind of thing that trips you up six months on. Keeping a short note of your chosen values pays off, and there's a case for treating your own setup notes as a real deliverable rather than a scratch file.

Queue mode with workers on an 8 GB VPS

Queue mode splits n8n into a main instance that handles the UI and webhooks, plus one or more workers that run executions. This is the better shape for a shared VPS, because the memory-hungry part is isolated in a container you can cap aggressively without breaking the editor.

The allocation logic changes. The main instance stays small, around 1g, since it isn't processing data. Each worker gets the larger limit, because that's where payloads live.

Redis needs 128m to 256m. If you've moved off SQLite, Postgres wants its own reservation, typically 512m to 1g on a box this size.

  n8n-worker:
    image: docker.n8n.io/n8nio/n8n
    restart: unless-stopped
    command: worker --concurrency=5
    mem_limit: 1.5g
    memswap_limit: 1.5g
    cpus: '1.0'
    environment:
      - EXECUTIONS_MODE=queue
      - NODE_OPTIONS=--max-old-space-size=1024
      - QUEUE_BULL_REDIS_HOST=redis

Add up every limit before you deploy. On 8 GB, 1g main plus two 1.5g workers plus 256m Redis plus 1g Postgres comes to 5.25 GB, which leaves a reasonable cushion for the OS, your web server and page cache. Overcommitting is allowed by Docker and punished by the kernel, so the arithmetic is worth doing on paper.

Worker concurrency defaults to 10 per worker, which is far too high for a shared box. Dropping it to 5, or even 3 for heavy workflows, cuts peak memory more effectively than any Docker flag.

Note: two workers at 5 concurrency each is not the same as one worker at 10. Two containers give you two independent memory ceilings, so one bad execution can only kill half your capacity.

The n8n settings that do the other half of the job

Docker limits contain the damage. These settings reduce how often it happens, and they're where the real gains are.

Move binary data off the heap. N8N_DEFAULT_BINARY_DATA_MODE=filesystem writes files to disk under the n8n data directory instead of holding them in RAM. If you process attachments, images or PDFs at all, this single change often halves peak memory. It needs a persistent volume, and it uses disk instead, so keep an eye on space. n8n also supports S3-compatible object storage on newer versions if disk is tight.

Knowing what file sizes your host is comfortable with helps here, because filesystem mode moves the pressure from RAM to storage.

Cap incoming payloads. N8N_PAYLOAD_SIZE_MAX sets the maximum request body size in MiB and defaults to 16. Raising it to 100 or 200 to accommodate one large webhook also hands any misbehaving caller the ability to push 200 MB into your container's memory. Leave it low unless you have a specific need.

Limit production concurrency. N8N_CONCURRENCY_PRODUCTION_LIMIT caps how many production executions run at the same time on a main instance. The default is no limit (-1). Setting it to 5 on a shared VPS queues the rest instead of running them all simultaneously, which flattens the memory curve considerably.

Prune execution history. n8n prunes old execution data by default in current versions, controlled by EXECUTIONS_DATA_PRUNE, with EXECUTIONS_DATA_MAX_AGE in hours (336, or 14 days, is the documented default) and a max-count cap. Defaults have shifted between releases, so confirm yours against the self-hosting environment variable reference rather than assuming. Unpruned history bloats SQLite, and a bloated database slows every workflow list load.

Stop saving successful execution data if you don't read it. EXECUTIONS_DATA_SAVE_ON_SUCCESS=none cuts database writes and disk growth sharply. Keep error data. That's the part you actually open.

Use task runners where available. Recent n8n versions run Code node JavaScript in a separate task runner process, enabled with N8N_RUNNERS_ENABLED=true, with its own heap setting. That moves the most common source of memory blowups out of the main process. Check the current docs for the exact variable names on your version, since this area has changed quickly.

Rotate logs and set N8N_LOG_LEVEL to warn or error in production. Debug logging on a busy instance generates a surprising amount of disk I/O, and I/O contention on a shared VPS is felt by every service.

One more thing worth doing before any of it: take a snapshot or a full backup. Tightening a memory limit is reversible, but a half-migrated binary data directory is not fun to unpick. Most VPS providers include automatic backups, and it's worth knowing how far back your restore points go before you start changing container config.

Things people get wrong about Docker limits and n8n

Mix-up: Setting mem_limit alone protects the container from crashing.

Reality: It does the opposite for the container. The limit tells Docker when to kill n8n. Without a matching NODE_OPTIONS heap cap, Node has no idea a ceiling exists and gets SIGKILLed with no chance to finish writing to the database.

Pair the two, always.

Mix-up: mem_reservation guarantees n8n that much memory.

Reality: It's a soft target the kernel considers under pressure. Nothing is held aside. If your other services have already consumed the RAM, the reservation does not claw it back.

Mix-up: Adding swap fixes n8n memory problems on a small VPS.

Reality: Swap stops a hard crash and replaces it with disk thrashing. A workflow that swaps can take minutes instead of seconds, and the I/O load slows the whole host. Swap is a safety net for rare spikes, not capacity you can plan around.

That's why memswap_limit should match mem_limit for the n8n container specifically.

Mix-up: CPU limits stop workflows from timing out.

Reality: A tighter CPU cap makes executions take longer, so it can cause timeouts rather than prevent them. If webhooks are timing out, look at concurrency and payload size before touching cpus.

What to do when the limits cause new problems

Problem: The n8n container keeps restarting, and docker logs ends abruptly with no error.

Problem: You see WARNING: No swap limit support when starting Compose.

Fix: The host kernel doesn't have swap accounting enabled, so memswap_limit is ignored while mem_limit still works. On most Ubuntu and Debian VPS images you can enable it by adding cgroup_enable=memory swapaccount=1 to the GRUB kernel line and rebooting. On many small VPS instances there's no swap configured at all, in which case you can leave the warning alone.

Problem: A workflow now fails with JavaScript heap out of memory where it used to complete.

Fix: That's the Node heap cap doing its job, which is a better failure than a kernel kill. Either raise --max-old-space-size (and the container limit above it), or restructure the workflow to batch items using a Loop Over Items node so fewer records sit in memory at once.

Problem: n8n is fine, but your website slowed down after the change.

Fix: Check docker stats for the other containers. If your web stack is now hitting its own ceiling, you've simply moved the bottleneck. Total limits across all containers should sit at roughly 80% of host RAM, not 100%.

What this means for your setup, and when limits stop being enough

There's a point where tuning stops helping. If n8n needs more than about half your total RAM to run its normal workload, you're not managing resources any more, you're rationing them. Queue mode with a properly capped worker buys you real headroom, and it's the right move before adding RAM.

Beyond that, more memory is the answer, and the official Docker guidance on runtime resource constraints is worth a read if you want the full set of cgroup options.

A couple of adjacent things matter once n8n is stable. Webhooks need a proper hostname with valid HTTPS, so sorting out a certificate for the endpoint is part of the same job, and most people run n8n on a dedicated subdomain rather than a folder so the reverse proxy config stays clean.

If your VPS is genuinely too small for the workload, that's worth solving properly rather than shaving another 200 MB off a limit. Hostinger's KVM VPS plans give you full root access and enough RAM to run n8n next to a real site, and you can check current VPS pricing with the code hHostCouponHub for up to 85% off before you commit to a term.

Frequently asked questions

What does exit code 137 mean for an n8n Docker container?

How much RAM should I give the n8n container on a 2 GB VPS?

Around 768 MB to 1 GB, with NODE_OPTIONS=--max-old-space-size=640 to 700 and N8N_DEFAULT_BINARY_DATA_MODE=filesystem set. That leaves roughly 500 MB for the operating system, Docker itself and anything else running. Keep concurrency at 2 or 3 and avoid large file processing on a box this size.

Do Docker Compose resource limits work without Docker Swarm?

Yes. The service-level keys mem_limit, memswap_limit, cpus and pids_limit work with plain docker compose up, and Compose v2 also applies deploy.resources.limits outside Swarm. Only deploy.replicas and a handful of other deploy keys need Swarm mode.

Will setting a CPU limit slow my n8n workflows down?

Only when they're actively competing for CPU. A limit of cpus: '1.0' has no effect while n8n is using less than one core's worth of time, which is most of the day. Under load, executions run slower rather than starving your other services, which is the trade you're making on purpose.

Set the limits, run your heaviest workflow, and check docker stats one more time before you walk away; that five-minute verification is what turns a config change into an actual fix. And if the numbers tell you the box is simply too small, grab a VPS with room to breathe using code hHostCouponHub rather than fighting the ceiling every week.

This page contains affiliate links. If you purchase through the links on this page, we may earn a commission, at no extra cost to you.

Leave a Comment