How to Install n8n on a VPS: A Step-by-Step Docker Guide
n8n is an automation platform where you build workflows from blocks: receive an email, write it to a spreadsheet, send a Telegram message, call an AI model. Your own n8n instance on a VPS costs less than a cloud subscription and has no limit on workflow executions.
Self-hosting has other benefits too: your data and service credentials stay on your server, you can install any community nodes, and you can connect to internal services. For internal business use, n8n is free under the Sustainable Use License; the restrictions apply to reselling n8n as a service.
This guide installs n8n the official way: Docker Compose, a PostgreSQL database, a separate container for running code, Nginx and a free SSL certificate. Commands are for Ubuntu 22.04/24.04.
What You Need
- A VPS with 2 GB RAM and 2 vCPU. That's enough for dozens of workflows. If you plan to process many files, run workflows in parallel or handle large data volumes, get 4 GB.
- At least 20 GB of disk. The n8n database grows with execution history.
- A domain or subdomain, such as
n8n.example.com. Without HTTPS, n8n login won't work properly and external services can't send data to your webhooks. - A prepared server: a sudo user, SSH key login and a firewall. If the server is new, complete the initial VPS setup first.
Step 1. Install Docker
n8n is officially distributed as a Docker image, so start with Docker. The official script installs Docker Engine and the Docker Compose plugin from Docker's repository:
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER
Reconnect over SSH so the docker group takes effect, then check the installation:
docker --version
docker compose version
Both commands should print a version number with no errors.
Step 2. Point Your Domain to the Server
In your domain's DNS settings, create an A record: name n8n, value — your VPS IP. After a few minutes, check it:
dig +short n8n.example.com
The command should return your server's IP. If the domain is on Cloudflare, set the record to grey cloud (DNS only) while you get the SSL certificate.
Step 3. Run n8n with PostgreSQL Using Docker Compose
By default, n8n stores data in SQLite. For a production server, PostgreSQL is better: it's more reliable with parallel executions and easier to back up. The setup below follows n8n's official template: three containers — the database, n8n itself and a separate runner that safely executes code from Code nodes (JavaScript and Python).
Create a project folder:
mkdir ~/n8n && cd ~/n8n
Create a .env file with settings and random passwords. Replace the domain and time zone with your own:
cat > .env <<EOF
N8N_VERSION=stable
N8N_DOMAIN=n8n.example.com
TIMEZONE=Europe/London
POSTGRES_USER=n8n
POSTGRES_PASSWORD=$(openssl rand -hex 24)
POSTGRES_DB=n8n
N8N_ENCRYPTION_KEY=$(openssl rand -hex 32)
RUNNERS_AUTH_TOKEN=$(openssl rand -hex 32)
EOF
chmod 600 .env
Keep a copy of .env somewhere safe. N8N_ENCRYPTION_KEY encrypts all your service connections: API keys, passwords, tokens. Without it, they can't be restored from a backup.
Create docker-compose.yml:
services:
postgres:
image: postgres:16
restart: always
environment:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=${POSTGRES_DB}
volumes:
- db_storage:/var/lib/postgresql/data
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}']
interval: 5s
timeout: 5s
retries: 10
n8n:
image: docker.n8n.io/n8nio/n8n:${N8N_VERSION}
restart: always
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_HOST=${N8N_DOMAIN}
- N8N_PORT=5678
- N8N_PROTOCOL=https
- WEBHOOK_URL=https://${N8N_DOMAIN}/
- N8N_PROXY_HOPS=1
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
- GENERIC_TIMEZONE=${TIMEZONE}
- TZ=${TIMEZONE}
- N8N_RUNNERS_MODE=external
- N8N_RUNNERS_AUTH_TOKEN=${RUNNERS_AUTH_TOKEN}
- N8N_RUNNERS_BROKER_LISTEN_ADDRESS=0.0.0.0
volumes:
- n8n_storage:/home/node/.n8n
depends_on:
postgres:
condition: service_healthy
n8n-runner:
image: n8nio/runners:${N8N_VERSION}
restart: always
environment:
- N8N_RUNNERS_AUTH_TOKEN=${RUNNERS_AUTH_TOKEN}
- N8N_RUNNERS_TASK_BROKER_URI=http://n8n:5679
depends_on:
- n8n
volumes:
db_storage: n8n_storage:
What matters here:
127.0.0.1:5678:5678makes n8n reachable only from the server itself. Nginx exposes it over HTTPS. Without127.0.0.1, Docker opens the port to the internet, bypassing the firewall.WEBHOOK_URLis the address n8n uses in webhook URLs. Without it, external services get a link tolocalhost.N8N_PROXY_HOPS=1tells n8n it sits behind one proxy, so it sees visitors' real IPs.stableis the latest stable release. To avoid surprise updates, you can pin a specific version.
Start it:
docker compose up -d
docker compose logs -f n8n
Once the logs say the editor is accessible, press Ctrl+C; the containers keep running.
Step 4. Set Up Nginx and an SSL Certificate
Nginx accepts HTTPS requests from the internet and passes them to n8n. Install Nginx and Certbot for a free Let's Encrypt certificate, and open the web ports:
sudo apt install -y nginx certbot python3-certbot-nginx
sudo ufw allow 'Nginx Full'
Create the site config /etc/nginx/sites-available/n8n:
server { listen 80; server_name n8n.example.com; location / { proxy_pass http://127.0.0.1:5678; 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_buffering off; proxy_read_timeout 300s; client_max_body_size 50m; }}
The Upgrade and Connection headers are needed for WebSocket; without them the n8n editor keeps showing "Connection lost". proxy_read_timeout stops Nginx from cutting off long requests, and client_max_body_size allows uploads up to 50 MB.
Enable the site and get a certificate:
sudo ln -s /etc/nginx/sites-available/n8n /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d n8n.example.com
Certbot adds HTTPS to the config and sets up automatic renewal. Test renewal with sudo certbot renew --dry-run.
Step 5. Create the Owner Account
Open https://n8n.example.com in your browser. On first visit, n8n asks you to create the owner account: email, name and password. Do it right away: until the account exists, anyone who opens the address first can claim it.
Then turn on two-factor authentication: Settings → Personal → Enable 2FA.
To check webhooks, create a workflow with a Webhook node and look at its URL. It should start with https://n8n.example.com/webhook/, not localhost.
Step 6. Set Up Backups and Updates
Database backup. PostgreSQL stores all your workflows, credentials and execution history. Create a dump:
cd ~/n8n
mkdir -p backups
docker compose exec -T postgres pg_dump -U n8n n8n | gzip > backups/n8n-$(date +%F).sql.gz
To back up every night at 3:00 and keep copies for 14 days, add a cron job (crontab -e):
0 3 * * * cd ~/n8n && docker compose exec -T postgres pg_dump -U n8n n8n | gzip > backups/n8n-$(date +\%F).sql.gz && find backups -name '*.sql.gz' -mtime +14 -delete
Copy the backups and the .env file off the server: to S3 storage or another VPS. A backup without .env restores your workflows but not your service credentials.
Updating n8n. Back up first, then:
cd ~/n8n
docker compose pull
docker compose up -d
docker image prune -f
Before upgrading to a new major version (for example, from 2.x to 3.x), read the release notes on the n8n website: they sometimes include breaking changes for existing workflows.
n8n Security
n8n holds the keys to every connected service: email, CRM, payment systems. A compromised n8n exposes all of them at once, so it needs more protection than a typical website.
- Don't expose port 5678. Access goes only through Nginx over HTTPS; the compose file binds the port to
127.0.0.1. - Turn on 2FA for all users.
- Update n8n regularly. Vulnerabilities are found from time to time, and fixes ship in new versions.
- Don't enable dangerous nodes unless you need them. In recent versions, the Execute Command node, which runs commands on the server, is disabled by default. Keep it that way if you don't need it.
- Be careful with community nodes. They're third-party code with access to your data. Install only popular, open-source nodes.
- Restrict editor access by IP if you work from an office or over a VPN. Add
allow YOUR_IP;anddeny all;to the Nginxlocation /block. Then move webhooks to a separatelocation /webhook/block without restrictions, or external services won't be able to call them.
Common Errors
- Webhook URL shows
localhost:5678.WEBHOOK_URLisn't set. Check it in the compose file and restart:docker compose up -d. - The editor says "Connection lost". Nginx isn't passing WebSocket traffic. Check the
UpgradeandConnectionlines in the Nginx config. - Secure cookie error at login. You're opening n8n over
http://or by IP. Use only your domain overhttps://. - Service connections stop working after a migration. The new server has a different
N8N_ENCRYPTION_KEY. Copy the old key from.env. - n8n can't connect to the database after changing the password in
.env. PostgreSQL reads the password from.envonly on first start. Change the password inside the database or restore the old one in.env. - The n8n container keeps restarting. Check why:
docker compose logs --tail 100 n8n. If it's out of memory, add swap or RAM.
Checklist
FAQ
How much does self-hosted n8n cost? n8n itself is free for internal use; you pay only for the VPS. n8n Cloud charges by plan with execution limits, while your own server has no such cap.
What VPS do I need for n8n? 2 GB RAM and 2 vCPU are enough to start. If your workflows process files, use AI models or run hundreds of times an hour, get 4 GB or more.
Can I run n8n without a domain? Technically n8n will start on a bare IP, but without HTTPS, login and external webhooks won't work. A subdomain costs less than the time spent on workarounds.
How is self-hosted n8n different from n8n Cloud? The core features are the same. In the cloud, n8n handles updates and backups; on a VPS you do it yourself, but you get full control over your data and no execution limits.
How do I move workflows from n8n Cloud to my server? Export your workflows as JSON in the cloud and import them on the new server. You'll need to recreate credentials, since they're encrypted with the cloud instance's key.