Multi-Tenant n8n Architecture

Step by Step Guide to solve n8n multi tenant architecture 
Step by Step Guide to solve n8n multi tenant architecture


Who this is for: Platform engineers who need to run a single n8n instance that securely serves many isolated customers. We cover this in detail in the Production‑Grade n8n Architecture.

Quick diagnosis: You must prevent data bleed, credential leakage, and performance contention across tenants. A production‑ready path combines per‑tenant PostgreSQL schemas, tenant‑scoped credential encryption, and Kubernetes‑based pod isolation, then tunes n8n’s EXECUTIONS_PROCESS and WORKFLOW_EXECUTION_MODE settings.

The biggest pain point is ensuring a tenant cannot see another’s data.


1. Architectural overview

How each concern is isolated in a multi‑tenant deployment.

Data & Credential isolation

Layer Isolation technique Recommended setting
Data Separate PostgreSQL schema per tenant search_path=tenant_<id> in DATABASE_URL
Credentials Encrypted store keyed by tenant ID CREDENTIALS_ENCRYPTION_KEY + derived tenant key

The schema row tells PostgreSQL which namespace to use for each tenant. The credential row shows how we keep secrets separate.

Execution, API & Observability

Layer Isolation technique Recommended setting
Workflow execution Queue workers scoped by tenant EXECUTIONS_PROCESS=main
WORKFLOW_EXECUTION_MODE=queue
API access JWT with tenant claim (tid) N8N_JWT_TENANT_CLAIM=tid
Observability Prometheus labels & metrics per tenant PROMETHEUS_LABELS=tenant_id

Note – Schema‑level data isolation together with queue‑based execution gives enterprise‑grade reliability while keeping operational overhead low.


2. Database isolation – per‑tenant schemas

Create a template schema once, then clone it for each new tenant. If you encounter any production grade n8n architecture resolve them before continuing with the setup.

2.1 Create the template (run once)

CREATE SCHEMA tenant_template;
\i /opt/n8n/sql/init.sql   -- load n8n tables into the template

2.2 Provision a new tenant

-- Replace <tenant_id> with a short identifier
CREATE SCHEMA tenant_<tenant_id> AUTHORIZATION n8n_user;
-- Clone the template structure without data
DO $$
DECLARE r RECORD;
BEGIN
  FOR r IN SELECT tablename FROM pg_tables WHERE schemaname='tenant_template' LOOP
    EXECUTE format(
      'CREATE TABLE tenant_%I.%I (LIKE tenant_template.%I INCLUDING ALL)',
      '<tenant_id>', r.tablename, r.tablename);
  END LOOP;
END $$;

2.3 Tenant‑specific connection string

# Example for tenant_abc
DATABASE_URL=postgres://n8n_user:pwd@db.internal:5432/n8n?search_path=tenant_abc

EEFA warning – Do not switch search_path at request time; it adds latency and can be spoofed. Switching per request forces a round‑trip to the DB for every query. Use a dedicated connection pool per tenant.


3. Credential store segmentation

Derive a unique encryption key for each tenant so that a breach in one tenant never exposes another’s secrets. If you encounter any n8n architecture anti patterns resolve them before continuing with the setup.

3.1 Tenant‑specific key derivation (Node 18+)

// utils/tenantKey.js
const crypto = require('crypto');
const baseKey = process.env.N8N_ENCRYPTION_KEY; // 32‑byte master key

module.exports = (tenantId) =>
  crypto.createHmac('sha256', baseKey).update(tenantId).digest('hex');

3.2 Use the derived key in the credential service

// src/credentials/credentialService.ts (excerpt)
import getTenantKey from '../utils/tenantKey';

async function encryptCredential(cred, tenantId) {
  const key = getTenantKey(tenantId);
  const iv = crypto.randomBytes(12);
  const cipher = crypto.createCipheriv('aes-256-gcm', Buffer.from(key, 'hex'), iv);
  // ...continue with existing encryption flow
}

Usually, regenerating the key is faster than handling edge cases.

3.3 Guard the API with a tenant claim

// src/middleware/tenantGuard.ts
export function tenantGuard(req, res, next) {
  const tenantId = req.user?.tid;
  if (!tenantId) return res.status(401).json({ error: 'Missing tenant claim' });
  req.tenantId = tenantId;
  next();
}

EEFA integration – A per‑tenant derived key satisfies ISO 27001‑style isolation and limits blast radius.


4. Execution isolation: queue workers per tenant

Configure n8n to route each tenant’s workflows to its own Redis queue and run them in dedicated Kubernetes pods.

4.1 Turn on queue mode globally

EXECUTIONS_PROCESS=main
WORKFLOW_EXECUTION_MODE=queue
QUEUE_BROKER=redis://redis.internal:6379

4.2 Compute a tenant‑scoped queue name

// src/queues/queueService.ts
export const getQueueName = (tenantId) => `n8n:tenant:${tenantId}`;

4.3 Deploy worker pods (Kubernetes)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: n8n-worker
spec:
  replicas: 3
  selector:
    matchLabels:
      app: n8n
      role: worker
  template:
    metadata:
      labels:
        app: n8n
        role: worker
    spec:
      containers:
        - name: n8n
          image: n8nio/n8n:latest
          env:
            - name: EXECUTIONS_PROCESS
              value: "queue"
            - name: WORKFLOW_EXECUTION_MODE
              value: "queue"
            - name: QUEUE_BROKER
              value: "redis://redis.internal:6379"
          resources:
            limits:
              cpu: "500m"
              memory: "512Mi"
          affinity:
            podAntiAffinity:
              requiredDuringSchedulingIgnoredDuringExecution:
                - labelSelector:
                    matchExpressions:
                      - key: tenant-id
                        operator: In
                        values: ["tenant_a","tenant_b"]
                  topologyKey: "kubernetes.io/hostname"

EEFA checklist
✅ Workers read tenant-id from the job payload.
✅ Redis queues are prefixed with n8n:tenant:.
✅ PodAntiAffinity spreads a tenant’s workers across nodes.
✅ HPA can scale per‑tenant queue length via custom metrics.


5. API gateway & authentication

Validate the tenant claim at the edge and forward a trusted header to n8n.

5.1 JWT payload example

{
  "sub": "user@example.com",
  "tid": "tenant_42",
  "iat": 1700000000,
  "exp": 1700003600,
  "roles": ["admin"]
}

5.2 Nginx reverse‑proxy configuration

# /etc/nginx/conf.d/n8n-multi-tenant.conf
server {
  listen 443 ssl;
  server_name automation.mycorp.com;

  location / {
    proxy_pass http://n8n-service:5678;
    proxy_set_header Authorization $http_authorization;
    proxy_set_header X-Forwarded-Proto $scheme;

    auth_jwt "Protected API";
    auth_jwt_key_file /etc/keys/public.pem;
    auth_jwt_set $tenant_id $jwt_claim_tid;
    proxy_set_header X-Tenant-ID $tenant_id;
  }
}

5.3 n8n middleware that trusts the header

// src/middleware/tenantHeader.ts
export function tenantHeader(req, res, next) {
  const tenantId = req.headers['x-tenant-id'];
  if (!tenantId) return res.status(400).json({ error: 'Tenant header missing' });
  req.tenantId = tenantId;
  next();
}

Note – Edge validation plus a server‑controlled X‑Tenant‑ID header eliminates the risk of a client forging a tenant identifier. Don’t forget to set the header; otherwise the guard will reject every request.


6. Monitoring, logging & alerting per tenant

Expose tenant labels so you can slice metrics, logs, and errors.

6.1 Prometheus & Grafana

Tool Tenant‑scoped metric Example query
Prometheus n8n_workflow_executions_total{tenant_id="tenant_42"} sum by (tenant_id) (rate(n8n_workflow_executions_total[5m]))
Grafana Dashboard templated with $tenant variable Use $tenant in all panel queries

6.2 Centralized logging (ELK)

// src/logger.js
import pino from 'pino';
export const logger = pino({
  base: null,
  timestamp: pino.stdTimeFunctions.isoTime,
  formatters: {
    level(label) { return { level: label }; },
    bindings(b) { return { ...b, tenantId: b.tenantId || 'unknown' }; }
  }
});

6.3 Error tracking (Sentry)

Tool Tenant tag How to filter
Sentry tags.tenant_id Select tenant in the UI to see only its issues

EEFA checklist – All logs, metrics, and alerts carry a tenantId label, enabling per‑customer SLA monitoring. If you encounter any n8n control plane data plane resolve them before continuing with the setup.


7. Provisioning workflow – step‑by‑step automation

Automate tenant onboarding with a CI/CD pipeline.

Automation here is a series of scripts. Hook them into any CI system you already use.

  1. Create tenant record – Insert into a master tenants table (id, name, schema).
  2. Run schema provisioner – Execute the SQL script from §2.2.
  3. Generate tenant encryption keyopenssl rand -hex 32 → store encrypted in a vault.
  4. Deploy namespace‑scoped resources – Render a Helm values file per tenant:
# values-tenant-42.yaml
n8n:
  env:
    - name: DATABASE_URL
      value: "postgres://n8n_user:pwd@db.internal:5432/n8n?search_path=tenant_42"
    - name: N8N_ENCRYPTION_KEY
      valueFrom:
        secretKeyRef:
          name: tenant-42-secrets
          key: encryption-key
    - name: N8N_JWT_TENANT_CLAIM
      value: "tid"
  1. Install/upgrade via Helm
helm upgrade --install n8n-tenant-42 n8n/n8n -f values-tenant-42.yaml
  1. Validate – Trigger a simple workflow that writes a row; verify the row appears only in tenant_42 schema.

EEFA insight – CI/CD‑driven provisioning guarantees consistency and keeps onboarding time under five minutes per tenant.


8. Troubleshooting checklist

Symptom Likely cause Diagnostic command Fix
Workflows see another tenant’s data search_path not set on DB pool SELECT current_setting('search_path'); Use tenant‑specific DATABASE_URL per pool.
Credential decryption fails Mismatched derived key Log tenantId in request and derived key. Align JWT tid with schema name.
High latency for one tenant Queue backlog redis-cli llen n8n:tenant:<id> Scale worker pods or increase resources.
Cross‑tenant API access Missing X‑Tenant‑ID header Inspect NGINX access logs. Add proxy_set_header X‑Tenant‑ID $tenant_id;.
Metrics lack tenant label Prometheus relabel missing promtool check rules Add relabel_configs to inject tenant_id.

Most teams run into the queue‑backlog issue after a few weeks of steady load, not on day one.


Bottom line

Combine schema‑level data isolation, tenant‑derived encryption keys, queue‑based execution, and Kubernetes namespace scoping. The result is a single n8n instance that meets enterprise security, fault‑tolerance, and performance expectations. The approach stays fully configurable through code‑first tooling, making it suitable for production‑grade multi‑tenant deployments.

Leave a Comment

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