How to Deploy n8n with Docker on Any VPS (2026 Guide)
What You’ll Need
- n8n Cloud or self-hosted n8n (we’re covering self-hosted)
- Hetzner VPS , Contabo VPS , or DigitalOcean
- A domain name (optional, but recommended—grab one at Namecheap )
- Basic command-line comfort
- 2GB+ RAM and 20GB+ storage on your VPS
Table of Contents
- Why Self-Host n8n on a VPS?
- Setting Up Your VPS
- Installing Docker
- Deploying n8n with Docker Compose
- Configuring SSL and Your Domain
- Running Your First Workflow
- Getting Started with Production
Why Self-Host n8n on a VPS?
I made the switch to self-hosted n8n about two years ago, and honestly, it changed how I build automation. You get complete control over your workflows, no per-execution pricing limits, and the ability to run custom code without restrictions. Plus, if you’re already paying for a VPS for automation , you can run n8n alongside other services—databases, APIs, webhooks—all in one place.
The deployment is surprisingly straightforward with Docker. I’ll walk you through it step-by-step so you can have a production-ready n8n instance running in under an hour.
Setting Up Your VPS
Start with a fresh Ubuntu 22.04 LTS instance. If you’re comparing hosting options, I’ve got a detailed breakdown of VPS choices for automation (Hetzner vs Contabo vs Railway) that might help you decide. For this guide, I’m assuming you’ve already provisioned your server and can SSH into it.
Connect to your VPS:
ssh root@your_vps_ip_address
Update your system packages:
apt update && apt upgrade -y
Create a non-root user for security (optional but recommended):
adduser n8nuser
usermod -aG sudo n8nuser
su - n8nuser
Installing Docker
Docker is the easiest way to run n8n without dependency hell. Install the official Docker repository:
sudo apt install -y ca-certificates curl gnupg lsb-release
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Install Docker and Docker Compose:
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
Verify the installation:
docker --version
docker compose version
Add your user to the Docker group so you don’t need sudo every time:
sudo usermod -aG docker $USER
newgrp docker
Deploying n8n with Docker Compose
Create a project directory:
mkdir -p ~/n8n-deployment
cd ~/n8n-deployment
Create a .env file for configuration:
cat > .env << 'EOF'
N8N_HOST=your-domain.com
N8N_PORT=5678
N8N_PROTOCOL=https
NODE_ENV=production
N8N_SECURE_COOKIE=true
N8N_ENCRYPTION_KEY=$(openssl rand -hex 32)
GENERIC_TIMEZONE=UTC
DB_TYPE=postgres
DB_POSTGRESDB_HOST=postgres
DB_POSTGRESDB_PORT=5432
DB_POSTGRESDB_DATABASE=n8n
DB_POSTGRESDB_USER=n8n
DB_POSTGRESDB_PASSWORD=$(openssl rand -hex 16)
WEBHOOK_URL=https://your-domain.com/webhook
EOF
Replace your-domain.com with your actual domain. If you don’t have a domain yet, grab one from Namecheap
for cheap.
Now create the docker-compose.yml file:
cat > docker-compose.yml << 'EOF'
version: '3.8'
services:
postgres:
image: postgres:15-alpine
container_name: n8n-postgres
environment:
POSTGRES_USER: ${DB_POSTGRESDB_USER}
POSTGRES_PASSWORD: ${DB_POSTGRESDB_PASSWORD}
POSTGRES_DB: ${DB_POSTGRESDB_DATABASE}
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- n8n-net
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_POSTGRESDB_USER}"]
interval: 10s
timeout: 5s
retries: 5
n8n:
image: n8nio/n8n:latest
container_name: n8n-app
restart: unless-stopped
ports:
- "5678:5678"
environment:
- DB_TYPE=${DB_TYPE}
- DB_POSTGRESDB_HOST=${DB_POSTGRESDB_HOST}
- DB_POSTGRESDB_PORT=${DB_POSTGRESDB_PORT}
- DB_POSTGRESDB_DATABASE=${DB_POSTGRESDB_DATABASE}
- DB_POSTGRESDB_USER=${DB_POSTGRESDB_USER}
- DB_POSTGRESDB_PASSWORD=${DB_POSTGRESDB_PASSWORD}
- N8N_HOST=${N8N_HOST}
- N8N_PORT=${N8N_PORT}
- N8N_PROTOCOL=${N8N_PROTOCOL}
- NODE_ENV=${NODE_ENV}
- N8N_SECURE_COOKIE=${N8N_SECURE_COOKIE}
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
- GENERIC_TIMEZONE=${GENERIC_TIMEZONE}
- WEBHOOK_URL=${WEBHOOK_URL}
volumes:
- n8n_data:/home/node/.n8n
networks:
- n8n-net
depends_on:
postgres:
condition: service_healthy
nginx:
image: nginx:alpine
container_name: n8n-nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
networks:
- n8n-net
depends_on:
- n8n
volumes:
postgres_data:
n8n_data:
networks:
n8n-net:
driver: bridge
EOF
Create the Nginx configuration file:
cat > nginx.conf << 'EOF'
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
types_hash_max_size 2048;
gzip on;
upstream n8n {
server n8n:5678;
}
server {
listen 80;
server_name your-domain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name your-domain.com;
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
client_max_body_size 50M;
location / {
proxy_pass http://n8n;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
}
EOF
💡 Fast-Track Your Project: Don’t want to configure this yourself? I build custom n8n pipelines and bots. Message me with code SYS3-HUGO.
Configuring SSL and Your Domain
First, point your domain to your VPS IP address in your DNS provider. Create an SSL certificate using Let’s Encrypt:
sudo apt install -y certbot python3-certbot-nginx
sudo mkdir -p ssl
cd ssl
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout key.pem -out cert.pem -subj "/CN=your-domain.com"
cd ..
For production, use Let’s Encrypt instead:
sudo certbot certonly --standalone -d your-domain.com --non-interactive --agree-tos -m your-email@example.com
sudo cp /etc/letsencrypt/live/your-domain.com/fullchain.pem ssl/cert.pem
sudo cp /etc/letsencrypt/live/your-domain.com/privkey.pem ssl/key.pem
sudo chown -R $USER:$USER ssl/
Update your nginx.conf with your actual domain name (replace your-domain.com twice in the file).
Start the deployment:
docker compose up -d
Check that all containers are running:
docker compose ps
You should see three services: n8n-postgres, n8n-app, and n8n-nginx. View logs if anything fails:
docker compose logs -f n8n-app
Running Your First Workflow
Open your browser and go to https://your-domain.com. You’ll see the n8n setup screen. Create your admin account and log in.
Once inside, create a simple test workflow to verify everything works. Click the + button to add a trigger node. Select Webhook:
{
"method": "POST",
"path": "webhook/test"
}
Add an HTTP Request node:
Want to automate this yourself?
Start with n8n Cloud (free tier available) or self-host on a Hetzner VPS for full control.
📬 Get Weekly Automation Tips
One email per week with tutorials, tools, and workflows. No spam, unsubscribe anytime.