<figure class="wp-block-image aligncenter"><img src="https://flowgenius.in/wp-content/uploads/2026/01/missing-api-rate-limiting-dos.png" alt="Step by Step Guide to solve missing api rate limiting dos" /> <figcaption style="text-align: center;">Step by Step Guide to solve missing api rate limiting dos</p>
<hr />
</figcaption></figure>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>Who this is for:</strong> DevOps engineers, self‑hosted n8n operators, and security‑focused developers who need to protect a publicly reachable n8n instance. <strong>We cover this in detail in the </strong><a href="https://flowgenius.in/n8n-security-errors-guide/">n8n Security & Hardening Guide.</a></p>
<div style="margin: 55px 0;">
<hr />
</div>
<h2 style="margin-bottom: 45px; line-height: 1.3;">Quick diagnosis</h2>
<p style="margin-bottom: 2em; line-height: 1.9;">The n8n API is exposed without request‑throttling by default. An attacker can flood <code>/rest/*</code> endpoints (including workflow execution) with thousands of calls per second, exhausting CPU, memory, and database connections → denial‑of‑service (DoS).</p>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>Featured‑snippet solution:</strong> Add a reverse‑proxy (NGINX or Traefik) in front of n8n and configure a <code>rate‑limit</code> rule of <strong>10 r/s</strong> per IP (adjust to your traffic). Reload the proxy and verify that the API returns <strong>429 Too Many Requests</strong> when the limit is exceeded.</p>
<div style="margin: 55px 0;">
<hr />
</div>
<h2 style="margin-bottom: 45px; line-height: 1.3;">Why n8n needs API rate limiting ?</h2>
<p>If you encounter any <a href="/docker-container-misconfigurations">docker container misconfigurations </a>resolve them before continuing with the setup.</p>
<table style="border-collapse: collapse; width: 100%; margin-bottom: 2em;">
<thead>
<tr>
<th style="border: 1px solid #ddd; padding: 13px; text-align: left;">Threat</th>
<th style="border: 1px solid #ddd; padding: 13px; text-align: left;">Typical impact on n8n</th>
</tr>
</thead>
<tbody>
<tr>
<td style="border: 1px solid #ddd; padding: 13px;">Brute‑force credential guessing</td>
<td style="border: 1px solid #ddd; padding: 13px;">Exhausts login endpoint, locks out legitimate users</td>
</tr>
<tr>
<td style="border: 1px solid #ddd; padding: 13px;">Workflow execution flood</td>
<td style="border: 1px solid #ddd; padding: 13px;">Spikes CPU, DB connections, queue overflow</td>
</tr>
<tr>
<td style="border: 1px solid #ddd; padding: 13px;">Mass webhook abuse</td>
<td style="border: 1px solid #ddd; padding: 13px;">Saturates inbound webhook handler, drops legitimate events</td>
</tr>
<tr>
<td style="border: 1px solid #ddd; padding: 13px;">Scraping / data exfiltration</td>
<td style="border: 1px solid #ddd; padding: 13px;">Large read‑only queries starve the DB</td>
</tr>
</tbody>
</table>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>Why a limit stops it</strong> – Caps the number of requests an IP can make, forcing attackers to slow down while preserving resources for genuine traffic.</p>
<blockquote style="margin: 0 0 2em 0; padding-left: 1em; border-left: 4px solid #ddd; font-style: italic;">
<p style="margin: 0; line-height: 1.9;"><strong>EEFA note:</strong> Do not set the limit too low (e.g., 1 r/s) in production; you’ll block legitimate integrations. Start with a conservative baseline (10 r/s) and tune based on observed traffic patterns.</p>
</blockquote>
<div style="margin: 55px 0;">
<hr />
</div>
<h2 style="margin-bottom: 45px; line-height: 1.3;">Choosing the right rate‑limiting layer</h2>
<table style="border-collapse: collapse; width: 100%; margin-bottom: 2em;">
<thead>
<tr>
<th style="border: 1px solid #ddd; padding: 13px; text-align: left;">Layer</th>
<th style="border: 1px solid #ddd; padding: 13px; text-align: left;">Pros</th>
</tr>
</thead>
<tbody>
<tr>
<td style="border: 1px solid #ddd; padding: 13px;"><strong>NGINX (or Apache) reverse proxy</strong></td>
<td style="border: 1px solid #ddd; padding: 13px;">Mature <code>limit_req</code> module, easy per‑IP limits, can be combined with <code>limit_req_zone</code> for burst handling</td>
</tr>
<tr>
<td style="border: 1px solid #ddd; padding: 13px;"><strong>Traefik (Docker‑Compose)</strong></td>
<td style="border: 1px solid #ddd; padding: 13px;">Native middleware, dynamic config via labels, integrates with Docker Swarm/K8s</td>
</tr>
<tr>
<td style="border: 1px solid #ddd; padding: 13px;"><strong>Cloudflare / API‑gateway (e.g., AWS API GW)</strong></td>
<td style="border: 1px solid #ddd; padding: 13px;">Global edge protection, bot‑management, analytics</td>
</tr>
<tr>
<td style="border: 1px solid #ddd; padding: 13px;"><strong>n8n custom middleware (Node.js)</strong></td>
<td style="border: 1px solid #ddd; padding: 13px;">Fine‑grained per‑workflow limits</td>
</tr>
</tbody>
</table>
<table style="border-collapse: collapse; width: 100%; margin-bottom: 2em;">
<thead>
<tr>
<th style="border: 1px solid #ddd; padding: 13px; text-align: left;">Cons</th>
<th style="border: 1px solid #ddd; padding: 13px; text-align: left;">Typical use‑case</th>
</tr>
</thead>
<tbody>
<tr>
<td style="border: 1px solid #ddd; padding: 13px;">Requires separate container or host install</td>
<td style="border: 1px solid #ddd; padding: 13px;">Small‑to‑medium self‑hosted n8n</td>
</tr>
<tr>
<td style="border: 1px solid #ddd; padding: 13px;">Limited to HTTP‑level limits (no per‑method granularity)</td>
<td style="border: 1px solid #ddd; padding: 13px;">Container‑first deployments</td>
</tr>
<tr>
<td style="border: 1px solid #ddd; padding: 13px;">Extra cost, adds latency, needs DNS change</td>
<td style="border: 1px solid #ddd; padding: 13px;">Public‑facing n8n instances</td>
</tr>
<tr>
<td style="border: 1px solid #ddd; padding: 13px;">Requires code change, not officially supported</td>
<td style="border: 1px solid #ddd; padding: 13px;">Highly customized SaaS offering</td>
</tr>
</tbody>
</table>
<blockquote style="margin: 0 0 2em 0; padding-left: 1em; border-left: 4px solid #ddd; font-style: italic;">
<p style="margin: 0; line-height: 1.9;"><strong>EEFA tip:</strong> When using a reverse proxy, place the rate‑limiting rule <strong>before</strong> any authentication middleware so malicious traffic is blocked early. If you encounter any <a href="/rbac-pitfalls">rbac pitfalls </a>resolve them before continuing with the setup.</p>
</blockquote>
<div style="margin: 55px 0;">
<hr />
</div>
<h2 style="margin-bottom: 45px; line-height: 1.3;">Implementing rate limiting with NGINX</h2>
<h3 style="margin-bottom: 45px; line-height: 1.3;">Step 1 – Define a shared memory zone to store request counters.</h3>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;"># /etc/nginx/conf.d/rate_limit.conf
limit_req_zone $binary_remote_addr zone=n8n_api:10m rate=10r/s;</pre>
<h3 style="margin-bottom: 45px; line-height: 1.3;">Step 2 – Proxy configuration (basic reverse‑proxy settings).</h3>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;">location / {
proxy_pass http://n8n:5678;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}</pre>
<h3 style="margin-bottom: 45px; line-height: 1.3;">Step 3 – Apply the rate‑limit rule to the same location.</h3>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;"># Inside the same location block
limit_req zone=n8n_api burst=20 nodelay;
limit_req_status 429; # Return 429 Too Many Requests</pre>
<h3 style="margin-bottom: 45px; line-height: 1.3;">Step 4 – Reload NGINX without downtime</h3>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;">docker exec n8n-nginx nginx -s reload # Docker
# or
sudo systemctl reload nginx</pre>
<h3 style="margin-bottom: 45px; line-height: 1.3;">Step 5 – Verify the limit with a quick curl loop.</h3>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;">for i in {1..30}; do
curl -s -o /dev/null -w "%{http_code} " http://n8n.example.com/rest/workflows;
done</pre>
<p style="margin-bottom: 2em; line-height: 1.9;">You should see a series of <code>200</code> responses followed by <code>429</code> once the burst limit is reached. If you encounter any <a href="/missing-audit-logging-breach-detection">missing audit logging breach detection </a>resolve them before continuing with the setup.</p>
<h3 style="margin-bottom: 45px; line-height: 1.3;">Common pitfalls</h3>
<table style="border-collapse: collapse; width: 100%; margin-bottom: 2em;">
<thead>
<tr>
<th style="border: 1px solid #ddd; padding: 13px; text-align: left;">Symptom</th>
<th style="border: 1px solid #ddd; padding: 13px; text-align: left;">Cause</th>
<th style="border: 1px solid #ddd; padding: 13px; text-align: left;">Fix</th>
</tr>
</thead>
<tbody>
<tr>
<td style="border: 1px solid #ddd; padding: 13px;">429 on every request</td>
<td style="border: 1px solid #ddd; padding: 13px;">`burst` too low or `nodelay` missing</td>
<td style="border: 1px solid #ddd; padding: 13px;">Increase `burst` or remove `nodelay`</td>
</tr>
<tr>
<td style="border: 1px solid #ddd; padding: 13px;">No 429, still overloaded</td>
<td style="border: 1px solid #ddd; padding: 13px;">Zone size (`10m`) too small for traffic</td>
<td style="border: 1px solid #ddd; padding: 13px;">Raise zone size (`20m`) or split by `$http_user_agent`</td>
</tr>
<tr>
<td style="border: 1px solid #ddd; padding: 13px;">Client IP logged as 127.0.0.1</td>
<td style="border: 1px solid #ddd; padding: 13px;">NGINX behind another proxy without `real_ip` config</td>
<td style="border: 1px solid #ddd; padding: 13px;">Add <code>set_real_ip_from <trusted‑proxy‑ip>; real_ip_header X-Forwarded-For;</code></td>
</tr>
</tbody>
</table>
<div style="margin: 55px 0;">
<hr />
</div>
<h2 style="margin-bottom: 45px; line-height: 1.3;">Implementing rate limiting with Traefik (Docker‑Compose)</h2>
<h3 style="margin-bottom: 45px; line-height: 1.3;">Step 1 – Base compose file (defines n8n and Traefik services).</h3>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;">version: "3.8"
services:
n8n:
image: n8nio/n8n
environment:
- N8N_HOST=n8n.example.com
ports:
- "5678:5678"
networks:
- n8n-net
traefik:
image: traefik:v2.11
command:
- "--providers.docker=true"
- "--entrypoints.web.address=:80"
- "--api.insecure=true"
ports:
- "80:80"
- "8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- n8n-net
networks:
n8n-net:</pre>
<h3 style="margin-bottom: 45px; line-height: 1.3;">Step 2 – Add rate‑limit middleware via Docker labels (attached to the <code>n8n</code> service).</h3>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;"> n8n:
# …existing config…
labels:
- "traefik.enable=true"
- "traefik.http.routers.n8n.rule=Host(`n8n.example.com`)"
- "traefik.http.routers.n8n.entrypoints=web"
- "traefik.http.services.n8n.loadbalancer.server.port=5678"
# Rate‑limit: 10 r/s, burst up to 20
- "traefik.http.middlewares.n8n-ratelimit.ratelimit.average=10"
- "traefik.http.middlewares.n8n-ratelimit.ratelimit.burst=20"
- "traefik.http.routers.n8n.middlewares=n8n-ratelimit@docker"</pre>
<p style="margin-bottom: 2em; line-height: 1.9;">Deploy with <code>docker compose up -d</code> and test using the same curl loop as for NGINX.</p>
<h3 style="margin-bottom: 45px; line-height: 1.3;">EEFA considerations for Traefik</h3>
<ul style="line-height: 1.9; margin-bottom: 1.5em;">
<li>Traefik’s rate‑limit works <strong>per‑client IP</strong>; if you sit behind a CDN, ensure the real client IP is forwarded (<code>X-Forwarded-For</code>) and enable the forwarded‑headers middleware.</li>
<li>In Kubernetes, replace Docker labels with an <code>IngressRoute</code> CRD that references the same middleware.</li>
</ul>
<div style="margin: 55px 0;">
<hr />
</div>
<h2 style="margin-bottom: 45px; line-height: 1.3;">Detecting and responding to DoS attempts</h2>
<table style="border-collapse: collapse; width: 100%; margin-bottom: 2em;">
<thead>
<tr>
<th style="border: 1px solid #ddd; padding: 13px; text-align: left;">Detection method</th>
<th style="border: 1px solid #ddd; padding: 13px; text-align: left;">What to look for</th>
<th style="border: 1px solid #ddd; padding: 13px; text-align: left;">Tooling</th>
</tr>
</thead>
<tbody>
<tr>
<td style="border: 1px solid #ddd; padding: 13px;">NGINX/Traefik access logs</td>
<td style="border: 1px solid #ddd; padding: 13px;">Sudden spikes of <code>429</code> or <code>200</code> from a single IP</td>
<td style="border: 1px solid #ddd; padding: 13px;"><code>awk '{print $1}' | sort | uniq -c | sort -nr | head</code></td>
</tr>
<tr>
<td style="border: 1px solid #ddd; padding: 13px;">Prometheus metrics (via <code>nginx‑exporter</code> or <code>traefik‑metrics</code>)</td>
<td style="border: 1px solid #ddd; padding: 13px;"><code>http_requests_total{code="429"}</code> rising sharply</td>
<td style="border: 1px solid #ddd; padding: 13px;">Grafana alert: <code>rate(http_requests_total{code="429"}[1m]) > 100</code></td>
</tr>
<tr>
<td style="border: 1px solid #ddd; padding: 13px;">Fail2Ban</td>
<td style="border: 1px solid #ddd; padding: 13px;">Repeated <code>429</code> from same IP within 5 min</td>
<td style="border: 1px solid #ddd; padding: 13px;">Custom jail (see below)</td>
</tr>
<tr>
<td style="border: 1px solid #ddd; padding: 13px;">Cloudflare analytics</td>
<td style="border: 1px solid #ddd; padding: 13px;">“Top threats” → “Rate limiting”</td>
<td style="border: 1px solid #ddd; padding: 13px;">Enable Cloudflare Rate Limiting rule for <code>/rest/*</code></td>
</tr>
</tbody>
</table>
<h3 style="margin-bottom: 45px; line-height: 1.3;">Sample Fail2Ban jail for n8n</h3>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>Jail definition</strong> (<code>/etc/fail2ban/jail.d/n8n.conf</code>)</p>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;">[n8n-rate-limit]
enabled = true
filter = n8n
logpath = /var/log/nginx/access.log
maxretry = 5
findtime = 300
bantime = 3600
action = iptables[name=n8n, port=http, protocol=tcp]</pre>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>Filter</strong> (<code>/etc/fail2ban/filter.d/n8n.conf</code>)</p>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;">[Definition]
failregex = ^ - - \[.*\] ".* /rest/.*" 429
ignoreregex =</pre>
<p style="margin-bottom: 2em; line-height: 1.9;">Restart with <code>systemctl restart fail2ban</code>.</p>
<blockquote style="margin: 0 0 2em 0; padding-left: 1em; border-left: 4px solid #ddd; font-style: italic;">
<p style="margin: 0; line-height: 1.9;"><strong>EEFA warning:</strong> A CI/CD pipeline that polls <code>/rest/workflows</code> every second may be blocked. Whitelist known service IPs or raise <code>maxretry</code>. High‑frequency <code>429</code> responses can flood logs—rotate logs frequently (<code>logrotate</code>) and consider sending metrics to a central system instead of raw logs.</p>
</blockquote>
<div style="margin: 55px 0;">
<hr />
</div>
<h2 style="margin-bottom: 45px; line-height: 1.3;">Best‑practice checklist & monitoring dashboard</h2>
<table style="border-collapse: collapse; width: 100%; margin-bottom: 2em;">
<thead>
<tr>
<th style="border: 1px solid #ddd; padding: 13px; text-align: left;">Checklist item</th>
<th style="border: 1px solid #ddd; padding: 13px; text-align: left;">Recommended setting</th>
<th style="border: 1px solid #ddd; padding: 13px; text-align: left;">Verification</th>
</tr>
</thead>
<tbody>
<tr>
<td style="border: 1px solid #ddd; padding: 13px;">Rate‑limit rule</td>
<td style="border: 1px solid #ddd; padding: 13px;">10 r/s per IP, burst 20</td>
<td style="border: 1px solid #ddd; padding: 13px;">`curl` test returns 429 after 20 rapid calls</td>
</tr>
<tr>
<td style="border: 1px solid #ddd; padding: 13px;">Real‑client IP</td>
<td style="border: 1px solid #ddd; padding: 13px;">`real_ip_header X-Forwarded-For` (NGINX) or `ForwardedHeaders` (Traefik)</td>
<td style="border: 1px solid #ddd; padding: 13px;">`$remote_addr` shows external IP</td>
</tr>
<tr>
<td style="border: 1px solid #ddd; padding: 13px;">Burst handling</td>
<td style="border: 1px solid #ddd; padding: 13px;">`nodelay` disabled for smoother throttling</td>
<td style="border: 1px solid #ddd; padding: 13px;">No sudden traffic spikes in Grafana</td>
</tr>
<tr>
<td style="border: 1px solid #ddd; padding: 13px;">Alerting</td>
<td style="border: 1px solid #ddd; padding: 13px;">Grafana alert on `429` rate > 100 r/s</td>
<td style="border: 1px solid #ddd; padding: 13px;">Alert fires in test environment</td>
</tr>
<tr>
<td style="border: 1px solid #ddd; padding: 13px;">Fail2Ban</td>
<td style="border: 1px solid #ddd; padding: 13px;">Jail active, whitelist CI IPs</td>
<td style="border: 1px solid #ddd; padding: 13px;">`fail2ban-client status n8n-rate-limit` shows 0 bans after CI run</td>
</tr>
<tr>
<td style="border: 1px solid #ddd; padding: 13px;">Documentation</td>
<td style="border: 1px solid #ddd; padding: 13px;">Link to “Secure your n8n webhooks” for webhook‑specific limits</td>
<td style="border: 1px solid #ddd; padding: 13px;">Internal link present in “Detecting DoS” section</td>
</tr>
</tbody>
</table>
<blockquote style="margin: 0 0 2em 0; padding-left: 1em; border-left: 4px solid #ddd; font-style: italic;">
<p style="margin: 0; line-height: 1.9;"><strong>EEFA tip:</strong> When you adjust thresholds, run a traffic rehearsal (e.g., <code>hey</code> or <code>k6</code> load test) for at least 5 minutes to ensure legitimate bursts (like bulk data imports) are not unintentionally throttled.</p>
</blockquote>
<div style="margin: 55px 0;">
<hr />
</div>
<h2 style="margin-bottom: 45px; line-height: 1.3;">Conclusion</h2>
<p style="margin-bottom: 2em; line-height: 1.9;">Rate limiting is the most effective first line of defense against API‑driven DoS attacks on n8n. By placing a lightweight reverse proxy (NGINX or Traefik) in front of the application, you can:</p>
<ul style="line-height: 1.9; margin-bottom: 1.5em;">
<li><strong>Cap request volume per IP</strong> – protecting CPU, memory, and database connections.</li>
<li><strong>Provide predictable burst handling</strong> – allowing legitimate integration spikes while rejecting abuse.</li>
<li><strong>Enable fast detection</strong> – via 429 responses, logs, and metrics that feed into alerts and Fail2Ban.</li>
</ul>
<p style="margin-bottom: 2em; line-height: 1.9;">Implement the 10 r/s baseline, tune it with real traffic, and monitor the <code>429</code> metric. This approach safeguards n8n in production without altering the core application code, keeping your workflows responsive and your infrastructure resilient.</p>
Step by Step Guide to solve missing api rate limiting dos
Who this is for: DevOps engineers, self‑hosted n8n operators, and security‑focused developers who need to protect a publicly reachable n8n instance. We cover this in detail in the n8n Security & Hardening Guide.
Quick diagnosis
The n8n API is exposed without request‑throttling by default. An attacker can flood /rest/* endpoints (including workflow execution) with thousands of calls per second, exhausting CPU, memory, and database connections → denial‑of‑service (DoS).
Featured‑snippet solution: Add a reverse‑proxy (NGINX or Traefik) in front of n8n and configure a rate‑limit rule of 10 r/s per IP (adjust to your traffic). Reload the proxy and verify that the API returns 429 Too Many Requests when the limit is exceeded.
Why a limit stops it – Caps the number of requests an IP can make, forcing attackers to slow down while preserving resources for genuine traffic.
EEFA note: Do not set the limit too low (e.g., 1 r/s) in production; you’ll block legitimate integrations. Start with a conservative baseline (10 r/s) and tune based on observed traffic patterns.
Choosing the right rate‑limiting layer
Layer
Pros
NGINX (or Apache) reverse proxy
Mature limit_req module, easy per‑IP limits, can be combined with limit_req_zone for burst handling
Traefik (Docker‑Compose)
Native middleware, dynamic config via labels, integrates with Docker Swarm/K8s
Cloudflare / API‑gateway (e.g., AWS API GW)
Global edge protection, bot‑management, analytics
n8n custom middleware (Node.js)
Fine‑grained per‑workflow limits
Cons
Typical use‑case
Requires separate container or host install
Small‑to‑medium self‑hosted n8n
Limited to HTTP‑level limits (no per‑method granularity)
Container‑first deployments
Extra cost, adds latency, needs DNS change
Public‑facing n8n instances
Requires code change, not officially supported
Highly customized SaaS offering
EEFA tip: When using a reverse proxy, place the rate‑limiting rule before any authentication middleware so malicious traffic is blocked early. If you encounter any rbac pitfalls resolve them before continuing with the setup.
Implementing rate limiting with NGINX
Step 1 – Define a shared memory zone to store request counters.
for i in {1..30}; do
curl -s -o /dev/null -w "%{http_code} " http://n8n.example.com/rest/workflows;
done
You should see a series of 200 responses followed by 429 once the burst limit is reached. If you encounter any missing audit logging breach detection resolve them before continuing with the setup.
Common pitfalls
Symptom
Cause
Fix
429 on every request
`burst` too low or `nodelay` missing
Increase `burst` or remove `nodelay`
No 429, still overloaded
Zone size (`10m`) too small for traffic
Raise zone size (`20m`) or split by `$http_user_agent`
Client IP logged as 127.0.0.1
NGINX behind another proxy without `real_ip` config
Deploy with docker compose up -d and test using the same curl loop as for NGINX.
EEFA considerations for Traefik
Traefik’s rate‑limit works per‑client IP; if you sit behind a CDN, ensure the real client IP is forwarded (X-Forwarded-For) and enable the forwarded‑headers middleware.
In Kubernetes, replace Docker labels with an IngressRoute CRD that references the same middleware.
EEFA warning: A CI/CD pipeline that polls /rest/workflows every second may be blocked. Whitelist known service IPs or raise maxretry. High‑frequency 429 responses can flood logs—rotate logs frequently (logrotate) and consider sending metrics to a central system instead of raw logs.
Best‑practice checklist & monitoring dashboard
Checklist item
Recommended setting
Verification
Rate‑limit rule
10 r/s per IP, burst 20
`curl` test returns 429 after 20 rapid calls
Real‑client IP
`real_ip_header X-Forwarded-For` (NGINX) or `ForwardedHeaders` (Traefik)
`$remote_addr` shows external IP
Burst handling
`nodelay` disabled for smoother throttling
No sudden traffic spikes in Grafana
Alerting
Grafana alert on `429` rate > 100 r/s
Alert fires in test environment
Fail2Ban
Jail active, whitelist CI IPs
`fail2ban-client status n8n-rate-limit` shows 0 bans after CI run
Documentation
Link to “Secure your n8n webhooks” for webhook‑specific limits
Internal link present in “Detecting DoS” section
EEFA tip: When you adjust thresholds, run a traffic rehearsal (e.g., hey or k6 load test) for at least 5 minutes to ensure legitimate bursts (like bulk data imports) are not unintentionally throttled.
Conclusion
Rate limiting is the most effective first line of defense against API‑driven DoS attacks on n8n. By placing a lightweight reverse proxy (NGINX or Traefik) in front of the application, you can:
Cap request volume per IP – protecting CPU, memory, and database connections.
Provide predictable burst handling – allowing legitimate integration spikes while rejecting abuse.
Enable fast detection – via 429 responses, logs, and metrics that feed into alerts and Fail2Ban.
Implement the 10 r/s baseline, tune it with real traffic, and monitor the 429 metric. This approach safeguards n8n in production without altering the core application code, keeping your workflows responsive and your infrastructure resilient.