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=mainWORKFLOW_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_pathat 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 readtenant-idfrom the job payload.
✅ Redis queues are prefixed withn8n: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‑IDheader 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
tenantIdlabel, 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.
- Create tenant record – Insert into a master
tenantstable (id, name, schema). - Run schema provisioner – Execute the SQL script from §2.2.
- Generate tenant encryption key –
openssl rand -hex 32→ store encrypted in a vault. - 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"
- Install/upgrade via Helm
helm upgrade --install n8n-tenant-42 n8n/n8n -f values-tenant-42.yaml
- Validate – Trigger a simple workflow that writes a row; verify the row appears only in
tenant_42schema.
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.



