n n8n

How to self-host n8n on a VPS on n8n

intermediate 12 min read Updated 2026-03-13
Quick Answer

Self-host n8n on a VPS using Docker Compose with Postgres for production reliability. Set up in 30-60 minutes on Ubuntu by installing Docker, configuring .env and docker-compose.yml, then adding a reverse proxy for HTTPS. Test webhooks to confirm readiness.

Prerequisites

  • Ubuntu 22.04+ VPS with 2-4GB RAM and 1-2 vCPUs
  • Domain name pointed to VPS IP
  • SSH access as root or sudo user
  • Basic Linux and Docker knowledge

Step-by-Step Instructions

1

Connect to VPS and Update System

SSH into your VPS as root: ssh root@your-vps-ip-address. Update packages with apt update && apt upgrade -y. For security, create a non-root user: adduser n8nuser, usermod -aG sudo n8nuser, then su - n8nuser.
Use key-based SSH authentication instead of passwords for better security.
2

Install Docker and Docker Compose

Install Docker: sudo apt install docker.io -y, start and enable it with sudo systemctl start docker and sudo systemctl enable docker, add user to docker group: sudo usermod -aG docker $USER. Install Compose plugin: sudo apt install docker-compose-plugin -y. Log out and SSH back in to apply group changes.
Verify with <code>docker --version</code> and <code>docker compose version</code>.
3

Create Project Directory

Create and enter the n8n directory: mkdir ~/n8n && cd ~/n8n. This will hold your configuration files.
4

Create .env Configuration File

Use nano or vim to create .env. Add these settings (generate encryption key with openssl rand -base64 32):
N8N_HOST=n8n.yourdomain.com
N8N_PORT=5678
WEBHOOK_URL=https://n8n.yourdomain.com/
N8N_PROTOCOL=https
GENERIC_TIMEZONE=UTC
TZ=UTC
N8N_ENCRYPTION_KEY=your-32-char-random-key-here
DB_TYPE=postgresdb
DB_POSTGRESDB_DATABASE=n8n
DB_POSTGRESDB_HOST=postgres
DB_POSTGRESDB_PORT=5432
DB_POSTGRESDB_USER=n8n
DB_POSTGRESDB_PASSWORD=your-secure-db-password
DB_POSTGRESDB_SCHEMA=public
N8N_BASIC_AUTH_ACTIVE=true
N8N_BASIC_AUTH_USER=admin
N8N_BASIC_AUTH_PASSWORD=your-secure-auth-password
Replace placeholders with your values.
Keep the .env file secure and back it up; it contains sensitive keys and passwords.
5

Create docker-compose.yml

Create docker-compose.yml with this production stack:
version: '3.8'
services:
  postgres:
    image: postgres:15-alpine
    container_name: n8n-postgres
    restart: always
    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-network
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -U ${DB_POSTGRESDB_USER} -d ${DB_POSTGRESDB_DATABASE}']
      interval: 5s
      timeout: 5s
      retries: 5
  n8n:
    image: n8nio/n8n:latest
    container_name: n8n
    restart: always
    ports:
      - '5678:5678'
    environment:
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_DATABASE=${DB_POSTGRESDB_DATABASE}
      - DB_POSTGRESDB_HOST=${DB_POSTGRESDB_HOST}
      - DB_POSTGRESDB_PORT=${DB_POSTGRESDB_PORT}
      - DB_POSTGRESDB_USER=${DB_POSTGRESDB_USER}
      - DB_POSTGRESDB_PASSWORD=${DB_POSTGRESDB_PASSWORD}
      - DB_POSTGRESDB_SCHEMA=${DB_POSTGRESDB_SCHEMA}
      - N8N_HOST=${N8N_HOST}
      - N8N_PORT=${N8N_PORT}
      - N8N_PROTOCOL=${N8N_PROTOCOL}
      - WEBHOOK_URL=${WEBHOOK_URL}
      - GENERIC_TIMEZONE=${GENERIC_TIMEZONE}
      - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
      - 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_healthy
    networks:
      - n8n-network
volumes:
  postgres_data:
  n8n_data:
networks:
  n8n-network:
This includes healthchecks and volumes for persistence.
6

Start the n8n Stack

Run docker compose up -d to start in detached mode. Check logs with docker compose logs -f and status with docker ps. Access temporarily at http://your-vps-ip:5678 to set up owner account.
Use <code>docker compose down</code> to stop safely.
7

Set Up Reverse Proxy for HTTPS

Install Nginx: sudo apt install nginx -y. Configure a site for your domain, enable HTTPS with Certbot: sudo apt install certbot python3-certbot-nginx -y, then sudo certbot --nginx -d n8n.yourdomain.com. Proxy traffic from port 443 to n8n's 5678.
Example Nginx config: server block with <code>proxy_pass http://localhost:5678;</code>.
8

Verify Installation and Webhooks

Access https://n8n.yourdomain.com. Create a test workflow with a Webhook node, switch to Production URL, and test with Postman. Ensure URL starts with your domain, not localhost.
Check firewall: <code>sudo ufw allow 80,443</code> and <code>sudo ufw enable</code>.
9

Update and Maintain

To update: docker compose pull then docker compose up -d. Backup volumes regularly: docker volume ls and use provider snapshots.

Common Issues & Troubleshooting

Port conflicts or Docker not starting

Check running services with <code>sudo netstat -tuln | grep 5678</code>, kill conflicts, ensure Docker service is active: <code>sudo systemctl status docker</code>.

Volume permission errors

Fix ownership: <code>sudo chown -R $USER:$USER ~/n8n</code>, or run Docker as root temporarily.

Webhook URLs show localhost

Set <code>WEBHOOK_URL=https://n8n.yourdomain.com/</code> in .env and restart: <code>docker compose down && docker compose up -d</code>.

Postgres connection fails

Verify env vars match, check health: <code>docker compose logs postgres</code>, ensure depends_on with healthcheck.

Can't access after HTTPS setup

Check Nginx config syntax: <code>sudo nginx -t</code>, reload: <code>sudo systemctl reload nginx</code>, verify Certbot and firewall.