automation 15 read

How to Set Up Self-Hosted n8n: Complete Installation Guide

Step-by-step guide to setting up self-hosted n8n with Docker. Includes SSL, backups, and production best practices.

By Dmytro Klymentiev
How to Set Up Self-Hosted n8n: Complete Installation Guide

n8n is a powerful workflow automation tool that you can run on your own server. Unlike Zapier or Make, self-hosted n8n has no per-execution fees - pay only for your server.

I've deployed n8n for multiple clients and use it for my own automation needs. In my experience, self-hosting is worth the initial setup effort. This guide walks you through setting up n8n from download to production-ready deployment.

Why Self-Host n8n?

Before we start the installation, here's why you might choose self-hosted over n8n Cloud:

Cost savings: No per-execution fees. Run 100,000 workflows per month for the cost of a $20/month server.

Data privacy: Your data stays on your server. Critical for healthcare, finance, or any sensitive data.

Full control: Customize everything. No vendor limitations.

No vendor lock-in: Your workflows are yours. Export and migrate anytime.

The trade-off: You manage the infrastructure. I found it's surprisingly low-maintenance once set up properly - this guide shows you how.

Prerequisites

Before you download and install n8n, you'll need:

  • A server (VPS) with at least 2GB RAM
  • Ubuntu 22.04 or newer (this guide uses Ubuntu)
  • A domain name pointed to your server
  • Basic command line knowledge
  • SSH access to your server

Recommended providers:

  • DigitalOcean ($12-24/month)
  • Hetzner ($5-10/month) - Best value
  • Linode ($12-24/month)
  • AWS Lightsail ($10-20/month)

Quick Start: n8n Download and Setup

For those who want to get running fast, here's the minimal setup:

# Install Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

# Run n8n
docker run -d \
  --name n8n \
  --restart always \
  -p 5678:5678 \
  -v n8n_data:/home/node/.n8n \
  n8nio/n8n

Access n8n at http://your-server-ip:5678

This works but isn't production-ready. Keep reading for a proper setup.

Step 1: Prepare Your Server

Update System

sudo apt update && sudo apt upgrade -y

Install Docker

# Download and install Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

# Add your user to docker group
sudo usermod -aG docker $USER

# Log out and back in, then verify
docker --version

Install Docker Compose

sudo apt install docker-compose-plugin -y

# Verify
docker compose version

Configure Firewall

sudo ufw allow 22    # SSH
sudo ufw allow 80    # HTTP
sudo ufw allow 443   # HTTPS
sudo ufw enable

Step 2: Set Up n8n with Docker Compose

Create a directory for n8n:

mkdir -p ~/n8n && cd ~/n8n

Create the Docker Compose file:

nano docker-compose.yml

Paste this configuration:

version: '3.8'

services:
  n8n:
    image: n8nio/n8n
    container_name: n8n
    restart: always
    ports:
      - "5678:5678"
    environment:
      - N8N_HOST=${N8N_HOST}
      - N8N_PORT=5678
      - N8N_PROTOCOL=https
      - NODE_ENV=production
      - WEBHOOK_URL=https://${N8N_HOST}/
      - GENERIC_TIMEZONE=${TIMEZONE}
      # Security
      - N8N_BASIC_AUTH_ACTIVE=true
      - N8N_BASIC_AUTH_USER=${N8N_USER}
      - N8N_BASIC_AUTH_PASSWORD=${N8N_PASSWORD}
      # Database (optional - SQLite by default)
      # - DB_TYPE=postgresdb
      # - DB_POSTGRESDB_HOST=postgres
      # - DB_POSTGRESDB_DATABASE=n8n
      # - DB_POSTGRESDB_USER=n8n
      # - DB_POSTGRESDB_PASSWORD=${DB_PASSWORD}
    volumes:
      - n8n_data:/home/node/.n8n
      - ./local-files:/files
    networks:
      - n8n-network

networks:
  n8n-network:

volumes:
  n8n_data:

Create the environment file:

nano .env

Add your configuration:

N8N_HOST=n8n.yourdomain.com
TIMEZONE=America/New_York
N8N_USER=admin
N8N_PASSWORD=your-secure-password-here

Important: Use a strong password. This protects your entire automation system.

Step 3: Set Up SSL with Caddy

n8n needs HTTPS for webhooks to work reliably. We'll use Caddy as a reverse proxy - it handles SSL certificates automatically.

Add Caddy to your docker-compose.yml:

version: '3.8'

services:
  caddy:
    image: caddy:2
    container_name: caddy
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config
    networks:
      - n8n-network

  n8n:
    image: n8nio/n8n
    container_name: n8n
    restart: always
    # Remove ports - Caddy handles external access
    # ports:
    #   - "5678:5678"
    environment:
      - N8N_HOST=${N8N_HOST}
      - N8N_PORT=5678
      - N8N_PROTOCOL=https
      - NODE_ENV=production
      - WEBHOOK_URL=https://${N8N_HOST}/
      - GENERIC_TIMEZONE=${TIMEZONE}
      - N8N_BASIC_AUTH_ACTIVE=true
      - N8N_BASIC_AUTH_USER=${N8N_USER}
      - N8N_BASIC_AUTH_PASSWORD=${N8N_PASSWORD}
    volumes:
      - n8n_data:/home/node/.n8n
      - ./local-files:/files
    networks:
      - n8n-network

networks:
  n8n-network:

volumes:
  n8n_data:
  caddy_data:
  caddy_config:

Create the Caddyfile:

nano Caddyfile

Add:

n8n.yourdomain.com {
    reverse_proxy n8n:5678
}

Replace n8n.yourdomain.com with your actual domain.

Step 4: Start n8n

docker compose up -d

Check that everything is running:

docker compose ps

You should see both caddy and n8n with status "Up".

Access n8n at https://n8n.yourdomain.com

Step 5: Configure n8n

Initial Setup

On first access:

  1. Log in with the credentials from your .env file
  2. Create your owner account (this is different from basic auth)
  3. Set up your first workflow

Essential Settings

Go to Settings → Personal:

  • Set your timezone correctly
  • Configure your name and email

Go to Settings → Community nodes (optional):

  • Enable community nodes for additional integrations

Security Hardening

Add these environment variables for additional security:

environment:
  # ... existing vars ...
  - N8N_BLOCK_ENV_ACCESS_IN_NODE=true
  - N8N_PERSONALIZATION_ENABLED=false
  - EXECUTIONS_DATA_PRUNE=true
  - EXECUTIONS_DATA_MAX_AGE=168  # 7 days in hours

Step 6: Set Up Backups

Your n8n data is stored in Docker volumes. Set up automatic backups:

Create backup script:

nano ~/n8n/backup.sh

Add:

#!/bin/bash
BACKUP_DIR="/home/$USER/n8n-backups"
DATE=$(date +%Y-%m-%d_%H-%M-%S)

mkdir -p $BACKUP_DIR

# Export workflows
docker exec n8n n8n export:workflow --all --output=/files/workflows-$DATE.json

# Export credentials (encrypted)
docker exec n8n n8n export:credentials --all --output=/files/credentials-$DATE.json

# Backup the volume
docker run --rm \
  -v n8n_n8n_data:/data \
  -v $BACKUP_DIR:/backup \
  alpine tar czf /backup/n8n-data-$DATE.tar.gz -C /data .

# Move exported files
mv ~/n8n/local-files/workflows-$DATE.json $BACKUP_DIR/
mv ~/n8n/local-files/credentials-$DATE.json $BACKUP_DIR/

# Keep only last 7 backups
ls -tp $BACKUP_DIR/*.tar.gz | tail -n +8 | xargs -I {} rm -- {}
ls -tp $BACKUP_DIR/workflows-*.json | tail -n +8 | xargs -I {} rm -- {}
ls -tp $BACKUP_DIR/credentials-*.json | tail -n +8 | xargs -I {} rm -- {}

echo "Backup completed: $DATE"

Make it executable and schedule:

chmod +x ~/n8n/backup.sh

# Add to crontab - daily at 2 AM
(crontab -l 2>/dev/null; echo "0 2 * * * /home/$USER/n8n/backup.sh") | crontab -

Step 7: Set Up Monitoring

Basic Health Check

Add a simple health check script:

nano ~/n8n/health-check.sh
#!/bin/bash
HEALTH=$(curl -s -o /dev/null -w "%{http_code}" https://n8n.yourdomain.com/healthz)

if [ "$HEALTH" != "200" ]; then
    echo "n8n is down! Status: $HEALTH" | mail -s "n8n Alert" your@email.com
    # Or send to Slack/Discord webhook
fi

Execution Monitoring

Create a workflow in n8n that:

  1. Runs on schedule (daily)
  2. Queries the n8n API for failed executions
  3. Sends alert if failures exceed threshold

Production Configuration

Using PostgreSQL Instead of SQLite

For production with high volume, use PostgreSQL:

services:
  postgres:
    image: postgres:15
    container_name: n8n-postgres
    restart: always
    environment:
      - POSTGRES_USER=n8n
      - POSTGRES_PASSWORD=${DB_PASSWORD}
      - POSTGRES_DB=n8n
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - n8n-network

  n8n:
    # ... existing config ...
    environment:
      # ... existing vars ...
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_USER=n8n
      - DB_POSTGRESDB_PASSWORD=${DB_PASSWORD}
    depends_on:
      - postgres

Add to .env:

DB_PASSWORD=your-database-password

Queue Mode for High Volume

For many concurrent executions, enable queue mode with Redis:

services:
  redis:
    image: redis:7
    container_name: n8n-redis
    restart: always
    networks:
      - n8n-network

  n8n:
    environment:
      - EXECUTIONS_MODE=queue
      - QUEUE_BULL_REDIS_HOST=redis
      - QUEUE_HEALTH_CHECK_ACTIVE=true

Resource Limits

Add resource limits to prevent runaway workflows:

n8n:
  # ... existing config ...
  deploy:
    resources:
      limits:
        memory: 2G
        cpus: '2'
      reservations:
        memory: 512M

Updating n8n

Check Current Version

docker exec n8n n8n --version

Update Process

cd ~/n8n

# Pull latest image
docker compose pull

# Restart with new image
docker compose up -d

# Check logs for issues
docker compose logs -f n8n

Rollback if Needed

# Stop current version
docker compose down

# Use specific version
# Edit docker-compose.yml: image: n8nio/n8n:0.236.0
docker compose up -d

Troubleshooting

n8n Won't Start

Check logs:

docker compose logs n8n

Common issues:

  • Port already in use: Change the port in docker-compose.yml
  • Permission issues: Check volume permissions
  • Memory issues: Increase server RAM or add swap

Webhooks Not Working

Verify:

  1. SSL is working: curl -I https://n8n.yourdomain.com
  2. WEBHOOK_URL is correct in .env
  3. Firewall allows 443

Executions Failing

Check:

  • Execution logs in n8n UI
  • Docker logs: docker compose logs -f n8n
  • Resource usage: docker stats

Database Connection Issues

For PostgreSQL:

# Check if postgres is running
docker compose ps postgres

# Check connection
docker exec n8n-postgres psql -U n8n -d n8n -c "SELECT 1;"

n8n Download Locations

Official Sources

  • Docker Hub: docker pull n8nio/n8n
  • GitHub: https://github.com/n8n-io/n8n
  • npm: npm install -g n8n (not recommended for production)

Version Pinning

For production, pin to specific versions:

image: n8nio/n8n:1.20.0

Check releases: https://github.com/n8n-io/n8n/releases

Next Steps

Now that n8n is running:

  1. Create your first workflow: Start with something simple like a webhook-to-Slack notification
  2. Import community workflows: Many templates available on n8n.io
  3. Set up error notifications: Create a workflow that alerts on execution failures
  4. Document your setup: Keep notes for future maintenance

Related resources:

Summary

Self-hosted n8n setup involves:

  1. Server with Docker
  2. Docker Compose configuration
  3. SSL via Caddy reverse proxy
  4. Backup automation
  5. Monitoring setup
  6. Regular updates

Total setup time: 30-60 minutes for a production-ready deployment.

The ongoing maintenance is minimal - occasional updates and monitoring. The cost savings compared to per-execution pricing make self-hosting worthwhile for any serious automation use.


Need help with n8n setup or building complex workflows? Check out my n8n automation services or let's talk about your automation needs.

Need help with automation?

Let's discuss your project

Get in touch
RELATED