Self-hosting
How to Self-Host n8n on a VPS (Production-Ready in 2026)
A complete, battle-tested guide to self-hosting n8n on a VPS with Docker, Postgres and automatic HTTPS — including security, backups and updates.
Running n8n yourself instead of paying for the Cloud plan gives you three things you can’t buy back later: no per-execution pricing, full control over your data, and the ability to run as many workflows as your server can handle. The catch is that a naive docker run setup will bite you the first time you restart the server, lose your encryption key, or expose the editor to the open internet.
This guide walks through a setup we actually run in production: n8n + Postgres + Caddy, with automatic HTTPS, persistent data, a locked-down firewall, and a backup routine. Budget about 30–45 minutes.
What you’ll need
- A VPS with at least 2 GB RAM (n8n + Postgres are comfortable here; 1 GB works for light use but swaps under load).
- A domain name you can add a DNS record to.
- 30 minutes and a coffee.
Step 1 — Create the VPS
Almost any provider works, but for self-hosting n8n the sweet spot is a small, cheap instance you fully control. We run ours on Hetzner because the price-to-performance is hard to beat in Europe; DigitalOcean is the friendlier option if you’re new to servers.
Create an Ubuntu 24.04 server, add your SSH key during setup, and note the public IP. Everything below assumes you’re connected as a sudo-capable user:
ssh root@YOUR_SERVER_IP
Step 2 — Point your domain at the server
In your DNS provider, add an A record for the subdomain you want to use, pointing at the server’s IP:
Type Name Value
A n8n YOUR_SERVER_IP
So n8n will live at n8n.yourdomain.com. DNS can take a few minutes to propagate — you can carry on while it does.
Step 3 — Install Docker
Use Docker’s official convenience script, then add your user to the docker group so you don’t need sudo for every command:
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER
newgrp docker
docker --version
The Compose plugin ships with modern Docker, so docker compose version should already work.
Step 4 — Write the Compose stack
Create a project folder and three files. This is the core of the setup.
mkdir ~/n8n && cd ~/n8n
docker-compose.yml — n8n, Postgres for storage, and Caddy as a reverse proxy that handles TLS certificates for you:
services:
postgres:
image: postgres:16
restart: always
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: always
environment:
- N8N_HOST=${N8N_HOST}
- N8N_PORT=5678
- N8N_PROTOCOL=https
- NODE_ENV=production
- WEBHOOK_URL=https://${N8N_HOST}/
- GENERIC_TIMEZONE=${GENERIC_TIMEZONE}
- TZ=${GENERIC_TIMEZONE}
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
- N8N_PROXY_HOPS=1
- N8N_RUNNERS_ENABLED=true
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_DATABASE=${POSTGRES_DB}
- DB_POSTGRESDB_USER=${POSTGRES_USER}
- DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
volumes:
- n8n_data:/home/node/.n8n
depends_on:
postgres:
condition: service_healthy
caddy:
image: caddy:2
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
depends_on:
- n8n
volumes:
postgres_data:
n8n_data:
caddy_data:
caddy_config:
Caddyfile — three lines, and Caddy fetches and renews a Let’s Encrypt certificate automatically:
n8n.yourdomain.com {
reverse_proxy n8n:5678
}
Step 5 — Set your secrets
Create a .env file next to the Compose file. The most important line is the encryption key: n8n encrypts all your saved credentials with it, and if you lose it, those credentials become unrecoverable.
# generate a strong encryption key and a DB password
echo "N8N_ENCRYPTION_KEY=$(openssl rand -hex 32)" >> .env
echo "POSTGRES_PASSWORD=$(openssl rand -hex 24)" >> .env
Then add the rest, so your final .env looks like this:
N8N_HOST=n8n.yourdomain.com
GENERIC_TIMEZONE=Europe/Madrid
N8N_ENCRYPTION_KEY=... # generated above — back this up
POSTGRES_USER=n8n
POSTGRES_PASSWORD=... # generated above
POSTGRES_DB=n8n
Step 6 — Launch
docker compose up -d
docker compose ps
docker compose logs -f n8n
Watch the logs until you see n8n report it’s listening. Open https://n8n.yourdomain.com in a browser — Caddy will already have issued the certificate, so you get a padlock with no manual steps. On first load, n8n asks you to create the owner account. Use a strong password; this is the front door to everything.
Step 7 — Lock down the firewall
Right now the OS firewall may be wide open. Restrict it to SSH and web traffic only:
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status
That’s it for exposure: SSH for you, 80/443 for Caddy, nothing else. Postgres and n8n’s port are only reachable from inside Docker.
Step 8 — Backups that actually restore
A backup you’ve never restored isn’t a backup. Two things need to survive a server dying: the Postgres database and the encryption key.
Create backup.sh:
#!/usr/bin/env bash
set -euo pipefail
cd ~/n8n
STAMP=$(date +%F)
docker compose exec -T postgres pg_dump -U n8n n8n | gzip > "n8n-db-$STAMP.sql.gz"
cp .env "n8n-env-$STAMP.bak"
echo "Backup written: n8n-db-$STAMP.sql.gz"
Make it executable and schedule it nightly with cron:
chmod +x ~/n8n/backup.sh
( crontab -l 2>/dev/null; echo "0 3 * * * ~/n8n/backup.sh" ) | crontab -
Then copy those files off the server — to object storage, another machine, anywhere but the box itself. Test a restore at least once: spin up a fresh stack, load the dump with psql, reuse the same encryption key, and confirm your credentials decrypt.
Updating n8n
n8n ships often. To update, pull the new image and recreate the containers — your data lives in volumes, so it persists:
cd ~/n8n
docker compose pull
docker compose up -d
When not to self-host
We’re pro-self-hosting, but honesty first. Stay on n8n Cloud (or a managed host) if:
- You don’t want to own server maintenance, security patches and backups.
- Your workflows are mission-critical and you have no one to respond at 3 a.m.
- You need the latest enterprise features without managing them yourself.
If you’d rather have most of the control without managing a raw server, a platform like Railway sits in the middle — you deploy n8n from a template and it handles the host.
Where to go next
You now have a production-shaped n8n you fully own. From here:
- Harden it further with two-factor auth and external secrets.
- Connect it to an LLM and build your first AI-powered workflow.
- Compare it head-to-head with the hosted tools to see what you gained.
We cover each of these in the self-hosting and comparisons guides. If you hit a snag with this setup, tell us — we keep this guide current.