Self-hosted n8n under sustained load: 2,880 events at a free-tier VM
A 4h42m soak on a 1 GB Google Cloud e2-micro, measured end to end · run 2026-09-05 · no affiliate links on this page
Nothing was lost. Latency improved. The worst delay of the entire run was caused by the machine's own package manager, and it was invisible to CPU and memory monitoring.
We had published a 10-event burst result, and a reader replied that a burst and hours of elevated load are different animals. That is correct, and it is the better question. So on September 5 2026 we fired one event every 5 seconds for 4 hours and 42 minutes at the self-hosted n8n instance that runs our own benchmark harness: 2,880 events, 136x its normal hourly rate, held continuously, while sampling host telemetry every 15 minutes.
Read the scale honestly before the results. 0.17 events per second is a long duration at a modest rate. This is a test of whether a small box degrades, leaks or drops work over hours, and the answer is no. It is not a throughput benchmark, and it says nothing about 50 events per second. The workflow was our reference WF1: one webhook trigger, one outbound HTTP POST. A heavy multi-step workflow on the same box would be a different measurement.
Result
| Events fired | 2,880 |
| Accepted by n8n | 2,876 |
| Refused at send (loud failure) | 4 |
| Accepted then lost (silent failure) | 0 |
| Silent failure rate | 0 / 2,876 |
The four refusals are not counted in the failure rate, and that exclusion is deliberate. A refused send is a loud failure: our client got an error at the moment of sending, so it knows immediately and can retry. A silent failure is the opposite, an event that was accepted and then never arrived, with nothing anywhere reporting a problem. Mixing the two produces a number that means nothing. The reasoning is on silent failure vs rejected webhook.
Under load it got faster
The result we did not expect. Same workflow, same box, same receiver, compared against normal operation over the preceding 7 days:
| median | p95 | p99 | |
|---|---|---|---|
| Normal operation (7 days, n=260) | 782 ms | 10,577 ms | 16,937 ms |
| Normal operation (30 days, n=1,120) | 822 ms | 10,322 ms | 25,517 ms |
| Under soak (n=2,876) | 610 ms | 1,673 ms | 5,392 ms |
The tail improved by a factor of 6.3 while the load went up by a factor of 136, and the effect holds against 7, 14 and 30 day baselines, so it is not an artefact of stale comparison data.
The likely explanation, stated as an explanation and not a measurement: at roughly 4.5 events per hour this instance is idle almost all the time, and each arriving request pays a cold-path cost. Under a steady 5-second cadence nothing gets a chance to go cold. If that is right, then for low-traffic self-hosted automation the tail you see in production is mostly an idleness cost, not a capacity cost. We have not run the experiment that would confirm it (fire one event after a long idle period, compare), so we are not claiming it as a finding. What we do claim is the comparison in the table.
The worst delay of the run was the machine cleaning its own house
Every 30 minutes of the soak, bucketed. Note the 06:30 row:
| bucket (UTC) | fired | refused | undelivered | p50 | p95 | worst |
|---|---|---|---|---|---|---|
| 04:00 | 253 | 0 | 0 | 521 ms | 1728 ms | 32,831 ms |
| 04:30 | 319 | 0 | 0 | 581 ms | 796 ms | 4,032 ms |
| 05:00 | 308 | 4 | 0 | 608 ms | 782 ms | 13,100 ms |
| 05:30 | 319 | 0 | 0 | 579 ms | 783 ms | 2,445 ms |
| 06:00 | 314 | 0 | 0 | 590 ms | 2174 ms | 5,617 ms |
| 06:30 | 273 | 0 | 0 | 715 ms | 4727 ms | 56,726 ms |
| 07:00 | 312 | 0 | 0 | 658 ms | 1595 ms | 4,778 ms |
| 07:30 | 317 | 0 | 0 | 612 ms | 818 ms | 2,985 ms |
| 08:00 | 289 | 0 | 0 | 680 ms | 2363 ms | 36,194 ms |
| 08:30 | 176 | 0 | 0 | 536 ms | 965 ms | 7,669 ms |
56.7 seconds, 93x the median for that bucket. Our first hypothesis was an n8n container restart, because container memory dropped from 252.5 MiB to 88.3 MiB in one sample, below its value at the start of the run. That hypothesis was wrong. The container reports restarts=0, started 2026-07-12, up 7 weeks, no OOM kill, and the host has been up 54 days. The memory reading fell because the kernel reclaimed page cache, which the container memory figure includes.
What actually happened is in the system journal. At 06:04:38 the machine's own apt-news.service started and activated the PackageKit daemon, which spent about 45 minutes refreshing package metadata before quitting at 06:49:49.
| time (UTC) | %user CPU | %iowait | %idle | dirty pages (kB) |
|---|---|---|---|---|
| 06:10 | 1.65 | 2.66 | 95.05 | 204 |
| 06:20 | 1.46 | 2.03 | 95.93 | 388 |
| 06:30 | 4.09 | 20.20 | 71.54 | 41,316 |
| 06:40 | 4.04 | 34.24 | 57.02 | 524 |
| 06:50 | 1.66 | 29.12 | 68.12 | 336 |
| 07:00 | 1.61 | 3.64 | 93.88 | 664 |
Disk I/O wait went from 2.7 percent to 34.2 percent for about 45 minutes on a shared-core VM with a slow persistent disk, and the request tail went with it. The load average of 2.72 we sampled at 06:36 fits: Linux counts processes blocked in uninterruptible disk wait toward load average, which is why a machine can show 4 percent user CPU and a load of 2.72 at the same time.
On a small free-tier VM, the operating system's own unattended housekeeping is a bigger perturbation than 136x your production traffic. Our load did not produce a single lost event in five hours. Its package manager produced a 56-second request.
And that incident was invisible to CPU and memory monitoring
This is the part worth carrying away. During those 45 minutes:
- CPU utilisation barely moved. User time went from 1.6 percent to 4 percent. The machine looked idle, because by CPU it was idle: it was waiting on disk.
- Free memory went up, not down. The kernel reclaimed page cache under write pressure, so available memory rose from 317 MB to 396 MB. A memory alert would have read this as an improvement.
- Only iowait showed it, and iowait is not on most default dashboards.
A monitoring setup watching CPU and memory would have reported a healthy machine for the entire window in which its p99 was 56 seconds. That is the same shape as the failure class this whole site exists to measure: the instrument was working correctly and reporting on the wrong layer. See uptime vs failure rate for the vendor-facing version of the same problem.
Four refusals we cannot explain, and a blind spot of our own
Four sends were refused, at 05:23:43, 05:23:58, 05:24:41 and 05:24:56 UTC. Sequence numbers 825, 826, 829 and 830 failed while 827 and 828 succeeded between them, so this was flapping, not a clean outage window.
We checked four sources for that moment and all four are quiet:
- CPU: 1.51 percent user, 3.07 percent iowait, 94.69 percent idle
- Network: 5.12 packets/s in, 1.52 kB/s in, flat across the window
- Container memory: 257.9 MiB, steady
- n8n's own container log for 05:20 to 05:30: empty
We do not know why those four were refused. Worse, we made it harder on ourselves: our harness recorded that each send failed but not why, because the error was caught and discarded. Connection reset, timeout and a non-2xx response are indistinguishable in our own ledger for these four events. We have fixed the instrument so the next run records the reason. A harness that measures other people's silent failures should not have a blind spot of its own, and reporting the gap is cheaper than pretending we know.
Method
- Target: self-hosted n8n in Docker on a Google Cloud e2-micro (1 GB RAM, 0.25 vCPU baseline, 2 GB swap), the same instance that serves our production benchmark harness. Free tier, no cost.
- Workload: reference WF1, one Webhook trigger to one outbound HTTP POST.
- Rate: one event every 5,000 ms for 4h42m, 2,880 events total, 0.17 events/s.
- Measurement: each event is written to our ledger before sending; the workflow posts to our receiver, which records arrival. Latency is receiver arrival minus fire timestamp, so it includes n8n processing and both network legs.
- Host telemetry: sampled every 15 minutes during the run; post-hoc attribution from
sar(10-minute resolution) andjournalctl. - Cohort hygiene: every event carries a
WF1soaktag and is excluded from the headline numbers on our reliability and latency pages. A stress test must never share a denominator with normal operation.
Limits of this result
- One box, one config, one workflow shape. Nothing here transfers to a heavy multi-step workflow, a queue-mode deployment, or a different host.
- Duration, not throughput. 0.17 events/s sustained for five hours. We did not test peak rate.
- The cold-path explanation is unconfirmed. The latency comparison is measured; the reason for it is a hypothesis.
- The four refusals are unexplained, and our instrument could not have explained them.
- A single run. One soak on one day. The apt-news window in particular is a scheduled event we happened to catch, not a rate.
Questions
Can a 1 GB free-tier VM run self-hosted n8n?
For a low-volume webhook workload, our measurement says yes with room to spare. We hold a production n8n on a Google Cloud e2-micro (1 GB RAM, 0.25 vCPU baseline, no cost) and it has served our benchmark harness since July 2026. On September 5 2026 we fired 2,880 events at it over 4 hours 42 minutes, one every 5 seconds, which is 136x its normal hourly rate held continuously. It lost nothing: 2,876 of 2,880 sends were accepted and all 2,876 were delivered end to end. Note the honest limit of this result: 0.17 events per second is a long duration at a modest rate, not high throughput. It tells you a small box does not degrade or leak under hours of steady work. It does not tell you what happens at 50 events per second, and it was measured on a single-webhook-to-single-HTTP-call workflow, not a heavy multi-step one.
Does self-hosted n8n get slower under sustained load?
Ours got faster, which surprised us. Across the soak, median latency was 610 ms and the 95th percentile 1,673 ms. Over the same workflow in normal operation during the preceding 7 days, the median was 782 ms and the 95th percentile 10,577 ms. The tail improved by a factor of 6.3 under load. The most likely explanation is that low-traffic self-hosted instances pay a cold-path cost: at roughly 4.5 events per hour the instance is idle almost all the time, and each arriving request wakes something up. Under a steady 5-second cadence nothing gets a chance to go cold. We are labelling that an explanation, not a measurement, because we have not yet run the experiment that would confirm it: fire a single event after a long idle period and compare.
Why is my self-hosted n8n occasionally very slow for no reason?
Check disk I/O wait before you blame n8n or your workflow. The single worst latency in our entire 2,880-event run was 56.7 seconds, and it was not caused by our load. At 06:04 UTC the machine's own apt-news job woke the PackageKit daemon, which spent about 45 minutes refreshing package metadata. Disk I/O wait went from 2.7 percent to 34.2 percent and dirty pages spiked to 41,316 before returning to a few hundred. During that window CPU user time barely moved, from 1.6 percent to 4 percent, and free memory actually increased as the kernel reclaimed page cache. A CPU and memory dashboard would have shown a healthy machine. On a small shared-core VM, the operating system's own unattended housekeeping is a bigger perturbation than 136x your production traffic.
What is the difference between a burst test and a soak test?
A burst test fires a short spike and asks whether the system survives it. A soak test holds a steady rate for hours and asks whether the system degrades, leaks memory, or starts dropping work over time. They fail differently, and passing one says little about the other. We had previously published a 10-event burst result, and a reader pointed out, correctly, that a burst and hours of elevated load are different animals. This page is the soak test that comment asked for.