{"id":2999,"date":"2026-11-12T14:21:09","date_gmt":"2026-11-12T14:21:09","guid":{"rendered":"https:\/\/hostry.com\/blog\/?p=2999"},"modified":"2026-11-12T14:55:00","modified_gmt":"2026-11-12T14:55:00","slug":"n8n-out-of-memory-vps","status":"publish","type":"post","link":"https:\/\/hostry.com\/blog\/n8n-out-of-memory-vps\/","title":{"rendered":"n8n Out of Memory on a VPS: Find Which Process Died and Fix It"},"content":{"rendered":"\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1200\" height=\"675\" src=\"https:\/\/hostry.com\/blog\/wp-content\/uploads\/2026\/11\/n8n-out-of-memory-vps-cover.webp\" alt=\"n8n out of memory on a VPS: a two-room server house where one robot is buried under overflowing boxes and the memory gauge reads red\" class=\"wp-image-2995\" srcset=\"https:\/\/hostry.com\/blog\/wp-content\/uploads\/2026\/11\/n8n-out-of-memory-vps-cover.webp 1200w, https:\/\/hostry.com\/blog\/wp-content\/uploads\/2026\/11\/n8n-out-of-memory-vps-cover-300x169.webp 300w, https:\/\/hostry.com\/blog\/wp-content\/uploads\/2026\/11\/n8n-out-of-memory-vps-cover-1024x576.webp 1024w, https:\/\/hostry.com\/blog\/wp-content\/uploads\/2026\/11\/n8n-out-of-memory-vps-cover-450x253.webp 450w, https:\/\/hostry.com\/blog\/wp-content\/uploads\/2026\/11\/n8n-out-of-memory-vps-cover-768x432.webp 768w\" sizes=\"auto, (max-width: 1200px) 100vw, 1200px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">A fresh n8n 2.47.5 with SQLite and no workflows uses about 530 MiB of RAM in two Node.js processes, <span class=\"highlighted\">n8n start<\/span> (main) and the <span class=\"highlighted\">n8n task-runner<\/span> that executes Code nodes. An n8n out of memory crash means one of them hit its own heap limit or the kernel killed it, and each case has its own fix.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Unless marked otherwise, every status code and log line here was reproduced with n8n 2.47.5 on Node.js 24.22.3, Ubuntu 24.04 (npm install, SQLite, a 2 vCPU \/ 8 GB VM), the default internal task runner and nginx 1.24; Docker was not covered. Match your symptom:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>HTTP 500 <span class=\"highlighted\">{&#8220;message&#8221;:&#8221;Error in workflow&#8221;}<\/span> within seconds, n8n still up, <span class=\"highlighted\">Node ran out of memory<\/span> in its log: <strong><a href=\"#runner-oom\">the task runner&#8217;s heap<\/a><\/strong>.<\/li><li>HTTP 502, <span class=\"highlighted\">upstream prematurely closed connection<\/span> in the nginx error log, executions marked <span class=\"highlighted\">crashed<\/span> after the restart: <strong><a href=\"#main-oom\">the main process&#8217;s heap<\/a><\/strong>.<\/li><li><span class=\"highlighted\">TaskRunnerDisconnectedError<\/span>, or a 502 with nothing in n8n&#8217;s log: <strong><a href=\"#kernel-kill\">a kernel OOM kill<\/a><\/strong>, visible only in <span class=\"highlighted\">dmesg<\/span>.<\/li><li>HTTP 504 after exactly 60 seconds, execution successful: <strong><a href=\"#proxy-timeout\">nginx&#8217;s read timeout<\/a><\/strong>.<\/li><\/ul>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1200\" height=\"800\" src=\"https:\/\/hostry.com\/blog\/wp-content\/uploads\/2026\/11\/n8n-out-of-memory-vps-which-process.webp\" alt=\"n8n out of memory decision diagram: a 500 within seconds means the task runner heap, a 502 with n8n gone means the main heap, TaskRunnerDisconnectedError or a silent 502 means a kernel OOM kill, a 504 after 60 s means the nginx read timeout\" class=\"wp-image-3003\" srcset=\"https:\/\/hostry.com\/blog\/wp-content\/uploads\/2026\/11\/n8n-out-of-memory-vps-which-process.webp 1200w, https:\/\/hostry.com\/blog\/wp-content\/uploads\/2026\/11\/n8n-out-of-memory-vps-which-process-300x200.webp 300w, https:\/\/hostry.com\/blog\/wp-content\/uploads\/2026\/11\/n8n-out-of-memory-vps-which-process-1024x683.webp 1024w, https:\/\/hostry.com\/blog\/wp-content\/uploads\/2026\/11\/n8n-out-of-memory-vps-which-process-450x300.webp 450w, https:\/\/hostry.com\/blog\/wp-content\/uploads\/2026\/11\/n8n-out-of-memory-vps-which-process-768x512.webp 768w\" sizes=\"auto, (max-width: 1200px) 100vw, 1200px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"runner-oom\">HTTP 500 and &#8220;Node ran out of memory&#8221;: n8n out of memory in the task runner<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Code that exhausts the runner&#8217;s heap fails the request with a 500 within seconds; the editor stays up and n8n registers a new runner. A 500 after exactly <span class=\"highlighted\">N8N_RUNNERS_TASK_TIMEOUT<\/span> seconds (300 by default) is the runner&#8217;s other failure, a timeout (<span class=\"highlighted\">Task execution timed out after 15 seconds<\/span> at a setting of 15).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Search n8n&#8217;s log (<span class=\"highlighted\">journalctl -u n8n<\/span> or <span class=\"highlighted\">docker logs<\/span>) for <span class=\"highlighted\">JavaScript heap out of memory<\/span>, which ends both FATAL wordings (<span class=\"highlighted\">Reached heap limit<\/span>, <span class=\"highlighted\">Ineffective mark-compacts near heap limit<\/span>). A runner death adds <span class=\"highlighted\">Node ran out of memory<\/span> and a new <span class=\"highlighted\">Registered runner<\/span> line, with stack frames ending in <span class=\"highlighted\">[n8n task-runner]<\/span>; the <span class=\"highlighted\">[runner:js]<\/span> prefix is not always on the FATAL line. A 50,000,000-row loop at runner heap 128 MB:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>[runner:js] FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory\n[runner:js]  2: 0x75f7fa node::OOMErrorHandler(char const*, v8::OOMDetails const&amp;) [n8n task-runner]\nNode ran out of memory\nWebhook execution failed before a response was sent\nRegistered runner \"JS Task Runner\" (RmIuuu855N4goMgPgIjjD)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The execution stores <span class=\"highlighted\">TaskRunnerOomError: Node ran out of memory<\/span>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1200\" height=\"800\" src=\"https:\/\/hostry.com\/blog\/wp-content\/uploads\/2026\/11\/n8n-out-of-memory-vps-two-heaps.webp\" alt=\"Diagram of n8n memory on a VPS: the main process, limited by NODE_OPTIONS, starts the task runner, limited by N8N_RUNNERS_MAX_OLD_SPACE_SIZE; the OS, database and both heaps must fit in RAM\" class=\"wp-image-3004\" srcset=\"https:\/\/hostry.com\/blog\/wp-content\/uploads\/2026\/11\/n8n-out-of-memory-vps-two-heaps.webp 1200w, https:\/\/hostry.com\/blog\/wp-content\/uploads\/2026\/11\/n8n-out-of-memory-vps-two-heaps-300x200.webp 300w, https:\/\/hostry.com\/blog\/wp-content\/uploads\/2026\/11\/n8n-out-of-memory-vps-two-heaps-1024x683.webp 1024w, https:\/\/hostry.com\/blog\/wp-content\/uploads\/2026\/11\/n8n-out-of-memory-vps-two-heaps-450x300.webp 450w, https:\/\/hostry.com\/blog\/wp-content\/uploads\/2026\/11\/n8n-out-of-memory-vps-two-heaps-768x512.webp 768w\" sizes=\"auto, (max-width: 1200px) 100vw, 1200px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Task runner memory in n8n 2.x has its own limit. Main starts the runner with an allow-list of environment variables that omits NODE_OPTIONS, so <strong>NODE_OPTIONS=&#8211;max-old-space-size limits the main process only<\/strong>; the runner&#8217;s heap comes from <span class=\"highlighted\">N8N_RUNNERS_MAX_OLD_SPACE_SIZE<\/span>, passed on as NODE_OPTIONS, or from Node&#8217;s RAM-based default (2240 MB on the 8 GB VM). The <strong><a href=\"https:\/\/docs.n8n.io\/hosting\/scaling\/memory-errors\/\" target=\"_blank\" rel=\"noopener\">n8n docs page on memory errors<\/a><\/strong> names NODE_OPTIONS alone. A Code node building 1,000,000 rows:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>runner heap 128,  main 384:   {\"message\":\"Error in workflow\"} HTTP 500 in .7630 s\nrunner heap 1024, main 384:   {\"rows\":1000000} HTTP 200 in 1.200 s\nrunner heap 128,  main 2048:  {\"message\":\"Error in workflow\"} HTTP 500<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\" style=\"font-size:14px\"><strong>Fix: raise the runner heap<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Set it in MB in main&#8217;s environment. Both heaps must fit in the RAM left after the OS and database; if not, see <strong><a href=\"#kernel-kill\">the kernel section<\/a><\/strong>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># shell start\nN8N_RUNNERS_MAX_OLD_SPACE_SIZE=1024 n8n start\n\n# systemd: sudo systemctl edit n8n (your unit name may differ), then restart the unit\n[Service]\nEnvironment=N8N_RUNNERS_MAX_OLD_SPACE_SIZE=1024\n\n# Docker env file (--env-file or env_file:), KEY=value, no quotes\nN8N_RUNNERS_MAX_OLD_SPACE_SIZE=1024<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><span class=\"highlighted\">N8N_RUNNERS_MAX_CONCURRENCY<\/span> is 10 on 2.47.5 (the <strong><a href=\"https:\/\/docs.n8n.io\/hosting\/configuration\/environment-variables\/task-runners\/\" target=\"_blank\" rel=\"noopener\">task runner variables page<\/a><\/strong> says 5), all sharing one heap. If a bigger heap will not fit, put a <strong><a href=\"https:\/\/docs.n8n.io\/integrations\/builtin\/core-nodes\/n8n-nodes-base.splitinbatches\/\" target=\"_blank\" rel=\"noopener\">Loop Over Items (Split in Batches)<\/a><\/strong> node before the heavy Code node, as the error&#8217;s hint suggests.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\" style=\"font-size:14px\"><strong>Check<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo cat \/proc\/$(pgrep -f '^n8n task-runner')\/environ | tr '\\0' '\\n' | grep NODE_OPTIONS<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">It prints your value (<span class=\"highlighted\">NODE_OPTIONS=&#8211;max-old-space-size=128<\/span> in the 128 run), or nothing when Node&#8217;s default applies; \/proc reads need root or n8n&#8217;s user.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"main-oom\">502 Bad Gateway and &#8220;JavaScript heap out of memory&#8221;: the main process died<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">When nginx returns 502 mid-request and n8n is gone afterwards, the main process died and took the runner with it. In n8n&#8217;s log, <span class=\"highlighted\">JavaScript heap out of memory<\/span> with frames ending in <span class=\"highlighted\">[n8n start]<\/span> and no <span class=\"highlighted\">Node ran out of memory<\/span> line means main ran out of heap; no error at all points to the next section. Both logs (n8n.example.com stands in for yours):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>nginx: upstream prematurely closed connection while reading response header from upstream, client: 127.0.0.1, server: n8n.example.com, request: \"GET \/webhook\/oom?n=300000&amp;ret=1 HTTP\/1.1\"\nn8n:   FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory\nn8n:    2: 0x75f7fa node::OOMErrorHandler(char const*, v8::OOMDetails const&amp;) [n8n start]<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The next start reports the interrupted run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Marked executions as `crashed`\nFound unfinished executions: 7\nThis could be due to a crash of an active workflow or a restart of n8n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><span class=\"highlighted\">connect() failed (111: Connection refused)<\/span> next to a 502 means nothing listened on port 5678: n8n was stopped, already dead, or in its first 5 seconds of start-up.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">With main at <span class=\"highlighted\">&#8211;max-old-space-size=384<\/span>, a Code node returning 300,000 items crashed it 3.46 s in, while one that built 1,000,000 rows and returned only their count passed: main holds every item a node returns.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\" style=\"font-size:14px\"><strong>Fix: raise the heap, return less<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Raise NODE_OPTIONS and have Code nodes return a summary or only the fields the next node needs. The lowest value that started 2.47.5 was 320; 256 died about 10 seconds into start-up, twice.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># 1024 is an example, not from these runs; both heaps together must fit in RAM (next section)\n# shell start\nNODE_OPTIONS=--max-old-space-size=1024 n8n start\n\n# systemd drop-in\n[Service]\nEnvironment=NODE_OPTIONS=--max-old-space-size=1024\nRestart=on-failure\n\n# Docker env file\nNODE_OPTIONS=--max-old-space-size=1024<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><span class=\"highlighted\">Restart=on-failure<\/span> or a Docker restart policy brings n8n back (not reproduced). A NODE_OPTIONS exported in a shell profile also leaks into n8n.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\" style=\"font-size:14px\"><strong>Check<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo cat \/proc\/$(pgrep -f '^n8n start')\/environ | tr '\\0' '\\n' | grep NODE_OPTIONS\ncurl -s -o \/dev\/null -w '%{http_code}\\n' http:\/\/127.0.0.1:5678\/healthz\/readiness<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Wait for 200 from <span class=\"highlighted\">\/healthz\/readiness<\/span>; plain <span class=\"highlighted\">\/healthz<\/span> answered 200 during migrations, while readiness stayed 503 until the editor was usable (13.8 s into a first start).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"kernel-kill\">TaskRunnerDisconnectedError or a silent 502: the kernel killed the process<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">An n8n out of memory crash with no heap error in the log points to the kernel&#8217;s OOM killer, which ends a process when RAM or a container limit runs out. This section was reproduced inside a 1 GiB memory cgroup, the mechanism behind Docker&#8217;s &#8211;memory, not on a VPS.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo dmesg -T | grep -E 'Killed process [0-9]+ \\(n8n'\nsudo journalctl -k | grep -i 'killed process'     # same, from the journal<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Only the kernel log named the cause<\/strong>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Memory cgroup out of memory: Killed process 44831 (n8n task-runner) total-vm:10473432kB, anon-rss:616416kB, [...]\nMemory cgroup out of memory: Killed process 45598 (n8n start) total-vm:12667724kB, anon-rss:767688kB, [...]<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A machine-wide kill on a VPS reads <span class=\"highlighted\">Out of memory:<\/span> (not reproduced); the grep matches it too.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Killing the runner produced a 500 after 1.77 s and only <span class=\"highlighted\">TaskRunnerDisconnectedError: Node execution failed<\/span>, whose stored hint recommends raising N8N_RUNNERS_MAX_OLD_SPACE_SIZE, the setting behind this kill (2048 inside 1 GiB). A killed main took the runner along; nginx returned 502 after 3.59 s and n8n&#8217;s log stopped.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Node sizes its default heap from the memory limit: 560 MB per process inside 1 GiB. A 5,000,000-row Code node then failed cleanly with <span class=\"highlighted\">Node ran out of memory<\/span> in the lab run, but two 560 MB heaps exceed 1 GiB, so the first large request can be a kernel kill. With a heap above the RAM limit, the kernel kills the process before V8 reports anything.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\" style=\"font-size:14px\"><strong>Fix: fit both heaps in RAM<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If both limits cannot fit in the RAM left after the OS and database (or the container&#8217;s &#8211;memory value), lower them and batch, add swap, or add RAM. Docker&#8217;s docs on <strong><a href=\"https:\/\/docs.docker.com\/engine\/containers\/resource_constraints\/\" target=\"_blank\" rel=\"noopener\">container resource constraints<\/a><\/strong> confirm the kernel kills processes in an out-of-memory container by default.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">What swap fixes and what it does not<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Swap for n8n helps against this kind of kill only. With 2 GB of swap, the request that had killed the runner at heap 2048 returned 200 in 15.9 s, against 3.2 s with no memory limit. At runner heap 128, swap changed nothing (<span class=\"highlighted\">JavaScript heap out of memory<\/span>, 500 after 1.43 s). Swap ran on a virtual disk, so timings will differ.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo fallocate -l 2G \/swapfile\nsudo chmod 600 \/swapfile\nsudo mkswap \/swapfile\nsudo swapon \/swapfile\necho '\/swapfile none swap sw 0 0' | sudo tee -a \/etc\/fstab\nswapon --show<\/code><\/pre>\n\n\n\n\n<h2 class=\"wp-block-heading\" id=\"proxy-timeout\">504 Gateway Time-out after 60 seconds: the nginx read timeout<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Sixty seconds is the default of <strong><a href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_proxy_module.html#proxy_read_timeout\" target=\"_blank\" rel=\"noopener\">nginx&#8217;s proxy_read_timeout<\/a><\/strong>, so a 504 at that mark means nginx stopped waiting while n8n kept working. A Code node sleeping 75 seconds:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>HTTP\/1.1 504 Gateway Time-out   after 60.061679 s\n[error] upstream timed out (110: Connection timed out) while reading response header from upstream, client: 127.0.0.1, server: n8n.example.com, request: \"GET \/webhook\/sleep?s=75 HTTP\/1.1\"\nn8n side: 10|wfSleep0000001|success|2026-11-12 10:32:18.413|2026-11-12 10:33:33.484<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Install the client with <span class=\"highlighted\">sudo apt install sqlite3<\/span>, then look up the execution (<span class=\"highlighted\">success<\/span> means nothing crashed) as n8n&#8217;s own user, whose home holds <span class=\"highlighted\">~\/.n8n<\/span>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sqlite3 -readonly ~\/.n8n\/database.sqlite \"select id, workflowId, status, startedAt, stoppedAt from execution_entity order by id desc limit 5;\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\" style=\"font-size:14px\"><strong>Fix: a longer read timeout<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Raise the timeout where nginx proxies n8n; with 300s the same request returned 200 after 75.06 s. That matches N8N_RUNNERS_TASK_TIMEOUT, the longest a Code node may run by default, a default 2.47.5 warns will drop to 60 s.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>location \/ {\n    proxy_pass http:\/\/127.0.0.1:5678;\n    proxy_http_version 1.1;\n    proxy_set_header Upgrade $http_upgrade;\n    proxy_set_header Connection \"upgrade\";\n    proxy_set_header Host $host;\n    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n    proxy_set_header X-Forwarded-Proto $scheme;\n    proxy_buffering off;\n    proxy_read_timeout 300s;\n    proxy_send_timeout 300s;\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Keep the Upgrade, Connection and Host lines: without the first two the editor&#8217;s <span class=\"highlighted\">\/rest\/push<\/span> websocket got a 401, and without <span class=\"highlighted\">Host $host<\/span> n8n rejected it as an invalid origin (NODE_ENV=production). A 70 s Wait node needs no longer timeout (200 in 0.12 s).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\" style=\"font-size:14px\"><strong>Check<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo nginx -t &amp;&amp; sudo systemctl reload nginx\ncurl -s -o \/dev\/null -w '%{http_code} %{time_total}\\n' https:\/\/n8n.example.com\/webhook\/your-path<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">n8n SQLite database size: database.sqlite does not shrink after pruning<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">When database.sqlite keeps growing while nothing crashes, pruning is freeing space inside the file without shrinking it; on 2.47.5 it shrank only after a WAL checkpoint. Each execution of a two-node webhook workflow added about 4.7 KB.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">By default n8n 2.47.5 saves every execution and prunes those older than 336 hours or beyond the newest 10,000. Here 990 of 1,000 executions were pruned and the database compacted:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>after prune, n8n stopped:          database.sqlite 7540736   page_count=1841 freelist_count=1071\none start with DB_SQLITE_VACUUM_ON_STARTUP=true, then stop:  database.sqlite 7540736   -wal 4202432   page_count=752 freelist_count=0\nn8n stopped, PRAGMA wal_checkpoint(TRUNCATE) -&gt; 0|0|0; then database.sqlite 3084288, no -wal\/-shm files left<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">VACUUM on startup compacted the 1,071 free pages (4.39 MB) that pruning left, yet <span class=\"highlighted\">ls -l<\/span> still showed 7.5 MB across two restarts; only the checkpoint brought it to 3.08 MB.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\" style=\"font-size:14px\"><strong>Fix: store less, then compact once<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Main reads these untested values; EXECUTIONS_DATA_SAVE_ON_SUCCESS=none stops storing successful runs, so the 504 lookup would find nothing.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Docker env file\nEXECUTIONS_DATA_SAVE_ON_SUCCESS=none\nEXECUTIONS_DATA_MAX_AGE=72\nEXECUTIONS_DATA_PRUNE_MAX_COUNT=2000\n\n# systemd drop-in (for a shell start, put the same pairs before n8n start)\n[Service]\nEnvironment=EXECUTIONS_DATA_SAVE_ON_SUCCESS=none EXECUTIONS_DATA_MAX_AGE=72 EXECUTIONS_DATA_PRUNE_MAX_COUNT=2000<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then compact once, in <span class=\"highlighted\">~\/.n8n<\/span>:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>Stop n8n.<\/li><li>Back up (first command).<\/li><li>Start n8n once with <span class=\"highlighted\">DB_SQLITE_VACUUM_ON_STARTUP=true<\/span>.<\/li><li>Stop it after readiness returns 200.<\/li><li>Checkpoint (last command).<\/li><li>Remove the variable; VACUUM rewrites the file on every start.<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>sqlite3 database.sqlite \".backup database-before-vacuum.sqlite\"\n# one start with DB_SQLITE_VACUUM_ON_STARTUP=true, then stop n8n\nsqlite3 database.sqlite \"PRAGMA wal_checkpoint(TRUNCATE);\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\" style=\"font-size:14px\"><strong>Check<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sqlite3 -readonly ~\/.n8n\/database.sqlite \"PRAGMA page_count; PRAGMA freelist_count;\"\nls -l ~\/.n8n\/database.sqlite*<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Page count times the page size (4096 here) is the size after a checkpoint; count the -wal file too. Binary data is on disk as well: 2.47.5 defaults to filesystem mode (database in queue mode) under <span class=\"highlighted\">~\/.n8n\/storage\/workflows\/&lt;workflow-id&gt;\/executions\/&lt;execution-id&gt;\/binary_data\/<\/span>, though the <strong><a href=\"https:\/\/docs.n8n.io\/hosting\/scaling\/binary-data\/\" target=\"_blank\" rel=\"noopener\">binary data docs page<\/a><\/strong> says memory. Pruning deletes those files; <span class=\"highlighted\">du -sh ~\/.n8n\/storage<\/span> shows their size.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">n8n exits on start-up: Node 22, the &#8220;::&#8221; address and old variables<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">n8n 2.36.0 and later need Node.js 24. On Node 22, <span class=\"highlighted\">npm install n8n<\/span> quietly installed 2.35.7, the last release allowing Node 22, and 2.47.5 refused to run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Your Node.js version 22.23.4 is currently not supported by n8n.\nPlease use a Node.js version that satisfies the following version range: &gt;=24.0.0<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Install Node.js 24 first (check <span class=\"highlighted\">node &#8211;version<\/span>), then pin with <span class=\"highlighted\">npm install -g n8n@2.47.5<\/span>, after adding swap on a 1 GB server. On Node 22 the pin still succeeds with EBADENGINE warnings, and n8n then refuses to start.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The default listen address is <span class=\"highlighted\">::<\/span>, and on a host without IPv6 n8n exited after 4.5 s:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>n8n's address '::' is not available. Please run n8n with a different address, provide correct address in the environment variables N8N_LISTEN_ADDRESS and\/or N8N_WORKER_SERVER_ADDRESS.<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Set <span class=\"highlighted\">N8N_LISTEN_ADDRESS=127.0.0.1<\/span> for the main process, and create the owner account before nginx or an open port exposes n8n, because a never-configured instance gives that role to whoever finishes setup first.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Delete <span class=\"highlighted\">EXECUTIONS_PROCESS=own<\/span>, left over from pre-1.0 guides: 2.47.5 exits with code 255 right after migrations and prints nothing.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"ram-requirements\">n8n RAM requirements: what 2.47.5 uses per process at idle<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Idle n8n 2.47.5 needs about 530 MiB, and main briefly needs about 730 MiB while starting (<span class=\"highlighted\">\/proc\/&lt;pid&gt;\/smaps_rollup<\/span> after two minutes idle, three starts; peak from <span class=\"highlighted\">VmHWM<\/span>, seven starts).<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Process (as ps shows it)<\/th><th>What runs there<\/th><th>Heap limit set by<\/th><th>RSS idle<\/th><th>PSS idle<\/th><th>Peak RSS at start-up<\/th><\/tr><\/thead><tbody><tr><td>n8n start<\/td><td>Editor, REST API, webhooks, scheduler, execution data<\/td><td>NODE_OPTIONS<\/td><td>470-480 MiB<\/td><td>439-449 MiB<\/td><td>712-735 MiB<\/td><\/tr><tr><td>n8n task-runner<\/td><td>JavaScript Code nodes<\/td><td>N8N_RUNNERS_MAX_OLD_SPACE_SIZE<\/td><td>116-117 MiB<\/td><td>88-89 MiB<\/td><td>148-151 MiB<\/td><\/tr><tr><td>Both together<\/td><td><\/td><td><\/td><td><\/td><td>527-538 MiB<\/td><td><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">PSS splits shared pages between the two, so its sum is the real total. PostgreSQL 16 in place of SQLite added 54-71 MB PSS of its own. To measure yours:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo sh -c 'for p in $(pgrep n8n); do echo \"$p $(grep \"^Pss:\" \/proc\/$p\/smaps_rollup)\"; done'\ngrep VmHWM \/proc\/$(pgrep -f '^n8n start')\/status<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Our reading of the n8n VPS requirements: idle n8n fits in 1 GB, but <span class=\"highlighted\">npm install n8n@2.47.5<\/span> peaked at 1.73 GB RSS (2.8 GB of disk), so 1 GB needs swap to install. We would start at 2 GB with SQLite and plan 4 GB if Code nodes hold millions of rows like the test&#8217;s: 5,000,000 rows of an integer and a string of about 76 characters, returned only as a count, took the runner to 1.4 GiB. If an AI agent shares the server, add its RAM from <strong><a href=\"https:\/\/hostry.com\/blog\/hermes-agent-vs-openclaw-vps\/\">the Hermes Agent and OpenClaw comparison<\/a><\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The entry-level <strong><a href=\"https:\/\/hostry.com\/products\/vps\/\">Hostry KVM VPS<\/a><\/strong>, $6.00 a month for 1 vCPU, 1 GB RAM, NVMe storage and full root access, matches the 1 GiB limit of the kernel-kill runs, where two default heaps can outgrow RAM on one large Code node. On that plan, cap both heaps (main no lower than 320) or scale the VPS up; plans go to 32 GB RAM.<\/p>\n<!-- AddThis Advanced Settings generic via filter on the_content --><!-- AddThis Share Buttons generic via filter on the_content -->","protected":false},"excerpt":{"rendered":"<p>n8n 2.47.5 idles at about 530 MiB across two processes with separate heap limits. The status code and one log line show whether the task runner, the m&#8230;<\/p>\n","protected":false},"author":6,"featured_media":2996,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1,78,20],"tags":[],"class_list":["post-2999","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-virtual-private-server","category-apps","category-system-admin"],"read_time":9,"_links":{"self":[{"href":"https:\/\/hostry.com\/blog\/wp-json\/wp\/v2\/posts\/2999","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/hostry.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/hostry.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/hostry.com\/blog\/wp-json\/wp\/v2\/users\/6"}],"replies":[{"embeddable":true,"href":"https:\/\/hostry.com\/blog\/wp-json\/wp\/v2\/comments?post=2999"}],"version-history":[{"count":2,"href":"https:\/\/hostry.com\/blog\/wp-json\/wp\/v2\/posts\/2999\/revisions"}],"predecessor-version":[{"id":3009,"href":"https:\/\/hostry.com\/blog\/wp-json\/wp\/v2\/posts\/2999\/revisions\/3009"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/hostry.com\/blog\/wp-json\/wp\/v2\/media\/2996"}],"wp:attachment":[{"href":"https:\/\/hostry.com\/blog\/wp-json\/wp\/v2\/media?parent=2999"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/hostry.com\/blog\/wp-json\/wp\/v2\/categories?post=2999"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/hostry.com\/blog\/wp-json\/wp\/v2\/tags?post=2999"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}