Self-Host
Updated 25 min read

How to Self-Host n8n on a VPS (Production Setup)

Run n8n workflow automation on your own VPS. Build integrations without per-execution pricing.

How to Self-Host n8n on a VPS (Production Setup)

n8n is a workflow automation tool that connects your apps and services. Self-hosting means unlimited executions at flat server cost.

The managed version charges per execution. If you run many workflows, self-hosting makes financial sense.

What you will have at the end

  • n8n running at https://n8n.your-domain.com
  • Automatic SSL via Traefik
  • PostgreSQL database for production reliability
  • Persistent workflow storage

Step 1: Prepare the server

sudo apt update && sudo apt upgrade -y

Install Docker if needed:

curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker

Step 2: Create directory structure

mkdir -p ~/apps/n8n/data
mkdir -p ~/apps/n8n/files
cd ~/apps/n8n

Step 3: Create docker-compose.yml

services:
  traefik:
    image: traefik:v2.11
    command:
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
      - --certificatesresolvers.letsencrypt.acme.tlschallenge=true
      - [email protected]
      - --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
    ports:
      - 80:80
      - 443:443
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - letsencrypt:/letsencrypt
    restart: unless-stopped

  postgres:
    image: postgres:15-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: n8n
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: n8n
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U n8n"]
      interval: 10s
      timeout: 5s
      retries: 5

  n8n:
    image: n8nio/n8n:latest
    restart: unless-stopped
    depends_on:
      postgres:
        condition: service_healthy
    environment:
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_USER=n8n
      - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
      - N8N_BASIC_AUTH_ACTIVE=true
      - N8N_BASIC_AUTH_USER=${N8N_USER}
      - N8N_BASIC_AUTH_PASSWORD=${N8N_PASSWORD}
      - N8N_HOST=n8n.your-domain.com
      - N8N_PORT=5678
      - N8N_PROTOCOL=https
      - WEBHOOK_URL=https://n8n.your-domain.com/
      - GENERIC_TIMEZONE=UTC
    volumes:
      - ./data:/home/node/.n8n
      - ./files:/files
    labels:
      - traefik.enable=true
      - traefik.http.routers.n8n.rule=Host(`n8n.your-domain.com`)
      - traefik.http.routers.n8n.entrypoints=websecure
      - traefik.http.routers.n8n.tls=true
      - traefik.http.routers.n8n.tls.certresolver=letsencrypt
      - traefik.http.services.n8n.loadbalancer.server.port=5678

volumes:
  letsencrypt:
  postgres_data:

Step 4: Configure environment variables

Create .env file:

# Database password
POSTGRES_PASSWORD=$(openssl rand -hex 16)

# n8n admin credentials
N8N_USER=admin
N8N_PASSWORD=$(openssl rand -base64 24)

# Save to .env
cat > .env << EOF
POSTGRES_PASSWORD=$POSTGRES_PASSWORD
N8N_USER=$N8N_USER
N8N_PASSWORD=$N8N_PASSWORD
EOF

echo "Your n8n credentials:"
echo "User: $N8N_USER"
echo "Password: $N8N_PASSWORD"

Save these credentials securely.

Step 5: Deploy

docker compose up -d

Wait for PostgreSQL to initialize before n8n starts.

Step 6: Verify installation

docker compose ps
docker compose logs -f n8n

Visit https://n8n.your-domain.com and log in with your credentials.

Step 7: Configure webhooks

For webhook triggers to work:

  1. Your domain must be accessible from the internet
  2. The WEBHOOK_URL must match your domain
  3. Firewall must allow incoming connections on 443

Test with a simple webhook workflow to verify.

Backup strategy

n8n with PostgreSQL needs database backups and workflow exports.

#!/bin/bash
# Daily backup script
BACKUP_DIR=/backups/n8n/$(date +%Y%m%d)
mkdir -p $BACKUP_DIR

# Postgres dump
docker exec n8n-postgres-1 pg_dump -U n8n n8n > $BACKUP_DIR/n8n.sql

# Backup local files
tar -czf $BACKUP_DIR/n8n-data.tar.gz ~/apps/n8n/data

# Compress
tar -czf $BACKUP_DIR.tar.gz $BACKUP_DIR

Also consider exporting workflows as JSON from the n8n UI for version control.

Troubleshooting

Webhooks not triggering

  • Verify WEBHOOK_URL matches your domain exactly
  • Check firewall allows incoming HTTPS
  • Test with curl: curl -X POST https://n8n.your-domain.com/webhook-test/test
  • Review n8n logs for incoming requests

Cannot connect to external services

  • Some services block datacenter IPs
  • Check if the service requires IP whitelisting
  • Verify DNS resolution inside container: docker exec n8n-n8n-1 nslookup api.example.com

Workflows fail silently

  • Enable detailed logging: set N8N_LOG_LEVEL=debug
  • Check execution history in the n8n UI
  • Verify credentials are still valid

High memory usage

  • PostgreSQL needs tuning for large workflow history
  • Consider pruning old executions
  • Increase VPS RAM if running many concurrent workflows
n8n
automation
self-host
docker-compose

From across the StoicSoft network

Hand-curated reads on the same topic from sister sites in the StoicSoft family.

Keep reading