“n8n is slow” is four different problems that look identical from the editor: the UI lags, executions take longer than they used to, and eventually something times out.
Adding queue mode is the popular answer and it is usually wrong. Queue mode fixes exactly one of the four. The other three are configuration you already have, set to a default that does not suit you.
Work through them in this order - ordered by how often each is actually the cause, not by how interesting it is.
Why Is n8n Slow?
Usually not for the reason the internet tells you. Most guides say “turn on execution pruning,” but n8n enables pruning by default and has for a long time. The real causes, in order, are SQLite running in the slower journal mode, binary data held in memory, a Node heap larger than the machine, and only then genuine throughput limits.
Let us take the pruning myth off the table first, because it wastes the most time.
Is My Execution History the Problem?
Probably not by itself. n8n’s docs are explicit that “n8n enables pruning by default”, with EXECUTIONS_DATA_MAX_AGE defaulting to 336 hours (14 days) and EXECUTIONS_DATA_PRUNE_MAX_COUNT to 10,000 executions. If your database is enormous anyway, the interesting question is why pruning did not shrink it.
EXECUTIONS_DATA_PRUNE=true # default: true
EXECUTIONS_DATA_MAX_AGE=336 # hours. default: 336 = 14 days
EXECUTIONS_DATA_PRUNE_MAX_COUNT=10000 # default: 10000, 0 = no limit
Note MAX_AGE is in hours. EXECUTIONS_DATA_MAX_AGE=30 is thirty hours, not thirty days, and that is a real way people accidentally destroy their own history.
Three reasons a database stays huge with pruning working correctly:
- SQLite does not give the space back. From n8n’s docs: “If you run n8n using the default SQLite database, the disk space of any pruned data isn’t automatically freed up but rather reused for future executions data.” The rows are gone; the file is not smaller. Fix it with
DB_SQLITE_VACUUM_ON_STARTUP=trueor a manualVACUUM- and note the docs’ warning that this “is a long running blocking operation and increases start-up time.” - Some executions are never pruned. Per the docs, executions with
new,runningorwaitingstatus are not eligible, and “annotated executions (for example, executions with tags or ratings) are never pruned.” A workflow leaving hundreds of executions stuck inwaitingaccumulates forever. - Binary data. If files are stored in the database rather than on disk, the row size is the problem, not the row count. That is the next section.
How to check: look at the size of ~/.n8n/database.sqlite. A healthy instance is tens of megabytes. Over a gigabyte and you have one of the three above.
Cause 1: SQLite Is in the Slow Journal Mode
This is the highest-value single setting on a self-hosted n8n and almost nobody sets it. DB_SQLITE_POOL_SIZE defaults to 0, which means rollback-journal mode. n8n’s own docs say “WAL mode is much more performant and reliable than the rollback journal mode.” Setting it above zero switches to WAL.
DB_SQLITE_POOL_SIZE=4
The docs describe the value as “the number of parallel SQL read connections to configure.” A laggy editor on an otherwise idle box is very often this: the editor’s queries and the executor’s writes are serialising against each other in journal mode.
If you change one thing after reading this post, change this one. Take a backup first - it is a database mode switch, and n8n backups are their own topic.
To be straight about it: we do not set this on InstaPods pods either. Our n8n image sets the binary-data mode and the heap cap, not DB_SQLITE_POOL_SIZE, so a pod of ours is in rollback-journal mode until you change it. That is a gap on our side rather than a recommendation, and it is one line in a systemd drop-in on any pod.
Cause 2: Binary Data Going Through Memory
By default n8n keeps binary data in memory. The docs say so plainly: “When handling binary data, n8n keeps the data in memory by default. This can cause crashes when working with large files.” Setting N8N_DEFAULT_BINARY_DATA_MODE=filesystem writes it to disk instead.
N8N_DEFAULT_BINARY_DATA_MODE=filesystem
The default value is literally default, and default means memory. Valid alternatives are filesystem, database, s3 and azure, with the last two gated behind self-hosted Business and Enterprise plans.
If you have ever watched n8n get OOM-killed on a workflow that downloads an attachment, this is why. On a single self-hosted instance filesystem has essentially no downside, and it is what InstaPods sets in the shipped n8n unit.
One trap if you are heading for queue mode: n8n’s docs state it “doesn’t support queue mode with binary data storage in filesystem,” and point you at S3 external storage instead. So the fix for cause 2 and the fix for cause 4 conflict, which is another reason not to jump to queue mode early.
Cause 3: The Node Heap Is Bigger Than the Machine
n8n runs on Node, and V8 sizes its heap from the machine’s total RAM. In a container or VM with a memory limit, V8 does not see the limit - it sees the host, sizes accordingly, grows past the cap, and you either get OOM-killed or you thrash swap. Swapping is much slower than being killed and much harder to diagnose.
This is the failure that looks least like a memory problem. Nothing crashes. The instance just gets progressively, mysteriously slower under load.
Cap the heap explicitly, below the container’s limit:
NODE_OPTIONS=--max-old-space-size=1300 # for a 2 GB limit
Around 65% of the memory limit is a reasonable cap. It leaves headroom for Node’s non-heap memory, child processes such as n8n’s task runner, and the OS. The point is to make garbage collection reclaim before the cgroup wall rather than after it.
On InstaPods this is derived rather than hardcoded: n8n launches through a wrapper that reads the pod’s actual memory limit at startup and sets --max-old-space-size to 65% of it, so the cap follows a plan upgrade or downgrade on the next restart. This exact failure once wedged one of our own hosts, which is why the wrapper exists.
Cause 4: You Genuinely Need More Throughput
Only now is queue mode the answer. If WAL is on, binary data is on disk, the heap is capped, and executions are still queueing behind each other, you have a real throughput ceiling and workers are how you raise it.
For scale, n8n’s benchmarking docs claim “n8n can handle up to 220 workflow executions per second on a single instance, with the ability to scale up further by adding more instances” - measured on a 4 GB AWS instance running in main mode against Postgres. Most self-hosters are nowhere near that.
Be honest about which problem you have:
| Symptom | Queue mode helps? |
|---|---|
| One workflow takes 40s because it calls a slow API | No. It waits the same 40s on a worker. |
| Editor is laggy, database is 4 GB | No. That is WAL mode and VACUUM. |
| n8n gets OOM-killed on file-heavy workflows | No. That is binary data mode and the heap cap. |
| 200 executions fire at once and pile up behind each other | Yes. |
| You want executions spread across more than one machine | Yes. |
What Is n8n Queue Mode?
In queue mode n8n passes the execution ID to Redis, which holds the queue of pending executions, and separate worker processes pull and run them. You scale by adding workers. Redis is required, and PostgreSQL is required in practice.
EXECUTIONS_MODE=queue # default: regular
QUEUE_BULL_REDIS_HOST=redis # default: localhost
QUEUE_BULL_REDIS_PORT=6379
QUEUE_BULL_REDIS_PASSWORD=...
Then start workers as separate processes:
n8n worker --concurrency=10
--concurrency defaults to 10, and the docs add a warning worth heeding: “n8n recommends setting concurrency to 5 or higher for your worker instances. Setting low concurrency values with a large numbers of workers can exhaust your database’s connection pool, leading to processing delays and failures.”
Four things that bite people setting this up:
- Every worker needs the same encryption key. The docs are direct: “The encryption key of the main n8n instance must be shared with all worker and webhooks processor nodes to ensure these worker nodes are able to access credentials stored in the database.” Get this wrong and nodes fail with errors that never mention keys. See the n8n encryption key.
- SQLite is out. The docs say running queue mode on SQLite “isn’t recommended,” and elsewhere that a distributed setup “over SQLite isn’t supported.” Multi-main requires Postgres outright. Migrate the database first, then add workers.
- Filesystem binary data is out too. As above - queue mode wants S3 for binary data.
- You now run three things instead of one. n8n, Redis, and N workers, each failing independently. That is the real cost, and it is why queue mode should not be step one.
On InstaPods, PostgreSQL and Redis are both one-click adds - but be clear on what that means: they install onto the same pod, listening on localhost, not as a separate managed instance you point at. So you are adding two daemons to the same 2 vCPU and 2 GB, and you should size up before you do it.
Two honest caveats before anyone follows this path on a pod of ours. First, our daily backup captures n8n’s own data directory and nothing else, so moving n8n’s database into an in-pod PostgreSQL takes your workflows out of the backup set - that database becomes yours to back up. Second, our image sets filesystem binary-data mode, which as noted above queue mode does not support, so that has to change too. Configuring queue mode is your work either way: we install the database and the queue, we do not orchestrate a worker fleet.
Do I Need Queue Mode?
Almost certainly not. It is for instances where concurrent execution volume is the binding constraint. One team running dozens or low hundreds of executions a day will hit their upstream APIs’ limits long before they hit a single n8n process’s.
The threshold is not a workflow count, it is whether executions wait on each other. If the executions list shows things sitting in running or waiting while the CPU is pinned, that is a throughput problem. If the CPU is idle and things are still slow, it is one of causes 1 through 3, and queue mode adds moving parts without adding speed.
Related: n8n does not limit concurrent production executions in regular mode at all. N8N_CONCURRENCY_PRODUCTION_LIMIT defaults to -1, and the docs note it applies “only to production executions: those started from a webhook or trigger node.” Setting it to a real number is sometimes a better first move than queue mode, because it turns an overloaded instance into a slower-but-alive one.
The Diagnosis Checklist
Run these on the machine, in order, and stop at the first thing that is wrong:
# 1. How big is the database, and is space being reclaimed?
ls -lh ~/.n8n/database.sqlite
# 2. Is SQLite in WAL mode?
systemctl show n8n -p Environment | tr ' ' '\n' | grep DB_SQLITE
# 3. Where does binary data go, and what is the heap cap?
systemctl show n8n -p Environment | tr ' ' '\n' | grep -E 'BINARY|NODE_OPTIONS'
cat /sys/fs/cgroup/memory.max
# 4. Is the box busy, or is it swapping?
top -bn1 | head -5
Step 4 separates “needs more CPU” from “needs less memory pressure.” High load with low CPU usage is swap, and swap is cause 3.
Sizing, Not Slowness
If the checklist comes back clean and n8n is still slow, you may simply be on a box that is too small. Different question, own answer: how much RAM and CPU n8n actually needs.
FAQ
Why is the n8n editor slow but executions are fine?
Usually SQLite in journal mode. The editor queries the executions table constantly; the workflows themselves mostly do not. Turn on WAL with DB_SQLITE_POOL_SIZE.
Does n8n need PostgreSQL instead of SQLite?
Not for a single instance. SQLite in WAL mode is fine for one n8n process. You need PostgreSQL when you move to queue mode with multiple workers, and note that n8n 2.x dropped MySQL and MariaDB - sqlite and postgresdb are the options now.
Will more CPU make n8n faster? Only if the CPU is what is busy. Most n8n workflows are waiting on HTTP requests, where more cores change nothing. Check before you upgrade.
How much execution history should I keep? The 14-day default covers almost every real debugging need. For longer audit retention, ship what you need out of the workflow rather than holding it in n8n’s database.
Does restarting n8n help? It clears accumulated memory pressure, so things improve for a while and then degrade again. If restarting helps, that points hard at cause 2 or cause 3.
Want the binary-data mode and heap cap already right? Deploy n8n on InstaPods - $7/mo, 2 vCPU, 2 GB RAM, with those two defaults set in the image. The SQLite WAL setting above is still yours to make, on our pods as much as anywhere. Or price the DIY route against seven other hosts.