<figure class="wp-block-image aligncenter"><img src="https://flowgenius.in/wp-content/uploads/2026/01/n8n-control-plane-data-plane.png" alt="Step by Step Guide to solve n8n control plane data plane" /> <figcaption style="text-align: center;">Step by Step Guide to solve n8n control plane data plane</p>
<hr />
</figcaption></figure>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>Who this is for:</strong> DevOps or platform engineers who run n8n in production and need a secure, scalable setup. <strong>We cover this in detail in the </strong>Production‑Grade n8n Architecture.</p>
<hr style="border: none; margin: 55px 0;" />
<h2 style="margin-bottom: 45px; line-height: 1.3;">Quick Diagnosis</h2>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>Problem</strong> – A single n8n runtime handles UI, API, workflow storage <strong>and</strong> heavy data processing. That mixes responsibilities, creates security exposure, and leads to scaling bottlenecks.</p>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>Solution</strong> – Split the <strong>control‑plane</strong> (API, UI, workflow storage) from the <strong>data‑plane</strong> (execution workers, payload handling). Deploy them as isolated services, lock down network policies, and set execution modes for horizontal scaling and hardened security.</p>
<blockquote style="margin-bottom: 2em; line-height: 1.9; font-style: italic;"><p>In production it usually appears after a few weeks of steady traffic, not on day one.</p></blockquote>
<hr style="border: none; margin: 55px 0;" />
<h2 style="margin-bottom: 45px; line-height: 1.3;">Summary</h2>
<p>If you encounter any <a href="/production-grade-n8n-architecture">production grade n8n architecture </a>resolve them before continuing with the setup.</p>
<table style="border-collapse: collapse; width: 100%; margin-bottom: 1.8em;">
<thead>
<tr>
<th style="padding: 13px; border: 1px solid #ddd; text-align: left;">Layer</th>
<th style="padding: 13px; border: 1px solid #ddd; text-align: left;">Primary Role</th>
<th style="padding: 13px; border: 1px solid #ddd; text-align: left;">Typical Deployment</th>
<th style="padding: 13px; border: 1px solid #ddd; text-align: left;">Key Settings</th>
</tr>
</thead>
<tbody>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">Control‑Plane</td>
<td style="padding: 13px; border: 1px solid #ddd;">Manage workflow definitions, UI, authentication, orchestration</td>
<td style="padding: 13px; border: 1px solid #ddd;">Single container/service (e.g., <code>n8n-web</code>)</td>
<td style="padding: 13px; border: 1px solid #ddd;"><code>EXECUTIONS_MODE=main</code>, <code>N8N_HOST</code>, <code>N8N_PORT</code></td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">Data‑Plane</td>
<td style="padding: 13px; border: 1px solid #ddd;">Run workflow executions, process payloads, call external APIs</td>
<td style="padding: 13px; border: 1px solid #ddd;">One‑or‑many workers behind a queue (e.g., <code>n8n-worker</code>)</td>
<td style="padding: 13px; border: 1px solid #ddd;"><code>EXECUTIONS_MODE=queue</code>, <code>EXECUTIONS_PROCESS=worker</code>, <code>QUEUE_BULL_REDIS_HOST</code></td>
</tr>
</tbody>
</table>
<p style="margin-bottom: 2em; line-height: 1.9;"><em>Result</em>: Isolated control traffic, independent scaling of heavy workloads, and a clear security boundary.</p>
<hr style="border: none; margin: 55px 0;" />
<h2 style="margin-bottom: 45px; line-height: 1.3;">1. Why Separate Control‑Plane from Data‑Plane?</h2>
<p>If you encounter any <a href="/n8n-architecture-anti-patterns">n8n architecture anti patterns </a>resolve them before continuing with the setup.</p>
<table style="border-collapse: collapse; width: 100%; margin-bottom: 1.8em;">
<thead>
<tr>
<th style="padding: 13px; border: 1px solid #ddd; text-align: left;">Concern</th>
<th style="padding: 13px; border: 1px solid #ddd; text-align: left;">Drawbacks of a Single Plane</th>
<th style="padding: 13px; border: 1px solid #ddd; text-align: left;">Benefits of Separation</th>
</tr>
</thead>
<tbody>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;"><strong>Security</strong></td>
<td style="padding: 13px; border: 1px solid #ddd;">UI & execution share the same network namespace → credential leakage</td>
<td style="padding: 13px; border: 1px solid #ddd;">Control‑plane behind a strict firewall; workers run in a sandboxed subnet</td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;"><strong>Scalability</strong></td>
<td style="padding: 13px; border: 1px solid #ddd;">CPU‑intensive executions block UI responsiveness</td>
<td style="padding: 13px; border: 1px solid #ddd;">Workers autoscale horizontally without touching the UI service</td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;"><strong>Maintainability</strong></td>
<td style="padding: 13px; border: 1px solid #ddd;">UI updates affect the execution runtime (and vice‑versa)</td>
<td style="padding: 13px; border: 1px solid #ddd;">Independent versioning; patch control‑plane without restarting workers</td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;"><strong>Observability</strong></td>
<td style="padding: 13px; border: 1px solid #ddd;">Mixed logs make troubleshooting noisy</td>
<td style="padding: 13px; border: 1px solid #ddd;">Separate log streams simplify monitoring and alerting</td>
</tr>
</tbody>
</table>
<blockquote style="margin-bottom: 2em; line-height: 1.9; font-style: italic;"><p>EEFA Note – Run workers in a <strong>non‑privileged</strong> container or VM and enforce <strong>least‑privilege IAM</strong> for external API credentials.</p></blockquote>
<hr style="border: none; margin: 55px 0;" />
<h2 style="margin-bottom: 45px; line-height: 1.3;">2. Architectural Blueprint</h2>
<h3 style="margin-bottom: 45px; line-height: 1.3;">Overview Diagram (markdown‑compatible)</h3>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;">+-------------------+ +-------------------+
| n8n Control‑Plane| | n8n Data‑Plane |
| (Web UI & API) | | (Execution Workers)|
| - n8n-web | <----> | - n8n-worker-1 |
| - DB (Postgres) | Queue | - n8n-worker-2 |
| - Auth Service | (Redis) | - … |
+-------------------+ +-------------------+
| |
| HTTPS (public) | Internal TCP (queue)
v v
Internet Users External APIs / Webhooks</pre>
<h3 style="margin-bottom: 45px; line-height: 1.3;">Core Components</h3>
<table style="border-collapse: collapse; width: 100%; margin-bottom: 1.8em;">
<thead>
<tr>
<th style="padding: 13px; border: 1px solid #ddd; text-align: left;">Component</th>
<th style="padding: 13px; border: 1px solid #ddd; text-align: left;">Placement</th>
<th style="padding: 13px; border: 1px solid #ddd; text-align: left;">Recommended Settings</th>
</tr>
</thead>
<tbody>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;"><strong>n8n‑web</strong> (UI + API)</td>
<td style="padding: 13px; border: 1px solid #ddd;">Control‑Plane</td>
<td style="padding: 13px; border: 1px solid #ddd;"><code>EXECUTIONS_MODE=main</code></td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;"><strong>n8n‑worker</strong> (execution)</td>
<td style="padding: 13px; border: 1px solid #ddd;">Data‑Plane</td>
<td style="padding: 13px; border: 1px solid #ddd;"><code>EXECUTIONS_MODE=queue</code> + <code>EXECUTIONS_PROCESS=worker</code></td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;"><strong>PostgreSQL</strong></td>
<td style="padding: 13px; border: 1px solid #ddd;">Control‑Plane (or managed DB)</td>
<td style="padding: 13px; border: 1px solid #ddd;"><code>DB_TYPE=postgresdb</code></td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;"><strong>Redis (BullMQ)</strong></td>
<td style="padding: 13px; border: 1px solid #ddd;">Shared but network‑isolated</td>
<td style="padding: 13px; border: 1px solid #ddd;"><code>QUEUE_BULL_REDIS_HOST</code></td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;"><strong>Reverse Proxy (Traefik/NGINX)</strong></td>
<td style="padding: 13px; border: 1px solid #ddd;">Control‑Plane edge</td>
<td style="padding: 13px; border: 1px solid #ddd;">TLS termination, IP allow‑list</td>
</tr>
</tbody>
</table>
<hr style="border: none; margin: 55px 0;" />
<h2 style="margin-bottom: 45px; line-height: 1.3;">3. Deploying a Split Architecture with Docker‑Compose</h2>
<blockquote style="margin-bottom: 2em; line-height: 1.9; font-style: italic;"><p>Prerequisite – Docker Engine ≥ 20.10, Docker Compose ≥ 2.0.</p></blockquote>
<h3 style="margin-bottom: 45px; line-height: 1.3;">3.1 Directory Layout</h3>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;">n8n/
├─ docker-compose.yml
├─ .env.control # env for control‑plane
├─ .env.worker # env for data‑plane workers
└─ redis/
└─ redis.conf</pre>
<h3 style="margin-bottom: 45px; line-height: 1.3;">3.2 Control‑Plane Environment (<code>.env.control</code>)</h3>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;"># Core n8n settings
N8N_HOST=0.0.0.0
N8N_PORT=5678
N8N_BASIC_AUTH_ACTIVE=true
N8N_BASIC_AUTH_USER=admin
N8N_BASIC_AUTH_PASSWORD=StrongP@ssw0rd
# Execution mode – only orchestrates
EXECUTIONS_MODE=main
EXECUTIONS_PROCESS=main
# Queue connection (read‑only)
QUEUE_BULL_REDIS_HOST=redis
QUEUE_BULL_REDIS_PORT=6379</pre>
<h3 style="margin-bottom: 45px; line-height: 1.3;">3.3 Data‑Plane Environment (<code>.env.worker</code>)</h3>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;"># Workers never expose UI
N8N_HOST=0.0.0.0
N8N_PORT=5679 # internal only
# Execution mode – consumes jobs
EXECUTIONS_MODE=queue
EXECUTIONS_PROCESS=worker
# Queue connection (read/write)
QUEUE_BULL_REDIS_HOST=redis
QUEUE_BULL_REDIS_PORT=6379
# OPTIONAL: limit CPU for sandboxing
N8N_MAX_EXECUTION_TIMEOUT=3600</pre>
<h3 style="margin-bottom: 45px; line-height: 1.3;">3.4 Docker‑Compose Service Definitions</h3>
<h4 style="margin-bottom: 45px; line-height: 1.3;">Redis Service</h4>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;">redis:
image: redis:7-alpine
command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
volumes:
- ./redis/redis.conf:/usr/local/etc/redis/redis.conf:ro
networks:
- n8n-net</pre>
<h4 style="margin-bottom: 45px; line-height: 1.3;">PostgreSQL Service</h4>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;">db:
image: postgres:15-alpine
environment:
POSTGRES_USER: n8n
POSTGRES_PASSWORD: n8nPass!
POSTGRES_DB: n8n
volumes:
- pgdata:/var/lib/postgresql/data
networks:
- n8n-net</pre>
<h4 style="margin-bottom: 45px; line-height: 1.3;">Control‑Plane Container (<code>n8n-web</code>)</h4>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;">n8n-web:
image: n8nio/n8n:latest
env_file:
- .env.control
ports:
- "5678:5678"
depends_on:
- db
- redis
networks:
- n8n-net
restart: unless-stopped</pre>
<h4 style="margin-bottom: 45px; line-height: 1.3;">Data‑Plane Workers (<code>n8n-worker</code>)</h4>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;">n8n-worker:
image: n8nio/n8n:latest
env_file:
- .env.worker
depends_on:
- db
- redis
networks:
- n8n-net
deploy:
mode: replicated
replicas: 3 # scale horizontally
restart: unless-stopped</pre>
<h4 style="margin-bottom: 45px; line-height: 1.3;">Compose Boilerplate</h4>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;">version: "3.8"
networks:
n8n-net:
driver: bridge
volumes:
pgdata:</pre>
<p style="margin-bottom: 2em; line-height: 1.9;">The pieces above are deliberately kept separate so you can tweak one side without touching the other.</p>
<h3 style="margin-bottom: 45px; line-height: 1.3;">3.5 Bring the Stack Up</h3>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;">docker compose up -d
docker compose ps # verify all services are running</pre>
<blockquote style="margin-bottom: 2em; line-height: 1.9; font-style: italic;"><p>EEFA Warning – Never expose the <code>n8n-worker</code> port to the public internet. Keep it confined to the internal Docker network or block it with firewall rules. If you encounter any <a href="/n8n-multi-tenant-architecture">n8n multi tenant architecture </a>resolve them before continuing with the setup.</p>
<h3 style="margin-bottom: 45px; line-height: 1.3;"></h3>
</blockquote>
<hr style="border: none; margin: 55px 0;" />
<h2 style="margin-bottom: 45px; line-height: 1.3;">4. Kubernetes‑Native Split (Production‑Grade)</h2>
<h3 style="margin-bottom: 45px; line-height: 1.3;">4.1 Namespace Layout</h3>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;">apiVersion: v1
kind: Namespace
metadata:
name: n8n-control
---
apiVersion: v1
kind: Namespace
metadata:
name: n8n-data</pre>
<h3 style="margin-bottom: 45px; line-height: 1.3;">4.2 Control‑Plane Deployment (<code>n8n-web</code>)</h3>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;">apiVersion: apps/v1
kind: Deployment
metadata:
name: n8n-web
namespace: n8n-control
spec:
replicas: 1
selector:
matchLabels:
app: n8n-web
template:
metadata:
labels:
app: n8n-web
spec:
containers:
- name: n8n
image: n8nio/n8n:latest
envFrom:
- secretRef:
name: n8n-control-env
ports:
- containerPort: 5678
resources:
limits:
cpu: "500m"
memory: "512Mi"</pre>
<h4 style="margin-bottom: 45px; line-height: 1.3;">Service exposing the UI</h4>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;">apiVersion: v1
kind: Service
metadata:
name: n8n-web
namespace: n8n-control
spec:
selector:
app: n8n-web
ports:
- protocol: TCP
port: 80
targetPort: 5678
type: ClusterIP</pre>
<h3 style="margin-bottom: 45px; line-height: 1.3;">4.3 Data‑Plane Workers (<code>n8n-worker</code>)</h3>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;">apiVersion: apps/v1
kind: Deployment
metadata:
name: n8n-worker
namespace: n8n-data
spec:
replicas: 3 # HPA can adjust this
selector:
matchLabels:
app: n8n-worker
template:
metadata:
labels:
app: n8n-worker
spec:
containers:
- name: n8n
image: n8nio/n8n:latest
envFrom:
- secretRef:
name: n8n-worker-env
ports:
- containerPort: 5679
resources:
limits:
cpu: "1000m"
memory: "1Gi"
securityContext:
runAsNonRoot: true
readOnlyRootFilesystem: true</pre>
<h4 style="margin-bottom: 45px; line-height: 1.3;">Service for the workers (internal queue)</h4>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;">apiVersion: v1
kind: Service
metadata:
name: n8n-queue
namespace: n8n-data
spec:
selector:
app: n8n-worker
ports:
- protocol: TCP
port: 5679
targetPort: 5679
type: ClusterIP</pre>
<h3 style="margin-bottom: 45px; line-height: 1.3;">4.4 Shared Queue & Database (Redis & PostgreSQL)</h3>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;">apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-n8n
namespace: n8n-infra
spec:
podSelector: {}
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: n8n-control
- namespaceSelector:
matchLabels:
name: n8n-data
ports:
- protocol: TCP
port: 6379 # Redis
- protocol: TCP
port: 5432 # Postgres</pre>
<blockquote style="margin-bottom: 2em; line-height: 1.9; font-style: italic;"><p>EEFA Tip – Enforce <strong>Pod Security Standards</strong> (runAsUser ≥ 1000, drop all Linux capabilities) on the worker pods.</p></blockquote>
<h3 style="margin-bottom: 45px; line-height: 1.3;">4.5 Autoscaling the Workers</h3>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;">apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: n8n-worker-hpa
namespace: n8n-data
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: n8n-worker
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70</pre>
<hr style="border: none; margin: 55px 0;" />
<h2 style="margin-bottom: 45px; line-height: 1.3;">5. Troubleshooting Checklist</h2>
<table style="border-collapse: collapse; width: 100%; margin-bottom: 1.8em;">
<thead>
<tr>
<th style="padding: 13px; border: 1px solid #ddd; text-align: left;">Symptom</th>
<th style="padding: 13px; border: 1px solid #ddd; text-align: left;">Likely Cause</th>
<th style="padding: 13px; border: 1px solid #ddd; text-align: left;">Diagnostic Steps</th>
<th style="padding: 13px; border: 1px solid #ddd; text-align: left;">Fix</th>
</tr>
</thead>
<tbody>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">UI hangs</td>
<td style="padding: 13px; border: 1px solid #ddd;">Workers saturate Redis → back‑pressure on API</td>
<td style="padding: 13px; border: 1px solid #ddd;"><code>kubectl logs n8n-web</code> → look for “Queue size exceeds limit”</td>
<td style="padding: 13px; border: 1px solid #ddd;">Add more worker replicas or raise <code>QUEUE_BULL_MAX_JOBS</code></td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">Execution fails with <strong>ECONNREFUSED</strong></td>
<td style="padding: 13px; border: 1px solid #ddd;">Worker cannot reach Redis (NetworkPolicy)</td>
<td style="padding: 13px; border: 1px solid #ddd;"><code>kubectl exec -it <worker-pod> -- curl redis:6379</code></td>
<td style="padding: 13px; border: 1px solid #ddd;">Update NetworkPolicy to allow traffic</td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">Credential leak in logs</td>
<td style="padding: 13px; border: 1px solid #ddd;">Worker runs as root, writes secrets to stdout</td>
<td style="padding: 13px; border: 1px solid #ddd;">Inspect pod <code>securityContext</code></td>
<td style="padding: 13px; border: 1px solid #ddd;">Set <code>runAsNonRoot: true</code>, mount secrets as files</td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">High UI‑to‑worker latency</td>
<td style="padding: 13px; border: 1px solid #ddd;">Cross‑zone traffic, no locality</td>
<td style="padding: 13px; border: 1px solid #ddd;">Ping between pods, check RTT</td>
<td style="padding: 13px; border: 1px solid #ddd;">Deploy workers in same zone or enable VPC peering</td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">DB connection timeout</td>
<td style="padding: 13px; border: 1px solid #ddd;">Control‑plane points to wrong DB host</td>
<td style="padding: 13px; border: 1px solid #ddd;">Verify <code>DB_POSTGRESDB_HOST</code> env var</td>
<td style="padding: 13px; border: 1px solid #ddd;">Point to managed DB endpoint, enable TLS</td>
</tr>
</tbody>
</table>
<blockquote style="margin-bottom: 2em; line-height: 1.9; font-style: italic;"><p>EEFA Reminder – Enable <strong>audit logging</strong> on PostgreSQL and Redis. In regulated environments, encrypt data‑in‑flight (<code>REDIS_TLS_ENABLED=true</code>, <code>POSTGRES_SSLMODE=require</code>).</p></blockquote>
<hr style="border: none; margin: 55px 0;" />
<h2 style="margin-bottom: 45px; line-height: 1.3;">6. Security Hardening Checklist</h2>
<ul style="margin-bottom: 1.8em; line-height: 1.9;">
<li><strong>Network segmentation</strong> – Control‑plane in a public subnet, data‑plane in a private subnet. Use security groups or NetworkPolicies to block inbound traffic to workers.</li>
<li><strong>Least‑privilege IAM</strong> – Separate IAM roles for UI (read/write workflows) vs workers (execute only).</li>
<li><strong>Secret management</strong> – Store API keys in Vault, AWS Secrets Manager, or Kubernetes Secrets with sealed‑secrets. Never embed them in Docker env files.</li>
<li><strong>Runtime sandboxing</strong> – Enable <code>N8N_EXECUTIONS_SAND_BOXED=true</code> (Docker sandbox) or run workers inside gVisor / Kata Containers for extra isolation.</li>
<li><strong>TLS everywhere</strong> – Terminate TLS at the reverse proxy for the UI; use mutual TLS between control‑plane and workers when possible.</li>
</ul>
<hr style="border: none; margin: 55px 0;" />
<h2 style="margin-bottom: 45px; line-height: 1.3;">7. Performance Tuning – Scaling the Data‑Plane</h2>
<table style="border-collapse: collapse; width: 100%; margin-bottom: 1.8em;">
<thead>
<tr>
<th style="padding: 13px; border: 1px solid #ddd; text-align: left;">Parameter</th>
<th style="padding: 13px; border: 1px solid #ddd; text-align: left;">Description</th>
<th style="padding: 13px; border: 1px solid #ddd; text-align: left;">Suggested Starting Value</th>
</tr>
</thead>
<tbody>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;"><code>EXECUTIONS_TIMEOUT</code></td>
<td style="padding: 13px; border: 1px solid #ddd;">Max seconds a workflow may run</td>
<td style="padding: 13px; border: 1px solid #ddd;">3600 (1 h)</td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;"><code>EXECUTIONS_MAX_TIMEOUT</code></td>
<td style="padding: 13px; border: 1px solid #ddd;">Hard ceiling for any workflow</td>
<td style="padding: 13px; border: 1px solid #ddd;">86400 (24 h)</td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;"><code>QUEUE_BULL_MAX_JOBS</code></td>
<td style="padding: 13px; border: 1px solid #ddd;">Max queued jobs per worker</td>
<td style="padding: 13px; border: 1px solid #ddd;">200</td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;"><code>N8N_MAX_WORKFLOW_RUNS</code></td>
<td style="padding: 13px; border: 1px solid #ddd;">Parallel runs per worker pod</td>
<td style="padding: 13px; border: 1px solid #ddd;">10</td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;"><code>N8N_WORKER_CONCURRENCY</code></td>
<td style="padding: 13px; border: 1px solid #ddd;">Number of Node.js worker threads</td>
<td style="padding: 13px; border: 1px solid #ddd;">4 (CPU cores)</td>
</tr>
</tbody>
</table>
<h3 style="margin-bottom: 45px; line-height: 1.3;">Tuning Steps</h3>
<ol style="margin-bottom: 1.8em; line-height: 1.9;">
<li><strong>Baseline</strong> – Run a load test (e.g., <code>hey -n 5000 -c 50 http://n8n-web/api/v1/workflows</code>) and capture queue length.</li>
<li><strong>Add workers</strong> – Increase <code>replicas</code> in the worker Deployment or <code>docker compose up --scale n8n-worker=5</code>. <em>Adding more workers is usually faster than trying to squeeze more CPU out of a single instance.</em></li>
<li><strong>Raise queue limits</strong> – If you see “Queue full” errors, bump <code>QUEUE_BULL_MAX_JOBS</code>.</li>
<li><strong>Monitor</strong> – Pull Prometheus metrics (<code>/metrics</code>) and watch <code>n8n_queue_job_count</code> and <code>n8n_execution_duration_seconds</code>.</li>
</ol>
<hr style="border: none; margin: 55px 0;" />
<h3 style="margin-bottom: 45px; line-height: 1.3;">Bottom Line</h3>
<p style="margin-bottom: 2em; line-height: 1.9;">Decoupling the <strong>control‑plane</strong> (UI, API, orchestration) from the <strong>data‑plane</strong> (execution workers) gives you a <strong>secure, horizontally‑scalable, and maintainable</strong> n8n deployment. Follow the Docker‑Compose or Kubernetes recipes above, validate with the troubleshooting checklist, and iterate on the performance metrics to keep your automation platform robust in real‑world production.</p>
Step by Step Guide to solve n8n control plane data plane
Who this is for: DevOps or platform engineers who run n8n in production and need a secure, scalable setup. We cover this in detail in the Production‑Grade n8n Architecture.
Quick Diagnosis
Problem – A single n8n runtime handles UI, API, workflow storage and heavy data processing. That mixes responsibilities, creates security exposure, and leads to scaling bottlenecks.
Solution – Split the control‑plane (API, UI, workflow storage) from the data‑plane (execution workers, payload handling). Deploy them as isolated services, lock down network policies, and set execution modes for horizontal scaling and hardened security.
In production it usually appears after a few weeks of steady traffic, not on day one.
The pieces above are deliberately kept separate so you can tweak one side without touching the other.
3.5 Bring the Stack Up
docker compose up -d
docker compose ps # verify all services are running
EEFA Warning – Never expose the n8n-worker port to the public internet. Keep it confined to the internal Docker network or block it with firewall rules. If you encounter any n8n multi tenant architecture resolve them before continuing with the setup.
kubectl logs n8n-web → look for “Queue size exceeds limit”
Add more worker replicas or raise QUEUE_BULL_MAX_JOBS
Execution fails with ECONNREFUSED
Worker cannot reach Redis (NetworkPolicy)
kubectl exec -it <worker-pod> -- curl redis:6379
Update NetworkPolicy to allow traffic
Credential leak in logs
Worker runs as root, writes secrets to stdout
Inspect pod securityContext
Set runAsNonRoot: true, mount secrets as files
High UI‑to‑worker latency
Cross‑zone traffic, no locality
Ping between pods, check RTT
Deploy workers in same zone or enable VPC peering
DB connection timeout
Control‑plane points to wrong DB host
Verify DB_POSTGRESDB_HOST env var
Point to managed DB endpoint, enable TLS
EEFA Reminder – Enable audit logging on PostgreSQL and Redis. In regulated environments, encrypt data‑in‑flight (REDIS_TLS_ENABLED=true, POSTGRES_SSLMODE=require).
6. Security Hardening Checklist
Network segmentation – Control‑plane in a public subnet, data‑plane in a private subnet. Use security groups or NetworkPolicies to block inbound traffic to workers.
Least‑privilege IAM – Separate IAM roles for UI (read/write workflows) vs workers (execute only).
Secret management – Store API keys in Vault, AWS Secrets Manager, or Kubernetes Secrets with sealed‑secrets. Never embed them in Docker env files.
Runtime sandboxing – Enable N8N_EXECUTIONS_SAND_BOXED=true (Docker sandbox) or run workers inside gVisor / Kata Containers for extra isolation.
TLS everywhere – Terminate TLS at the reverse proxy for the UI; use mutual TLS between control‑plane and workers when possible.
7. Performance Tuning – Scaling the Data‑Plane
Parameter
Description
Suggested Starting Value
EXECUTIONS_TIMEOUT
Max seconds a workflow may run
3600 (1 h)
EXECUTIONS_MAX_TIMEOUT
Hard ceiling for any workflow
86400 (24 h)
QUEUE_BULL_MAX_JOBS
Max queued jobs per worker
200
N8N_MAX_WORKFLOW_RUNS
Parallel runs per worker pod
10
N8N_WORKER_CONCURRENCY
Number of Node.js worker threads
4 (CPU cores)
Tuning Steps
Baseline – Run a load test (e.g., hey -n 5000 -c 50 http://n8n-web/api/v1/workflows) and capture queue length.
Add workers – Increase replicas in the worker Deployment or docker compose up --scale n8n-worker=5. Adding more workers is usually faster than trying to squeeze more CPU out of a single instance.
Raise queue limits – If you see “Queue full” errors, bump QUEUE_BULL_MAX_JOBS.
Monitor – Pull Prometheus metrics (/metrics) and watch n8n_queue_job_count and n8n_execution_duration_seconds.
Bottom Line
Decoupling the control‑plane (UI, API, orchestration) from the data‑plane (execution workers) gives you a secure, horizontally‑scalable, and maintainable n8n deployment. Follow the Docker‑Compose or Kubernetes recipes above, validate with the troubleshooting checklist, and iterate on the performance metrics to keep your automation platform robust in real‑world production.