
Per-task automation pricing feels cheap—until you connect real operations. Add uptime checks, customer notifications, billing events, Git hooks, database jobs, and on-call workflows. Then the counter never stops.
If you want Zapier-style automation with predictable infrastructure costs, self-host n8n on a VPS. You can run workflows next to your apps, logs, and private network.
This guide focuses on n8n (open-source workflow automation) and a VPS setup that holds up in production. You’ll use Docker Compose, a reverse proxy with HTTPS, realistic sizing, backups you can restore, and a low-drama update routine.
You’ll see the pieces that matter, without a detour into every possible flag.
Why self-host n8n on a VPS instead of paying per task
n8n is a workflow automation platform. It supports triggers, actions, branching logic, retries, and integrations. You connect APIs, webhooks, databases, and internal services. Then you run those workflows on infrastructure you control.
Hosting it yourself changes two operator-level realities:
- Cost predictability: you pay for compute, storage, and bandwidth—not each task executed.
- Network proximity: workflows can talk to private services directly (RFC1918 networks, internal APIs, local databases) without routing data through a third-party automation cloud.
You also own the boring-but-critical stuff. That includes TLS policy, firewall rules, backup retention, and maintenance windows.
Once workflows become operational dependencies, that control stops being optional.
If you’re choosing where to run it, a general-purpose HostMyCode VPS fits n8n well. You can dial in CPU/RAM and keep the environment stable as usage grows.
What n8n is (and what it replaces)
n8n is a self-hostable workflow engine with a visual editor and a large node library. It also includes an execution model: scheduling, retries, error handling, and queues (depending on your setup).
You can start with a single webhook and an HTTP call. You can then grow into heavier ops automation without switching tools.
License note: n8n uses a source-available license (the Sustainable Use License). You can self-host it, modify it, and run it for your own use. It is not “permissive open source” in the MIT/Apache sense.
For internal automations, that’s usually fine. Just don’t treat it as something you can repackage into a hosted service without reading the license terms.
Teams often move to n8n (or reduce Zapier/Make usage) when they:
- run high-volume workflows and want to avoid task-based pricing
- need private network access to internal APIs or databases
- require tighter control over data residency and secrets
- want workflow logic versioned and deployed like infrastructure
Quick comparison: n8n vs Zapier/Make for operators
This isn’t about ideology. It’s about tradeoffs you’ll feel in production.
| Concern | Hosted automation (Zapier/Make) | n8n on your VPS |
|---|---|---|
| Pricing | Recurring + task/operation based | Compute/storage based (predictable) |
| Data path | Often leaves your network | Stays inside your infra if you want |
| Ops burden | Vendor handles availability | You own uptime, backups, updates |
| Integrations | Huge catalog; polished templates | Strong catalog; very flexible via HTTP nodes |
| Compliance constraints | Depends on vendor plan/region | You choose location and controls |
If you already run services on a VPS or dedicated host, n8n usually slots in cleanly. This is especially true if you standardize on Docker Compose and one reverse-proxy pattern.
Resource requirements and sizing that won’t bite you later
n8n itself is not a resource hog. Workflow executions can be, especially with heavy JSON transforms, lots of HTTP calls, or CPU-heavy nodes like PDF generation.
Plan for how you’ll use it, not how the UI feels.
- Small setup (personal / light ops): 1 vCPU, 2 GB RAM, 30–50 GB SSD. Fine for a few low-frequency workflows.
- Team setup (multiple workflows, frequent triggers): 2 vCPU, 4 GB RAM, 60+ GB SSD. A comfortable baseline.
- Busy setup (high volume webhooks, many executions): 4 vCPU, 8 GB RAM. Consider separating the database and using queues.
Storage pressure usually comes from two places. The first is the database (executions and workflow data). The second is any file outputs you keep.
Execution history can grow faster than people expect. Decide early how long you actually need it.
Deployment overview: Docker Compose + PostgreSQL + HTTPS
A sensible “single VPS, production-ready” shape looks like this:
- n8n container for the app
- PostgreSQL as the primary database (a better choice than SQLite for real workloads)
- Reverse proxy (Nginx or Caddy) terminating TLS and forwarding requests to n8n
- Backups for PostgreSQL + encryption key material + Compose env files
Use the proxy you already run. Caddy is great if it’s your standard. Nginx is equally fine.
What matters is the pattern. Terminate HTTPS at the edge, set the correct base URL, and don’t expose the container port to the public internet.
For reverse proxy basics and TLS troubleshooting on a VPS, keep this handy: configure HTTPS with Let’s Encrypt on a VPS.
Set up the VPS: DNS, firewall, and a non-negotiable checklist
Before you touch Docker, handle the fundamentals. This is where most “avoidable outages” start.
- Point DNS (A/AAAA record) for a subdomain like
n8n.example.comto your server. - Lock the firewall to only what you need: SSH (22 or your custom port), HTTP (80), and HTTPS (443).
- Patch regularly and enable unattended security updates where appropriate.
If you’re moving DNS between providers and want a clean cutover, this walkthrough helps. It avoids the classic “TLS breaks after migration” scenario: migrate a domain to Cloudflare DNS without downtime.
On Ubuntu, a minimal UFW ruleset looks like:
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose
If you want a refresher (including the usual footguns like IPv6 and locking yourself out), see: Set up UFW on Ubuntu for a web server.
Docker Compose for n8n: a production-friendly baseline
Create a working directory. Keeping everything under one path makes backups, audits, and restores much easier:
sudo mkdir -p /opt/n8n
sudo chown -R $USER:$USER /opt/n8n
cd /opt/n8n
Create an environment file for secrets and settings. Keep it out of shell history and out of Git.
cat > .env <<'EOF'
# Domain used by your reverse proxy
N8N_HOST=n8n.example.com
N8N_PORT=5678
N8N_PROTOCOL=https
WEBHOOK_URL=https://n8n.example.com/
# Required for secure session/encryption handling
N8N_ENCRYPTION_KEY=change-me-to-a-long-random-string
# Basic hardening
N8N_DIAGNOSTICS_ENABLED=false
N8N_PERSONALIZATION_ENABLED=false
# PostgreSQL
POSTGRES_DB=n8n
POSTGRES_USER=n8n
POSTGRES_PASSWORD=change-me-to-a-strong-password
EOF
Generate strong values (example using OpenSSL):
openssl rand -hex 32
Now create docker-compose.yml. This layout keeps PostgreSQL private on the Docker network. It also exposes n8n only on localhost, so only your reverse proxy can reach it.
cat > docker-compose.yml <<'EOF'
services:
postgres:
image: postgres:16
container_name: n8n-postgres
restart: unless-stopped
environment:
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
n8n:
image: n8nio/n8n:latest
container_name: n8n
restart: unless-stopped
depends_on:
- postgres
environment:
- N8N_HOST=${N8N_HOST}
- N8N_PORT=${N8N_PORT}
- N8N_PROTOCOL=${N8N_PROTOCOL}
- WEBHOOK_URL=${WEBHOOK_URL}
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
- N8N_DIAGNOSTICS_ENABLED=${N8N_DIAGNOSTICS_ENABLED}
- N8N_PERSONALIZATION_ENABLED=${N8N_PERSONALIZATION_ENABLED}
- 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}
ports:
- "127.0.0.1:5678:5678"
volumes:
- n8n_data:/home/node/.n8n
volumes:
postgres_data:
n8n_data:
EOF
Bring it up:
docker compose up -d
docker compose ps
docker logs --tail=100 n8n
Pitfall: if WEBHOOK_URL is wrong, failures look “random.” Webhooks get registered under the wrong URL. Callbacks can point at HTTP instead of HTTPS. Triggers may quietly stop firing.
Set it correctly on day one.
Reverse proxy with HTTPS (Nginx example)
On a VPS, n8n almost always sits behind a proxy. That gives you TLS, sane request limits, compression, and one place to manage certificates.
Install Nginx and Certbot (Ubuntu example):
sudo apt update
sudo apt install -y nginx python3-certbot-nginx
Create an Nginx server block at /etc/nginx/sites-available/n8n:
sudo tee /etc/nginx/sites-available/n8n > /dev/null <<'EOF'
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 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_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
client_max_body_size 20m;
}
EOF
Enable it and reload:
sudo ln -s /etc/nginx/sites-available/n8n /etc/nginx/sites-enabled/n8n
sudo nginx -t
sudo systemctl reload nginx
Issue the certificate:
sudo certbot --nginx -d n8n.example.com
If proxy errors show up during testing (often after a change or update), this is a fast way to narrow it down: Nginx 502 Bad Gateway fixes on Ubuntu VPS.
Security note: n8n can reach a lot of systems and store a lot of secrets. Treat it as an admin surface.
Use a dedicated subdomain, enforce strong authentication, and restrict editor access (VPN, IP allowlist, or SSO) whenever you can.
Backups: what to copy, how often, and how to prove you can restore
For n8n, a “backup” is not just a database dump. Make sure you preserve:
- PostgreSQL data (workflows, credentials metadata, executions depending on settings)
- n8n persistent volume (local config, sometimes binary data depending on nodes)
- Your
.envfile (or a secrets manager export) - The encryption key (
N8N_ENCRYPTION_KEY) — losing it can make stored credentials unusable
A simple approach that works well on a single VPS:
- Nightly
pg_dumpto a local directory - Archive
/opt/n8n(excluding large caches) alongside the dumps - Sync backups offsite (object storage, another VPS, or a backup service)
Example backup script (/usr/local/sbin/backup-n8n.sh):
sudo tee /usr/local/sbin/backup-n8n.sh > /dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
TS=$(date +%F-%H%M)
BASE="/var/backups/n8n"
DIR="$BASE/$TS"
mkdir -p "$DIR"
chmod 700 "$DIR"
cd /opt/n8n
# Load env for DB creds
set -a
source ./.env
set +a
# PostgreSQL dump
docker exec -t n8n-postgres pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB" > "$DIR/n8n.sql"
# Compose + env snapshot (protect this; it contains secrets)
cp -a /opt/n8n/docker-compose.yml "$DIR/"
cp -a /opt/n8n/.env "$DIR/.env"
# Optional: capture n8n volume data as a tarball
docker run --rm \
-v n8n_n8n_data:/data:ro \
-v "$DIR":/backup \
alpine sh -c "cd /data && tar -czf /backup/n8n_data.tgz ."
# Basic retention: keep 14 days
find "$BASE" -mindepth 1 -maxdepth 1 -type d -mtime +14 -exec rm -rf {} \;
EOF
sudo chmod 700 /usr/local/sbin/backup-n8n.sh
Schedule it with cron:
sudo crontab -e
# Add:
15 2 * * * /usr/local/sbin/backup-n8n.sh
Restore test (do this once, then quarterly): spin up a second small VPS or a local VM. Deploy the same Compose file. Restore the SQL dump into PostgreSQL.
Then confirm workflows and credentials behave as expected. The restore step is where “we have backups” turns into “we can recover.”
Updates: keep it boring, keep it safe
n8n updates are usually painless. Problems come from two habits: upgrading without a backup, and upgrading without checking logs.
A safe routine:
- Run your backup job (or at least take a fresh database dump).
- Pull new images.
- Restart with Compose.
- Read logs, then run one real workflow end-to-end.
cd /opt/n8n
sudo /usr/local/sbin/backup-n8n.sh
docker compose pull
docker compose up -d
docker logs --tail=200 n8n
If you’re running multiple services on one host, routine maintenance can steal time from product work. You may prefer to offload patching, monitoring, and recovery.
Managed VPS hosting can cover the same deployment with ongoing maintenance, without building an ops function from scratch.
Operational hardening that matters for n8n
n8n is valuable because it can connect to almost anything. That’s also why it deserves real hardening.
- Do not publish port 5678 publicly. Bind to
127.0.0.1as shown and force all access through HTTPS. - Restrict admin access. For internal automations, put n8n behind a VPN or an IP allowlist at Nginx.
- Protect secrets. Keep
.envreadable only by root, and be careful with shell history and backups. - Patch the host. Kernel and OpenSSL updates still matter even if everything runs in containers.
For a clean baseline on Ubuntu security patching, see: set up automated security updates on Ubuntu.
Monitoring: treat automations like a production dependency
If workflows touch billing, onboarding, or incident response, “it ran last week” isn’t monitoring. You need three signals:
- Is the web UI reachable?
- Are workflows executing (and not backing up)?
- Is the VPS healthy (CPU steal, memory pressure, disk growth)?
Uptime checks plus host metrics cover most of it. If you already run Uptime Kuma, point it at https://n8n.example.com/.
For detailed VPS telemetry, add Netdata. HostMyCode has guides for both: self-host Uptime Kuma and run Netdata for live server monitoring.
Quick diagnostic: if disk usage jumps, check PostgreSQL volume growth and n8n execution retention. Databases don’t “randomly” expand. They grow because retention settings keep more data.
If you want automations running 24/7 without per-task fees, running n8n on your own server is a sensible move. Start with a right-sized HostMyCode VPS. If you’d rather not spend weekends on patching and recovery drills, managed VPS hosting can cover the same deployment with ongoing maintenance.
FAQ
Do I need PostgreSQL, or can I use SQLite?
You can use SQLite for testing, but PostgreSQL is the safer default for real workloads. It handles concurrency better and makes backup/restore routines more predictable.
Should I put n8n on the same VPS as my production database?
Usually no. n8n can generate bursty load (webhooks and retries). You don’t want that competing with your primary app database.
If you must colocate, enforce resource limits and watch disk growth closely.
How do I lock down access to the n8n editor?
Start with strong n8n authentication. Then add a network control: VPN access, an IP allowlist on Nginx, or a private admin subnet.
Don’t rely on obscurity or a non-standard port.
What’s the one secret I can’t afford to lose?
Your N8N_ENCRYPTION_KEY. Keep it backed up securely. If it’s lost or changed, previously stored credentials may become unreadable.
Summary: a practical path to reliable self-hosted automation
When you self-host n8n on a VPS, you’re making a straightforward trade. You get predictable infrastructure costs, but you also own uptime, security, and backups.
Docker Compose plus a reverse proxy gives you a clean, repeatable layout. A tested backup-and-update routine keeps the risk under control.
If you want a stable place to run it, deploy on a HostMyCode VPS. Operate it like any production service: TLS, firewall rules, monitoring, and restores you’ve practiced.
That’s how self-hosted automation stays boring—in the best way.