Control Plane vs Data Plane in n8n: Architecture Deep Dive

Step by Step Guide to solve n8n control plane data plane 
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.


Summary

If you encounter any production grade n8n architecture resolve them before continuing with the setup.

Layer Primary Role Typical Deployment Key Settings
Control‑Plane Manage workflow definitions, UI, authentication, orchestration Single container/service (e.g., n8n-web) EXECUTIONS_MODE=main, N8N_HOST, N8N_PORT
Data‑Plane Run workflow executions, process payloads, call external APIs One‑or‑many workers behind a queue (e.g., n8n-worker) EXECUTIONS_MODE=queue, EXECUTIONS_PROCESS=worker, QUEUE_BULL_REDIS_HOST

Result: Isolated control traffic, independent scaling of heavy workloads, and a clear security boundary.


1. Why Separate Control‑Plane from Data‑Plane?

If you encounter any n8n architecture anti patterns resolve them before continuing with the setup.

Concern Drawbacks of a Single Plane Benefits of Separation
Security UI & execution share the same network namespace → credential leakage Control‑plane behind a strict firewall; workers run in a sandboxed subnet
Scalability CPU‑intensive executions block UI responsiveness Workers autoscale horizontally without touching the UI service
Maintainability UI updates affect the execution runtime (and vice‑versa) Independent versioning; patch control‑plane without restarting workers
Observability Mixed logs make troubleshooting noisy Separate log streams simplify monitoring and alerting

EEFA Note – Run workers in a non‑privileged container or VM and enforce least‑privilege IAM for external API credentials.


2. Architectural Blueprint

Overview Diagram (markdown‑compatible)

+-------------------+          +-------------------+
|  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

Core Components

Component Placement Recommended Settings
n8n‑web (UI + API) Control‑Plane EXECUTIONS_MODE=main
n8n‑worker (execution) Data‑Plane EXECUTIONS_MODE=queue + EXECUTIONS_PROCESS=worker
PostgreSQL Control‑Plane (or managed DB) DB_TYPE=postgresdb
Redis (BullMQ) Shared but network‑isolated QUEUE_BULL_REDIS_HOST
Reverse Proxy (Traefik/NGINX) Control‑Plane edge TLS termination, IP allow‑list

3. Deploying a Split Architecture with Docker‑Compose

Prerequisite – Docker Engine ≥ 20.10, Docker Compose ≥ 2.0.

3.1 Directory Layout

n8n/
├─ docker-compose.yml
├─ .env.control      # env for control‑plane
├─ .env.worker       # env for data‑plane workers
└─ redis/
   └─ redis.conf

3.2 Control‑Plane Environment (.env.control)

# 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

3.3 Data‑Plane Environment (.env.worker)

# 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

3.4 Docker‑Compose Service Definitions

Redis Service

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

PostgreSQL Service

db:
  image: postgres:15-alpine
  environment:
    POSTGRES_USER: n8n
    POSTGRES_PASSWORD: n8nPass!
    POSTGRES_DB: n8n
  volumes:
    - pgdata:/var/lib/postgresql/data
  networks:
    - n8n-net

Control‑Plane Container (n8n-web)

n8n-web:
  image: n8nio/n8n:latest
  env_file:
    - .env.control
  ports:
    - "5678:5678"
  depends_on:
    - db
    - redis
  networks:
    - n8n-net
  restart: unless-stopped

Data‑Plane Workers (n8n-worker)

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

Compose Boilerplate

version: "3.8"

networks:
  n8n-net:
    driver: bridge

volumes:
  pgdata:

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.


4. Kubernetes‑Native Split (Production‑Grade)

4.1 Namespace Layout

apiVersion: v1
kind: Namespace
metadata:
  name: n8n-control
---
apiVersion: v1
kind: Namespace
metadata:
  name: n8n-data

4.2 Control‑Plane Deployment (n8n-web)

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"

Service exposing the UI

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

4.3 Data‑Plane Workers (n8n-worker)

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

Service for the workers (internal queue)

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

4.4 Shared Queue & Database (Redis & PostgreSQL)

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

EEFA Tip – Enforce Pod Security Standards (runAsUser ≥ 1000, drop all Linux capabilities) on the worker pods.

4.5 Autoscaling the Workers

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

5. Troubleshooting Checklist

Symptom Likely Cause Diagnostic Steps Fix
UI hangs Workers saturate Redis → back‑pressure on API 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

  1. Baseline – Run a load test (e.g., hey -n 5000 -c 50 http://n8n-web/api/v1/workflows) and capture queue length.
  2. 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.
  3. Raise queue limits – If you see “Queue full” errors, bump QUEUE_BULL_MAX_JOBS.
  4. 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.

Leave a Comment

Your email address will not be published. Required fields are marked *