I've set up self-hosted n8n on AWS EC2 for multiple clients. This guide covers the exact setup I use in production — Docker Compose, Nginx with SSL, PostgreSQL persistence, and auto-restart on reboot. Total cost: under $10/month on a t3.small instance.
Prerequisites: AWS account, a domain name (or subdomain), basic terminal comfort. This guide assumes Ubuntu 22.04 LTS.
Step 1 — Launch EC2 Instance
- Go to EC2 → Launch Instance
- Select Ubuntu Server 22.04 LTS
- Choose t3.small (2 vCPU, 2 GB RAM) — minimum for stable n8n
- Storage: set to 20 GB gp3
- Security Group: open ports
22(SSH),80(HTTP),443(HTTPS) - Create a key pair and download the
.pemfile
SSH into your instance:
ssh -i your-key.pem ubuntu@YOUR_EC2_PUBLIC_IP
Step 2 — Install Docker & Docker Compose
sudo apt update && sudo apt upgrade -y
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker ubuntu
newgrp docker
sudo apt install docker-compose-plugin -y
docker --version # confirm it works
Step 3 — Create the Docker Compose File
Create a working directory and the compose file:
mkdir ~/n8n && cd ~/n8n
nano docker-compose.yml
Paste this — replace the values in angle brackets:
version: "3.8"
services:
postgres:
image: postgres:15
restart: always
environment:
POSTGRES_USER: n8n
POSTGRES_PASSWORD: <STRONG_PASSWORD>
POSTGRES_DB: n8n
volumes:
- postgres_data:/var/lib/postgresql/data
n8n:
image: docker.n8n.io/n8nio/n8n
restart: always
ports:
- "5678:5678"
environment:
- N8N_HOST=<YOUR_DOMAIN>
- N8N_PORT=5678
- N8N_PROTOCOL=https
- WEBHOOK_URL=https://<YOUR_DOMAIN>/
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_DATABASE=n8n
- DB_POSTGRESDB_USER=n8n
- DB_POSTGRESDB_PASSWORD=<STRONG_PASSWORD>
- N8N_BASIC_AUTH_ACTIVE=true
- N8N_BASIC_AUTH_USER=admin
- N8N_BASIC_AUTH_PASSWORD=<ADMIN_PASSWORD>
- EXECUTIONS_PROCESS=main
- GENERIC_TIMEZONE=UTC
volumes:
- n8n_data:/home/node/.n8n
depends_on:
- postgres
volumes:
postgres_data:
n8n_data:
Step 4 — Install Nginx & Certbot
sudo apt install nginx certbot python3-certbot-nginx -y
Create an Nginx config for your domain:
sudo nano /etc/nginx/sites-available/n8n
server {
listen 80;
server_name <YOUR_DOMAIN>;
location / {
proxy_pass http://localhost: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_read_timeout 86400;
}
}
sudo ln -s /etc/nginx/sites-available/n8n /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Step 5 — SSL with Let's Encrypt
Point your domain's A record to the EC2 public IP first, then:
sudo certbot --nginx -d <YOUR_DOMAIN>
Certbot will automatically update your Nginx config to redirect HTTP to HTTPS and add SSL certificates. Auto-renewal is configured by default.
Step 6 — Start n8n
cd ~/n8n
docker compose up -d
docker compose logs -f # watch for errors
Visit https://YOUR_DOMAIN — you should see the n8n login screen. Log in with the basic auth credentials you set in the compose file.
Step 7 — Production Hardening
Auto-restart on reboot
The restart: always in the compose file handles this for Docker. But also enable Docker to start on boot:
sudo systemctl enable docker
Monitoring with a simple health check
Add a cron job that pings n8n and alerts you via email if it's down:
crontab -e
# Add this line:
*/5 * * * * curl -sf https://<YOUR_DOMAIN>/healthz || echo "n8n DOWN" | mail -s "n8n Alert" you@email.com
Backup PostgreSQL daily
crontab -e
# Add:
0 2 * * * docker exec n8n-postgres-1 pg_dump -U n8n n8n | gzip > ~/backups/n8n-$(date +\%Y\%m\%d).sql.gz
Cost breakdown: t3.small = ~$0.0208/hr × 730 hrs = ~$15/month. Use a t3.micro if you have low workflow volume — drops to ~$7.50/month (or free tier eligible for new accounts).
Common Issues I've Hit
- Webhooks not triggering: Make sure
WEBHOOK_URLin docker-compose matches your actual domain withhttps:// - 502 Bad Gateway: n8n container hasn't started yet — wait 30 seconds after
docker compose up - Execution timeout: Add
EXECUTIONS_TIMEOUT=3600to env vars for long-running workflows - Database connection errors: Run
docker compose restart— usually a startup timing issue between postgres and n8n containers
That's the full setup. I've run this stack in production for 6+ months without downtime. If you want me to set this up for you or migrate your existing Zapier workflows to n8n, get in touch.