Running n8n on your own server is the difference between paying per execution and having unlimited automation for the price of a VPS. To self-host n8n you need a VPS with 2 GB of RAM or more (USD 6-12/month), a domain pointing to the server, and Docker Compose with three pieces: the n8n container, PostgreSQL as the database, and a reverse proxy that handles HTTPS. The whole process takes 1-2 hours, and the two errors that wreck most installs —webhooks that never arrive and credentials lost on update— are prevented with two environment variables. This guide gets straight to the point and flags exactly where the traps are.
Step 0: the server and the domain
Realistic minimum requirements for light production:
- VPS: 2 vCPU, 4 GB RAM, 40 GB of disk. Typical providers: Hetzner (~USD 6), DigitalOcean (~USD 24), Vultr (~USD 12). It works with 2 GB, but workflows with AI or heavy data will struggle.
- OS: Ubuntu 22.04 or 24.04 LTS.
- Domain: a subdomain like
n8n.yourcompany.comwith an A record pointing to the VPS's IP. Do this before you start: SSL certificate issuance needs it propagated.
Install Docker with the official script:
curl -fsSL https://get.docker.com | sh
Step 1: structure and the critical environment variables
Create the project folder and the .env file:
mkdir -p /opt/n8n && cd /opt/n8n
# .env
N8N_HOST=n8n.yourcompany.com
WEBHOOK_URL=https://n8n.yourcompany.com/
N8N_PROTOCOL=https
N8N_PORT=5678
# THE most important variable in the entire install:
N8N_ENCRYPTION_KEY=a-long-random-key-you-will-store-off-the-server
GENERIC_TIMEZONE=America/Argentina/Buenos_Aires
TZ=America/Argentina/Buenos_Aires
# Database
DB_TYPE=postgresdb
DB_POSTGRESDB_HOST=postgres
DB_POSTGRESDB_DATABASE=n8n
DB_POSTGRESDB_USER=n8n
DB_POSTGRESDB_PASSWORD=another-strong-key
# Hygiene: prune old executions so the disk doesn't blow up
EXECUTIONS_DATA_PRUNE=true
EXECUTIONS_DATA_MAX_AGE=336
Two of these variables prevent the two classic disasters:
WEBHOOK_URL: without it, n8n builds webhook URLs withlocalhost:5678. Everything looks fine in the editor… until you connect WhatsApp, a payment provider, or any external service and the events never arrive. It's the number-one install error, and the symptom is misleading because manual executions do work.N8N_ENCRYPTION_KEY: n8n encrypts every saved credential (Google tokens, API keys, database logins) with this key. If you don't pin it, n8n generates one; if the container is recreated without the right volume or you migrate servers, the key changes and all your credentials become unreadable, with no way to recover them. Generate it withopenssl rand -hex 24, put it in the.env, and keep a copy off the server (in a password manager).
Step 2: docker-compose.yml
services:
postgres:
image: postgres:16
restart: unless-stopped
environment:
- POSTGRES_DB=n8n
- POSTGRES_USER=n8n
- POSTGRES_PASSWORD=${DB_POSTGRESDB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
n8n:
image: docker.n8n.io/n8nio/n8n:latest
restart: unless-stopped
env_file: .env
ports:
- "127.0.0.1:5678:5678"
volumes:
- n8n_data:/home/node/.n8n
depends_on:
- postgres
volumes:
postgres_data:
n8n_data:
One important security detail: the port is bound to 127.0.0.1, so n8n is not exposed directly to the internet. Only the reverse proxy from the next step can reach it. Bring everything up with:
docker compose up -d
Step 3: HTTPS with a reverse proxy
n8n without HTTPS is not an option: your credentials and your clients' data would travel in plain text, and most services (Meta, Google) outright reject webhooks without TLS. The simplest route is Caddy, which issues and renews certificates on its own:
apt install -y caddy
# /etc/caddy/Caddyfile
n8n.yourcompany.com {
reverse_proxy 127.0.0.1:5678
}
systemctl reload caddy
Done: https://n8n.yourcompany.com already responds with a valid certificate. If you prefer nginx + certbot, it works just as well; just remember to enable WebSocket support (the Upgrade and Connection headers), because the n8n editor uses it and without it the interface behaves oddly.
The first time you log in, n8n asks you to create the owner user. Do it immediately: a freshly installed instance with no owner, exposed to the internet, is an open invitation.
Step 4: backups, or the insurance that costs 10 minutes
A production n8n instance accumulates two assets: the workflows and the credentials. Losing them means redoing weeks of work. The minimum viable backup is a daily cron job:
#!/bin/bash
# /opt/n8n/backup.sh
DATE=$(date +%F)
docker exec $(docker compose -f /opt/n8n/docker-compose.yml ps -q postgres) \
pg_dump -U n8n n8n | gzip > /opt/backups/n8n-$DATE.sql.gz
cp /opt/n8n/.env /opt/backups/env-$DATE
find /opt/backups -mtime +14 -delete
And the rule that separates a real backup from wishful thinking: copy it off the server (S3, Backblaze B2, or at least another VPS). A backup that lives on the same machine dies with that machine. Remember that the .env holds the encryption key: a database dump without that key is useless.
Would you rather have someone who has done this dozens of times handle all of it? Book an intro meeting and we'll quote a turnkey install: server, HTTPS, backups, and monitoring up and running in under a week.
Common errors and their quick fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Webhooks don't arrive (but the manual test works) | WEBHOOK_URL missing or still set to localhost |
Set it to the public domain and recreate the container |
| "Credentials could not be decrypted" after an update or migration | The N8N_ENCRYPTION_KEY changed |
Restore the original key from the backup; if it's lost, you must re-enter credentials by hand |
| The disk fills up after a few months | Unlimited execution history | EXECUTIONS_DATA_PRUNE=true + EXECUTIONS_DATA_MAX_AGE |
| The interface loads but keeps disconnecting | Reverse proxy without WebSocket | Enable the Upgrade/Connection headers in nginx |
| Slow or crashing workflows under load | 1-2 GB VPS at its limit | Bump to 4 GB; review workflows that load thousands of items into memory |
| Scheduled tasks run at random times | Default timezone (UTC) | Set GENERIC_TIMEZONE and TZ to your zone |
To update versions: back up first, then docker compose pull && docker compose up -d. Avoid jumping from a very old version straight to the latest in one shot; n8n migrates the database schema on startup, so it's worth stepping through intermediate versions and reading the release notes.
When self-hosting is NOT worth it
This guide gives you a solid instance, but self-hosting is an ongoing responsibility, not a one-time errand:
- If no one on your team will dedicate 2-4 hours a month (updates, logs, checking backups), n8n Cloud at USD 24/month is cheaper than your first unattended incident. We compare the total cost of both models in how much n8n costs in 2026 and in n8n cloud vs self-hosted.
- If your workflows will handle business-critical data (collections, invoicing), the cost of a poorly secured instance isn't the server: it's the incident.
- If you haven't validated yet that automation pays off for you, test in Cloud first and migrate later; workflows export as JSON.
Instance ready, now what?
The install is the foundation; the value is in what you build on top: connecting your CRM, automating WhatsApp, orchestrating AI agents, and plugging in your own APIs. That's where the hours you invested turn into hours saved.
And if you got this far thinking "this is exactly what I don't want to do myself": at Deepyze we install self-hosted n8n turnkey —hardened server, HTTPS, verified backups, monitoring— and we build the workflows your operation needs as part of our AI automation service. Tell us about your project and within 24 hours you'll have a fixed-price proposal, from a team in your time zone that doesn't disappear afterward: maintenance is on us too.
Frequently asked questions
What do I need to self-host n8n?+
Three things: a VPS with 2 GB of RAM or more (from USD 6-12/month), a domain or subdomain pointing to that server, and Docker with Docker Compose installed. With that, a basic install with HTTPS is up and running in 1-2 hours if you follow the right steps.
Why aren't my webhooks reaching n8n?+
This is the number-one error: the WEBHOOK_URL variable isn't set to your public domain (https://n8n.yourdomain.com/). Without it, n8n generates webhook URLs using localhost, and external services can't reach them. Also check that your reverse proxy forwards the right headers and that your firewall allows port 443.
Why did n8n lose my credentials after an update?+
Because the N8N_ENCRYPTION_KEY changed or was never pinned. n8n encrypts saved credentials with that key: if the container is recreated and generates a new key, your existing credentials become permanently unreadable. Set it explicitly in the .env from day one and store a copy off the server.
Does self-hosted n8n need PostgreSQL?+
By default it uses SQLite, which is fine for testing. For production, PostgreSQL is the better choice: it handles more volume, is more resilient to crashes, and simplifies backups. Adding it is about 15 extra lines in the docker-compose plus the DB_TYPE=postgresdb variable.
How do I update n8n without breaking anything?+
Back up first (database and the .env with the encryption key), then run docker compose pull and docker compose up -d. Avoid jumping across many versions at once: n8n migrates its data schema on startup, and it's worth reading the release notes for breaking changes in nodes you rely on.
Want this working in your company?
At Deepyze we turn manual processes into systems that work on their own: AI automation, web and mobile apps, and custom software. Tell us your case and you will have a concrete proposal within 24 hours.
Sin compromiso · Respuesta en 24 hs · Equipo en tu mismo huso horario