Self-Hosting n8n on Kubernetes: HA, Queue Mode, Autoscaling, and Backups That Actually Restore
n8n·By Islam Gamal··32 min read

Self-Hosting n8n on Kubernetes: HA, Queue Mode, Autoscaling, and Backups That Actually Restore

The complete production playbook for n8n on Kubernetes — queue mode topology, KEDA autoscaling on Redis depth, Postgres HA and PITR, encryption-key rotation, zero-downtime upgrades, and the backup drill you must actually run.

Islam Gamal
Islam Gamal
AI · Data · Automation Engineer
TL;DR

For any team past ~10k executions/day, self-hosted n8n in queue mode on Kubernetes — with Postgres, Redis, S3 for binary data, KEDA-driven worker autoscaling, and a rehearsed restore drill — is the operational sweet spot. Split main/webhook/worker into separate deployments, autoscale workers on Redis queue depth (not CPU), keep the encryption key in a KMS you can rotate, and run a monthly game day where you actually restore from backup. You don't have a backup until you've restored one.

Key takeaways
  • Queue mode is not optional above trivial volume.
  • Split main, webhook, and worker into separate deployments.
  • Autoscale workers on Redis queue depth via KEDA, not CPU via HPA.
  • Store binary data in S3-compatible storage — never in Postgres.
  • Rotate the n8n encryption key with a documented runbook; losing it bricks every stored credential.
  • Restore from backup monthly — scheduled, on the calendar, with a smoke workflow.
  • Upgrade workers first, main last; drain the queue before rolling.
  • Keep Postgres and Redis close (same VPC / same region) — network latency dominates n8n throughput.

When self-hosting is the right call

Managed n8n Cloud is a great default. Move to self-hosted Kubernetes when at least two of these are true:

  • You process regulated or highly sensitive data that cannot leave your VPC.
  • You exceed ~10k executions/day and the Cloud plan starts biting.
  • You need custom nodes, custom Docker images, or unusual runtime dependencies.
  • Your platform team already operates Kubernetes competently.
Watch out: Don't adopt Kubernetes for n8n alone. If your team doesn't already run K8s, use Docker Compose on a single beefy VM until you outgrow it.

The topology — three deployments, three responsibilities

DeploymentRoleReplicasScaling signal
mainUI, editor, cron scheduler, leader-elected1–2 (leader lock)Static
webhookInbound HTTP for webhooks — stateless2–NRequests / sec
workerExecutes workflow steps pulled from RedisN (auto)Redis queue depth

Backing services:

  • Postgres 15+ — execution metadata, credentials, workflows. HA via managed (Cloud SQL / RDS / Aiven) or a Postgres operator (CloudNativePG).
  • Redis 7+ — job queue (BullMQ). HA via managed or Redis Operator.
  • Object storage — S3, MinIO, or GCS for binary data (PDFs, images, videos).
  • External Secrets — pull credentials from Vault / AWS Secrets Manager / GCP Secret Manager.

A minimal-but-real Helm values.yaml

# values.yaml — production-shaped n8n
image:
  repository: n8nio/n8n
  tag: 1.72.0            # pin, never :latest

env:
  EXECUTIONS_MODE: queue
  QUEUE_BULL_REDIS_HOST: redis
  DB_TYPE: postgresdb
  DB_POSTGRESDB_HOST: pg-primary
  DB_POSTGRESDB_DATABASE: n8n
  N8N_ENCRYPTION_KEY: <from-external-secret>
  N8N_DEFAULT_BINARY_DATA_MODE: s3
  N8N_EXTERNAL_STORAGE_S3_HOST: s3.amazonaws.com
  N8N_EXTERNAL_STORAGE_S3_BUCKET_NAME: n8n-binary-prod
  N8N_METRICS: 'true'
  N8N_LOG_LEVEL: info
  N8N_PAYLOAD_SIZE_MAX: '16'
  N8N_RUNNERS_ENABLED: 'true'

main:
  replicas: 2
  resources: { requests: { cpu: 500m, memory: 1Gi }, limits: { memory: 2Gi } }

webhook:
  replicas: 3
  resources: { requests: { cpu: 250m, memory: 512Mi } }

worker:
  minReplicas: 3
  maxReplicas: 40
  resources: { requests: { cpu: 500m, memory: 1Gi }, limits: { memory: 2Gi } }
Pro tip: Pin the image tag. A silent minor bump has broken node behavior on more than one client — see the upgrade section.

Autoscaling that responds to actual load — KEDA + Redis

CPU-based HPA lags real load by 30–60 seconds because workers spend most of their time waiting on I/O, not burning CPU. Scale on queue depth instead:

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: n8n-worker
spec:
  scaleTargetRef:
    name: n8n-worker
  pollingInterval: 15
  cooldownPeriod: 120
  minReplicaCount: 3
  maxReplicaCount: 40
  triggers:
    - type: redis
      metadata:
        address: redis.n8n.svc.cluster.local:6379
        listName: bull:jobs:wait
        listLength: '20'
        activationListLength: '5'
  • listLength: 20 — one extra worker per 20 queued jobs.
  • activationListLength: 5 — don't wake workers for tiny bursts.
  • cooldownPeriod: 120 — wait 2 min before scaling down; avoids thrash during bursty traffic.

If workers OOM under load, cap concurrency per worker via N8N_CONCURRENCY_PRODUCTION_LIMIT and let KEDA add replicas instead — vertical scaling is a trap here.

Postgres — HA, sizing, and the queries that will bite you

  • Managed (Cloud SQL / RDS / Aiven) is the default answer. Self-managed via CloudNativePG is fine if you already operate Postgres.
  • Same region, same AZ or adjacent — network latency to Postgres dominates workflow p95.
  • Storage: budget 1–5GB per 100k executions with default retention. Executions are the storage hog.
  • Set EXECUTIONS_DATA_PRUNE=true and EXECUTIONS_DATA_MAX_AGE=336 (14 days) — or you'll wake up to a full disk.
  • Enable pg_stat_statements; the n8n execution history queries are the usual slow-query culprits.

For the deeper Postgres playbook see Postgres for product engineers.

Redis — small, hot, and boring

  • Redis is only a queue here — no persistence required, but enable AOF appendonly for safety.
  • 1–2 GB memory is plenty for most deployments; alert at 70%.
  • Managed Redis is fine; Sentinel or Cluster only if you already run them.
  • Never point n8n at a shared Redis with other apps — noisy-neighbor issues are painful to debug.

Binary data — S3, never the database

Storing binary data in Postgres bloats the DB, slows every query, and makes backups painful. Configure S3 mode from day one:

env:
  N8N_DEFAULT_BINARY_DATA_MODE: s3
  N8N_EXTERNAL_STORAGE_S3_BUCKET_NAME: n8n-binary-prod
  N8N_EXTERNAL_STORAGE_S3_BUCKET_REGION: eu-west-1
  N8N_EXTERNAL_STORAGE_S3_ACCESS_KEY: <from-secret>
  N8N_EXTERNAL_STORAGE_S3_ACCESS_SECRET: <from-secret>
  • Lifecycle rule: expire binary objects after 30 days unless a workflow tags them for longer retention.
  • Bucket policy: private, server-side encryption on, versioning on.
  • If regulated: object lock in compliance mode; matches the compliance blueprint in workflow automation blueprints.

Secrets, credentials, and the encryption key

n8n encrypts every stored credential with a single symmetric key (N8N_ENCRYPTION_KEY). Lose it and every credential in the database is unrecoverable — you re-enter them all by hand.

  • Store the key in AWS Secrets Manager / GCP Secret Manager / Vault. Never commit, never bake into an image.
  • Sync into the cluster via External Secrets Operator.
  • Snapshot the key alongside the DB backup, encrypted with a separate KMS key. See the backups section.
  • Rotation is possible but painful — you re-encrypt every credential. Document the runbook; rehearse it once so it isn't the first time in a real incident.

Backups that actually restore

  1. Nightly logical backuppg_dump or your managed DB's export, uploaded to object storage with 30-day retention.
  2. Continuous WAL archiving for PITR — restore to any point in the last 7 days.
  3. Encryption-key backup — versioned copy in a separate KMS/account, protected by IAM policy.
  4. Binary data — bucket versioning + cross-region replication for the S3 bucket.
  5. Monthly game day — restore into a scratch namespace, boot n8n, run a smoke workflow. Time it. Document it. If you skip this, your backup is fiction.
Watch out: The single most common self-hosting outage I fix: 'we have backups' but nobody has ever restored. Schedule the drill; put it on the platform team's calendar; make skipping it visible.

Zero-downtime upgrades

  1. Read the n8n changelog for the target version — flag any breaking node changes (Code node, Function node, HTTP Request behavior).
  2. Bump image tag in a staging cluster; run the full smoke workflow suite.
  3. In prod: roll workers first with a max-surge=1 rolling update. KEDA will replace them one at a time.
  4. Drain the webhook deployment behind a load balancer; roll it.
  5. Roll main last. Because main is leader-elected, expect a 5–10s cron gap — schedule the roll outside cron-critical windows.
Pro tip: Keep a one-version rollback image tag warm in your registry. Rollbacks under pressure are 10× easier if the previous image is already cached on nodes.

Observability — the metrics that catch real problems

  • n8n Prometheus metrics — enable N8N_METRICS=true; scrape /metrics.
  • Queue depth (Redis LLEN bull:jobs:wait) — if it climbs faster than workers scale, you have a bottleneck downstream (usually a slow HTTP integration).
  • Execution p95 duration per workflow — regressions after a deploy are your #1 signal.
  • Failed executions rate — pair with the DLQ pattern from n8n error handling.
  • Postgres connections — n8n opens a lot; use PgBouncer in transaction mode.
  • Node OOM / restart count — sustained restarts mean per-execution memory limits are too tight or one workflow leaks.

Bridge into OTLP tracing per execution; see observability for AI systems for the tracing recipes.

Security hardening

  • Terminate TLS at the ingress; force HTTPS.
  • Basic auth or SSO in front of the editor UI — the editor is not designed to be exposed to the internet.
  • Network policies: workers can talk to Redis + Postgres + egress; the editor is internal-only.
  • Pin runners (N8N_RUNNERS_ENABLED=true) to isolate Code/Function-node execution.
  • Rotate DB and Redis credentials quarterly via External Secrets.
  • Log every credential-touch event to your SIEM; the audit trail matters when things go wrong.

Cost — what a real deployment actually spends

ComponentSmall (10k exec/day)Medium (100k)Large (1M+)
main + webhook$40$120$400
worker (autoscaled)$80$400$2,000
Postgres (managed HA)$120$300$900
Redis (managed)$40$80$250
S3 (binary + backups)$10$60$400
Egress / LB$30$100$500
Total (USD/month)~$320~$1,060~$4,450

Compare to Cloud pricing at those tiers before committing — self-hosting wins on scale, not on small deployments.

Failure modes and their fixes

SymptomLikely causeFix
Queue depth grows despite workers scalingDownstream integration slow / rate-limitedAdd per-workflow concurrency cap; retry with jitter
Workers OOM under loadOne heavy workflowCap worker concurrency; split the workflow
Main pod restart loopDB connection storm on bootPgBouncer in transaction mode
Execution history query slowMissing prune / no indexesEnable prune; add index on executed_at
Credentials can't decrypt after restoreEncryption key mismatchRestore key from KMS backup
Cron jobs firing twiceTwo mains without leader lockEnsure main has leader election enabled

A production checklist you can print

  1. Queue mode on; main/webhook/worker split into three deployments.
  2. KEDA autoscaling workers on Redis queue depth.
  3. Postgres HA (managed or CloudNativePG) with PgBouncer.
  4. Redis with AOF, dedicated to n8n.
  5. S3 for binary data with lifecycle + versioning.
  6. External Secrets for all credentials; encryption key in KMS with backup.
  7. Nightly logical backup + WAL PITR; monthly restore drill scheduled.
  8. Prometheus metrics scraped; queue depth + p95 dashboards live.
  9. OTLP tracing bridged into your central observability plane.
  10. Ingress with TLS, SSO on the editor, network policies enforced.
  11. Runbook for encryption-key rotation rehearsed once end-to-end.

Where to go next

#n8n#Kubernetes#Self-hosting#Reliability#KEDA#Postgres#Redis

Frequently asked

Docker Compose or Kubernetes?

Compose is fine for a single node and low volume — cheap, easy, boring. Move to Kubernetes when you need HA, autoscaling, or your team already operates it. Don't adopt K8s for n8n alone.

What Postgres size do I need?

Executions dominate. Budget ~1–5 GB per 100k executions at default retention, prune to 14 days, and monitor the pruning job — a stalled pruner fills the disk quietly.

Do I need queue mode below 10k executions/day?

Not strictly, but queue mode makes upgrades and scaling much less painful. If you expect growth, ship it in queue mode from day one — retrofitting later is more work than starting there.

How do I handle a lost N8N_ENCRYPTION_KEY?

You don't — you re-enter every credential by hand. Backup the key in a separate KMS/account, rehearse the restore, and treat the key with the same care as your root DB password.

Can I run everything in one cluster with other apps?

Yes — dedicate a namespace, apply resource quotas, and use network policies. But keep Redis dedicated to n8n; noisy-neighbor Redis issues are a nightmare to debug.

How do I test-drill a restore without touching prod?

Spin up a scratch namespace, point a fresh n8n install at a restored Postgres + a fresh Redis, load the KMS-backed encryption key, and run a smoke workflow. Time it end-to-end. Do this monthly.

Building something similar?

I help teams ship production-grade AI agents, n8n workflows, and data platforms. Let's talk about what you're building.

Work with me
Islam Gamal
Written by

Islam Gamal

AI, Data & Automation Engineer. I design and ship production AI agents, n8n workflows, and cloud data platforms — with a focus on reliability, cost, and measurable business impact. Founder of Tashghil and Tek bil Arabi.

More from Islam Gamal

Browse every article by Islam Gamal on the author page.