Self-Hosting n8n with Docker: A Complete Setup Guide
If you want to run n8n self host docker in a way that survives real traffic, you need more than docker run n8nio/n8n. That single command gets you a working editor in under a minute, but it stores everything in SQLite inside an ephemeral container, has no HTTPS, and loses your encryption key on the next restart if you did not set it explicitly. This guide walks through a self-hosted n8n setup with Docker Compose, a real Postgres database, persistent volumes, environment-based configuration, and a reverse proxy for TLS, the same shape you would actually put in front of production workflows.
n8n is a workflow automation tool, similar in spirit to Zapier or Make, but self-hostable and node-based, with a visual editor for chaining triggers, HTTP calls, transforms, and integrations. Self-hosting it with Docker gives you control over data residency, no per-execution pricing, and the ability to install community nodes or custom nodes that the cloud version restricts. The tradeoff is that you own uptime, backups, and upgrades.
Why self-host n8n instead of using n8n Cloud
Before setting anything up, it is worth being clear on the tradeoff, because "self host docker" is more operational work than it looks like on the landing page.
Reasons people choose to self-host n8n:
- No execution-based billing. You pay for the VM or server, not per workflow run.
- Data stays on infrastructure you control, which matters for anything touching customer PII or internal systems.
- Full access to community nodes and the ability to write custom nodes in TypeScript.
- Network-level access to internal services (a database on a private VPC, an internal API) without exposing them publicly.
Reasons to stay on n8n Cloud instead:
- You do not want to own patching, backups, or scaling.
- Your workflow volume is low enough that cloud pricing is cheaper than a server plus your time.
- You need SSO/SAML or other enterprise features that are easier to get from the hosted plan.
If you are past the "just trying it out" stage and plan to run n8n for real automations (webhook receivers, scheduled jobs hitting production APIs), self-hosting with Docker is the standard path. The rest of this guide assumes that decision is made.
Prerequisites
You need:
- A Linux server (Ubuntu 22.04/24.04 is the most common choice) with at least 2 vCPU and 2GB RAM. n8n itself is light, but workflow executions and Postgres both need headroom.
- Docker Engine and the Docker Compose plugin installed.
- A domain name (or subdomain) you can point at the server, needed for webhooks to work reliably and for HTTPS.
- Basic familiarity with
.envfiles and Docker Compose.
Install Docker if you have not already:
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USERLog out and back in so the group change takes effect, then confirm:
docker --version
docker compose versionStep 1: Project structure and environment file
Create a working directory and the folders n8n and Postgres will use for persistent data.
mkdir -p ~/n8n-stack/n8n_data ~/n8n-stack/postgres_data
cd ~/n8n-stackGenerate an encryption key now, before you create any credentials in the editor. n8n uses this key to encrypt stored credentials (API keys, OAuth tokens) at rest. If you lose it, every saved credential becomes unreadable and you will have to re-enter them all.
openssl rand -hex 32Save that output somewhere durable outside the server too (a password manager), not just in the .env file.
Create .env:
# Postgres
POSTGRES_USER=n8n
POSTGRES_PASSWORD=change_this_to_a_strong_password
POSTGRES_DB=n8n
# n8n core
N8N_ENCRYPTION_KEY=paste_the_openssl_output_here
N8N_HOST=n8n.yourdomain.com
N8N_PROTOCOL=https
N8N_PORT=5678
WEBHOOK_URL=https://n8n.yourdomain.com/
GENERIC_TIMEZONE=America/New_York
# Basic auth on the editor (optional but recommended if not behind SSO)
N8N_BASIC_AUTH_ACTIVE=true
N8N_BASIC_AUTH_USER=admin
N8N_BASIC_AUTH_PASSWORD=another_strong_passwordReplace the domain, timezone, and passwords with your own values. WEBHOOK_URL matters more than people expect: n8n uses it to generate the webhook URLs shown in the editor, and if it is wrong, every webhook-triggered workflow you build will show the wrong endpoint.
Step 2: docker-compose.yml with Postgres
The default n8n image uses SQLite, which works for testing but is not safe under concurrent writes and does not give you point-in-time backups. Switch to Postgres from the start.
services:
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- ./postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
n8n:
image: docker.n8n.io/n8nio/n8n:latest
restart: unless-stopped
ports:
- "127.0.0.1:5678:5678"
environment:
DB_TYPE: postgresdb
DB_POSTGRESDB_HOST: postgres
DB_POSTGRESDB_PORT: 5432
DB_POSTGRESDB_DATABASE: ${POSTGRES_DB}
DB_POSTGRESDB_USER: ${POSTGRES_USER}
DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD}
N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
N8N_HOST: ${N8N_HOST}
N8N_PROTOCOL: ${N8N_PROTOCOL}
N8N_PORT: ${N8N_PORT}
WEBHOOK_URL: ${WEBHOOK_URL}
GENERIC_TIMEZONE: ${GENERIC_TIMEZONE}
N8N_BASIC_AUTH_ACTIVE: ${N8N_BASIC_AUTH_ACTIVE}
N8N_BASIC_AUTH_USER: ${N8N_BASIC_AUTH_USER}
N8N_BASIC_AUTH_PASSWORD: ${N8N_BASIC_AUTH_PASSWORD}
volumes:
- ./n8n_data:/home/node/.n8n
depends_on:
postgres:
condition: service_healthyNotice 127.0.0.1:5678:5678 in the ports mapping: n8n is bound to localhost only. It is not exposed to the internet directly. That job belongs to the reverse proxy in the next step, which terminates TLS and forwards to this local port.
Bring the stack up:
docker compose up -d
docker compose logs -f n8nWatch for Editor is now accessible via: http://localhost:5678/ and no Postgres connection errors. Stop watching logs with Ctrl+C once it looks healthy; the containers keep running in the background.
Step 3: Reverse proxy and HTTPS with Caddy
Webhooks, OAuth callbacks, and the editor itself should all run over HTTPS. Caddy is the simplest option here because it handles Let's Encrypt certificate issuance automatically with almost no config.
Install Caddy on the host (outside the Docker Compose stack, or as its own container, either works; here it is a system package):
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddyEdit /etc/caddy/Caddyfile:
n8n.yourdomain.com {
reverse_proxy 127.0.0.1:5678
}Reload Caddy:
sudo systemctl reload caddyMake sure your domain's DNS A record already points at the server's public IP before this step, otherwise certificate issuance fails. Once DNS resolves and Caddy reloads, visiting https://n8n.yourdomain.com should show the n8n login screen with a valid certificate, issued and renewed automatically.
If you would rather run everything inside Docker, swap Caddy for an nginx-proxy plus acme-companion pair, or add a Caddy container to the same compose file with a caddy_data volume. The standalone install above is simplest for a single-app server.
Step 4: Queue mode for reliability at scale
The setup above runs n8n in single-process "regular" mode, fine for low-to-moderate workflow volume. Once you have enough concurrent executions that a slow workflow blocks others, or you want to survive a container restart mid-execution, switch to queue mode with Redis and separate worker containers.
Add Redis and a worker service to docker-compose.yml:
services:
redis:
image: redis:7-alpine
restart: unless-stopped
volumes:
- ./redis_data:/data
n8n-worker:
image: docker.n8n.io/n8nio/n8n:latest
restart: unless-stopped
command: worker
environment:
DB_TYPE: postgresdb
DB_POSTGRESDB_HOST: postgres
DB_POSTGRESDB_PORT: 5432
DB_POSTGRESDB_DATABASE: ${POSTGRES_DB}
DB_POSTGRESDB_USER: ${POSTGRES_USER}
DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD}
N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
EXECUTIONS_MODE: queue
QUEUE_BULL_REDIS_HOST: redis
depends_on:
- postgres
- redisAdd EXECUTIONS_MODE: queue and QUEUE_BULL_REDIS_HOST: redis to the main n8n service's environment as well, so the editor process enqueues jobs instead of running them inline. Scale workers with docker compose up -d --scale n8n-worker=3 once you have confirmed one worker runs cleanly.
Most people running a handful of internal automations do not need this. Add it when you actually see execution queueing or timeouts, not preemptively.
Step 5: Backups
Two things need backing up: the Postgres database (workflows, credentials, execution history) and the n8n_data volume (binary data mode files, if you use that, plus some config).
A simple cron-driven Postgres dump:
0 3 * * * docker exec n8n-stack-postgres-1 pg_dump -U n8n n8n | gzip > /backups/n8n-$(date +\%F).sql.gzAdjust the container name to whatever docker compose ps shows you, it is usually <directory-name>-postgres-1. Keep at least 7 days of dumps and copy them off the server, S3 or a similar object store works well, since a backup that lives on the same disk as the database does not protect you from disk failure.
Restoring from a dump:
gunzip -c /backups/n8n-2026-07-01.sql.gz | docker exec -i n8n-stack-postgres-1 psql -U n8n -d n8nStep 6: Updating n8n safely
n8n ships frequent releases. Pin to a specific version tag instead of :latest in production once your workflows are stable, so an upgrade only happens when you choose it:
image: docker.n8n.io/n8nio/n8n:1.72.1To upgrade:
docker compose pull n8n
docker compose up -d n8nCheck the n8n release notes for breaking changes before jumping more than a few minor versions at once, and take a Postgres dump right before upgrading. Database migrations run automatically on startup and are one-directional, you cannot roll back to an older n8n version against a migrated database without restoring that backup first.
Common problems and fixes
Webhooks return 404 or never fire. Almost always a WEBHOOK_URL mismatch. It has to match the public HTTPS URL exactly, including the trailing slash. Check it under Settings inside the editor too, some versions cache it separately from the environment variable until a restart.
"Encryption key mismatch" after a restart. You did not set N8N_ENCRYPTION_KEY explicitly, so n8n generated a random one and stored it in n8n_data, then the volume was recreated or mounted from a different path. Always set the key explicitly in .env, never rely on the auto-generated one.
Postgres connection refused on first boot. The n8n container started before Postgres finished initializing. The depends_on: condition: service_healthy block in the compose file above handles this, but if you removed the healthcheck, add it back.
Editor is reachable but OAuth-based nodes (Google, Slack, etc.) fail to connect. OAuth redirect URIs are registered against the public HTTPS domain. If you are testing through http://server-ip:5678 instead of the real domain, OAuth callbacks will not match what you registered with the provider. Always test OAuth-based credentials through the final domain.
FAQ
Is n8n self host docker free? The n8n Community Edition (Sustainable Use License) that ships in the standard Docker image is free to self-host, with some feature and node restrictions around advanced enterprise capabilities. Check n8n's current licensing page for exactly what falls under Enterprise before assuming a feature is included.
Do I need Postgres, or can I stick with SQLite? SQLite is fine for a local trial with one user. For anything with concurrent workflow executions or that you care about not corrupting, use Postgres from the start, migrating a live SQLite database later is more work than starting correctly.
Can I run n8n self-hosted without a domain name? You can run it on an IP address for local testing, but webhooks, OAuth credentials, and HTTPS all expect a stable domain. Get at least a cheap subdomain before doing anything beyond local experimentation.
How much server capacity do I actually need? For light usage (a handful of scheduled workflows, occasional webhooks), a 2 vCPU / 2GB RAM VM handles it comfortably. Scale up when you see workflows with heavy data transforms, large file handling, or high webhook concurrency.
What is the difference between regular mode and queue mode? Regular mode runs everything in the main n8n process; simple to run, fine for low volume. Queue mode offloads execution to separate worker processes via Redis, so the editor stays responsive under load and you can scale workers horizontally. Switch when execution volume or workflow duration starts causing delays.
Where are credentials stored, and are they encrypted? Credentials are stored in the Postgres database, encrypted using the value in N8N_ENCRYPTION_KEY. Anyone with both database access and the encryption key can decrypt them, so treat the key with the same care as a root password and keep database backups access-controlled.
BootcampA 30-day guided bootcamp: build, harden and ship a production autonomous agent from scratch.
AI AgentsUnderstand how AI agents really work: the loop, the tools, the memory, and why most agent projects fail.