# What You'll Learn#
If you want to self host n8n for full control over data, costs, and compliance, you need more than a quick docker run. You need persistence, secrets, SSL, backups, and a security baseline that can survive real production use.
This guide gives you a deployment you can keep running for years: Docker Compose, Postgres, reverse proxy TLS, network isolation, backup and restore, and maintenance routines.
You will also get a security checklist tailored to n8n’s typical risks like exposed webhooks, weak credentials, and overly permissive Docker networking.
ℹ️ Note: If you are still deciding between self-hosting and SaaS automation tools, compare trade-offs in n8n vs Zapier vs Make in 2026. Self-hosting usually wins when you need data residency, custom nodes, or predictable costs at scale.
# Prerequisites#
| Requirement | Recommended | Notes |
|---|---|---|
| Linux VPS | Ubuntu 22.04 or 24.04 | Any modern distro works, Ubuntu is easiest to follow |
| CPU and RAM | 2 vCPU, 4 GB RAM | 1 vCPU and 2 GB can work for light usage |
| Storage | 40 GB SSD | More if you store binaries and large executions |
| Docker Engine | 24+ | Use official Docker repo packages |
| Docker Compose | v2 | Comes as docker compose plugin on most installs |
| Domain | n8n.yourdomain.com | Required for HTTPS and webhooks |
| SMTP account | Optional | For email notifications and password resets |
| SSH access | Key-based auth | Disable password login if possible |
Recommended network and ports#
| Port | Publicly exposed | Purpose |
|---|---|---|
| 22 | Yes, restricted | SSH administration |
| 80 | Yes | HTTP to HTTPS redirect and ACME challenges |
| 443 | Yes | HTTPS for n8n UI and webhooks |
| 5678 | No | n8n internal port, keep private behind reverse proxy |
| 5432 | No | Postgres internal port, never public |
# Architecture Overview: What You’re Deploying#
A production-grade n8n setup typically includes these components:
| Component | Why it matters | Best practice |
|---|---|---|
| n8n container | Runs the editor, webhooks, and executions | Run behind a reverse proxy, not exposed directly |
| Postgres | Reliable persistence for workflows and credentials metadata | Separate container, private network, routine dumps |
| Reverse proxy | TLS termination, routing, rate limiting options | Traefik or Nginx, only 80 and 443 exposed |
| Volumes | Persist n8n config, binaries, logs, and DB data | Named volumes or bind mounts with backups |
| Secrets | Protect DB passwords, encryption key, webhook URLs | .env plus Docker secrets or file-based secrets |
🎯 Key Takeaway: Treat n8n like any other web app: a private app container, a private database, and a single hardened public entry point with HTTPS.
# Step 1: Server Hardening Before You Install Anything#
Self-hosting fails most often because the server basics are ignored. Most compromises still come from exposed services, reused passwords, and unpatched packages.
Create a non-root user and lock down SSH#
- 1Create a new user and add it to sudo.
- 2Use SSH keys, disable password auth.
- 3Optionally change SSH port, but do not rely on that alone.
# create user
adduser deploy
usermod -aG sudo deploy
# harden ssh
sudo sed -i 's/^#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo sed -i 's/^#PermitRootLogin prohibit-password/PermitRootLogin no/' /etc/ssh/sshd_config
sudo systemctl restart sshFirewall baseline with UFW#
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status💡 Tip: Restrict SSH to your office IP or VPN range. A simple allowlist eliminates most SSH brute-force noise and reduces log volume significantly.
For a broader checklist you can reuse across projects, see Web Application Security Checklist.
# Step 2: Install Docker and Docker Compose#
Use Docker’s official repository to avoid outdated packages.
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-pluginAdd your deploy user to the docker group:
sudo usermod -aG docker deploy
newgrp docker
docker version
docker compose version# Step 3: Create a Clean Project Layout#
A predictable layout makes backups and upgrades safer.
mkdir -p ~/apps/n8n/{data,db,backups,traefik}
cd ~/apps/n8nSuggested directory purpose:
| Path | Purpose | Back up |
|---|---|---|
~/apps/n8n/data | n8n persistent data and binary storage | Yes |
~/apps/n8n/db | Postgres data directory if bind-mounted | Yes, or use named volume |
~/apps/n8n/backups | Local backup staging | Optional, but helpful |
~/apps/n8n/traefik | Reverse proxy config and ACME storage | Yes |
# Step 4: Secrets and Environment Setup#
n8n uses an encryption key to protect credentials at rest. If you lose it, you will not be able to decrypt credentials even if you restore the database.
Generate secrets:
openssl rand -hex 32
openssl rand -base64 48Create a .env file. Do not commit it to git.
cd ~/apps/n8n
nano .envExample .env:
# domain
N8N_HOST=n8n.yourdomain.com
N8N_PROTOCOL=https
N8N_PORT=5678
WEBHOOK_URL=https://n8n.yourdomain.com/
# timezone
GENERIC_TIMEZONE=Europe/Zagreb
TZ=Europe/Zagreb
# encryption and auth
N8N_ENCRYPTION_KEY=replace-with-64-hex-or-similar
N8N_BASIC_AUTH_ACTIVE=true
N8N_BASIC_AUTH_USER=replace-me
N8N_BASIC_AUTH_PASSWORD=replace-me-long-password
# database
DB_TYPE=postgresdb
DB_POSTGRESDB_HOST=postgres
DB_POSTGRESDB_PORT=5432
DB_POSTGRESDB_DATABASE=n8n
DB_POSTGRESDB_USER=n8n
DB_POSTGRESDB_PASSWORD=replace-me-long-password
# execution and data retention examples
EXECUTIONS_DATA_PRUNE=true
EXECUTIONS_DATA_MAX_AGE=168Recommended environment flags to consider:
| Variable | Recommended | Why |
|---|---|---|
N8N_ENCRYPTION_KEY | Set and store safely | Prevents credential loss on restore |
N8N_BASIC_AUTH_ACTIVE | Enable at least initially | Adds an extra gate if your SSO is not set up |
WEBHOOK_URL | Explicit HTTPS URL | Prevents incorrect webhook URLs behind proxy |
EXECUTIONS_DATA_PRUNE | true | Reduces disk growth and backup size |
EXECUTIONS_DATA_MAX_AGE | 72 to 336 hours | Keeps useful history without infinite retention |
⚠️ Warning: Changing
N8N_ENCRYPTION_KEYafter you already created credentials will effectively lock you out of those credentials. Treat it like a database master key.
# Step 5: Docker Compose with Postgres, n8n, and Traefik SSL#
This example uses Traefik for automatic TLS certificates via Let’s Encrypt. It is a good fit for a single-host VPS deployment.
Create docker-compose.yml:
services:
traefik:
image: traefik:v3.1
command:
- --api.dashboard=false
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --certificatesresolvers.le.acme.email=admin@yourdomain.com
- --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
- --certificatesresolvers.le.acme.httpchallenge.entrypoint=web
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./traefik:/letsencrypt
networks:
- edge
restart: unless-stopped
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: ${DB_POSTGRESDB_USER}
POSTGRES_PASSWORD: ${DB_POSTGRESDB_PASSWORD}
POSTGRES_DB: ${DB_POSTGRESDB_DATABASE}
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- internal
restart: unless-stopped
n8n:
image: n8nio/n8n:latest
environment:
N8N_HOST: ${N8N_HOST}
N8N_PROTOCOL: ${N8N_PROTOCOL}
N8N_PORT: ${N8N_PORT}
WEBHOOK_URL: ${WEBHOOK_URL}
GENERIC_TIMEZONE: ${GENERIC_TIMEZONE}
TZ: ${TZ}
N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
N8N_BASIC_AUTH_ACTIVE: ${N8N_BASIC_AUTH_ACTIVE}
N8N_BASIC_AUTH_USER: ${N8N_BASIC_AUTH_USER}
N8N_BASIC_AUTH_PASSWORD: ${N8N_BASIC_AUTH_PASSWORD}
DB_TYPE: ${DB_TYPE}
DB_POSTGRESDB_HOST: ${DB_POSTGRESDB_HOST}
DB_POSTGRESDB_PORT: ${DB_POSTGRESDB_PORT}
DB_POSTGRESDB_DATABASE: ${DB_POSTGRESDB_DATABASE}
DB_POSTGRESDB_USER: ${DB_POSTGRESDB_USER}
DB_POSTGRESDB_PASSWORD: ${DB_POSTGRESDB_PASSWORD}
EXECUTIONS_DATA_PRUNE: ${EXECUTIONS_DATA_PRUNE}
EXECUTIONS_DATA_MAX_AGE: ${EXECUTIONS_DATA_MAX_AGE}
volumes:
- n8n_data:/home/node/.n8n
networks:
- internal
- edge
labels:
- traefik.enable=true
- traefik.http.routers.n8n.rule=Host(`${N8N_HOST}`)
- traefik.http.routers.n8n.entrypoints=websecure
- traefik.http.routers.n8n.tls.certresolver=le
- traefik.http.services.n8n.loadbalancer.server.port=5678
depends_on:
- postgres
- traefik
restart: unless-stopped
networks:
edge:
name: n8n_edge
internal:
name: n8n_internal
internal: true
volumes:
n8n_data:
postgres_data:Bring it up:
docker compose up -d
docker compose ps
docker logs -n 200 n8n-n8n-1Confirm you can open:
https://n8n.yourdomain.com/for the UI- a test workflow webhook, if you enable one
Why this Compose file is safer than the typical quickstart#
| Practice | Typical quickstart | This guide |
|---|---|---|
| Database | SQLite inside the container | Postgres on a private network |
| TLS | Often missing | Automatic TLS via Traefik |
| Ports | n8n port exposed publicly | Only 80 and 443 public |
| Network isolation | Flat network | Internal network for DB traffic |
| Persistence | Sometimes ephemeral | Named volumes for DB and n8n data |
💡 Tip: Pin versions once stable. For example,
n8nio/n8n:1.90.0instead oflatest. It reduces surprise breakage during automatic updates.
# Step 6: Persistence and Data Retention Strategy#
Your two main persistent assets:
- 1Postgres data volume, which contains workflows, executions metadata, and encrypted credentials blobs.
- 2n8n data volume, which contains config and binary data, including file uploads depending on settings.
Keep executions under control#
Unbounded execution history causes two problems: disk growth and longer backups. If you run 5,000 executions per day and each stores even 50 KB of JSON, that is about 5000 * 50 KB = 250 MB per day, or roughly 7.5 GB per month.
Pruning settings in .env reduce this significantly. A common baseline is 7 days for debugging and audits, or 72 hours if you have separate logging and monitoring.
# Step 7: Secrets Management Options That Actually Work#
If you truly want to self host n8n long-term, .env is only step one. You need a plan for where secrets live, who can read them, and how they are rotated.
Option A: .env with strict file permissions#
This is the minimum viable approach.
chmod 600 ~/apps/n8n/.env
chown deploy:deploy ~/apps/n8n/.envPros: simple. Cons: secrets live on disk in plain text.
Option B: Docker secrets with file-based environment values#
Docker Compose can mount secrets as files, and many services can read from files. n8n itself primarily expects env vars, so the practical pattern is a wrapper entrypoint or exporting variables from files.
If you want a simpler hardening approach, keep .env but store the same values in a password manager and rotate them periodically.
Option C: External secrets manager#
For regulated environments, use a secrets manager and inject at runtime. The key requirement is that the encryption key and DB password are retrievable consistently on every restart.
ℹ️ Note: Regardless of method, your top priority secrets are
N8N_ENCRYPTION_KEY, database password, and any n8n credentials used for production systems like payment providers and CRM admins.
# Step 8: SSL Details and Common Reverse Proxy Issues#
Traefik terminates TLS and forwards to n8n. Two settings prevent 80 percent of “webhook URL” issues:
- Set
WEBHOOK_URLto your public HTTPS URL. - Ensure the Host header routing matches your domain.
Add an HTTP to HTTPS redirect#
Add these labels to the n8n router or configure Traefik global redirect. A simple pattern is a dedicated HTTP router that redirects.
If you want a clean minimal change, add this to the Traefik command list:
- --entrypoints.web.http.redirections.entrypoint.to=websecure
- --entrypoints.web.http.redirections.entrypoint.scheme=https# Step 9: Security Hardening for n8n in Production#
Self-hosting changes your threat model. Attackers will try credential stuffing, exploit exposed webhooks, and probe admin panels.
1) Authentication and permissions#
At a minimum:
- Use n8n user management and strong passwords.
- Keep basic auth enabled until you have SSO or network controls.
- Use separate n8n accounts per person, not shared credentials.
If your n8n instance supports roles, avoid giving everyone admin access. The main risk is accidental credential exposure and workflow edits.
2) Network isolation and inbound exposure#
The Compose file uses an internal network for Postgres. That prevents the DB container from being reachable from other Docker networks and from the host’s external interfaces.
Also ensure you are not publishing Postgres ports. Do not add ports for Postgres unless you have a very specific need.
3) Webhook exposure and allowlisting#
If you create public webhooks, they are internet-facing endpoints. Protect them like any API:
- Use secrets in the webhook path or headers.
- Validate payload signatures where supported.
- Rate-limit at the reverse proxy if possible.
A typical secure pattern is a header secret that the first node validates before doing anything expensive.
⚠️ Warning: “Hidden” webhook URLs are not authentication. If the URL leaks into logs, browser history, or a ticket, it becomes a permanent bypass.
4) Container permissions and filesystem#
Avoid running extra privileges:
- Do not run n8n with
privileged: true. - Do not mount the Docker socket into the n8n container.
- Prefer named volumes over binding sensitive host paths.
If you bind-mount directories, keep ownership tight and avoid world-writable permissions.
5) Security headers and app-level controls#
Reverse proxies can add headers like HSTS and X-Content-Type-Options. Start with the general practices in our Web Application Security Checklist and apply them to your proxy layer.
6) Keep dependencies updated, but not blindly#
Updating n8n is security-critical because automation tools touch many systems. However, updates can break workflows due to node changes.
A practical cadence:
| Item | Cadence | How |
|---|---|---|
| OS security updates | Weekly | unattended-upgrades or scheduled maintenance |
| Docker and engine | Monthly | pin and upgrade in a window |
| n8n version | Every 2 to 4 weeks | read release notes, test on staging |
| Postgres minor updates | Monthly | keep within major version |
# Step 10: Backups You Can Restore#
A backup is only real if you can restore it. For n8n, treat it like you would a production SaaS: database dump plus data directory snapshot, automated daily, stored offsite, and tested monthly.
What to back up#
| Asset | Contains | Backup method |
|---|---|---|
| Postgres database | Workflows, users, credentials blobs, execution records | pg_dump logical backups and occasional volume snapshots |
| n8n data volume | Instance config, binary data, some settings | volume archive or bind-mount tarball |
.env | encryption key, DB password, hostnames | store securely, version with secret manager |
| Reverse proxy ACME | TLS cert metadata | optional, can be reissued |
Backup script: Postgres plus n8n data#
Create backup.sh:
#!/usr/bin/env bash
set -euo pipefail
BASE_DIR="$HOME/apps/n8n"
BACKUP_DIR="$BASE_DIR/backups"
TS="$(date +%F_%H-%M)"
mkdir -p "$BACKUP_DIR"
# Postgres logical dump
docker exec -t n8n-postgres-1 pg_dump -U n8n n8n | gzip > "$BACKUP_DIR/postgres_$TS.sql.gz"
# n8n volume archive
docker run --rm \
-v n8n_n8n_data:/volume:ro \
-v "$BACKUP_DIR":/backup \
alpine:3.20 \
sh -c "cd /volume && tar -czf /backup/n8n_data_$TS.tar.gz ."
# optional: checksum
sha256sum "$BACKUP_DIR"/*"$TS"* > "$BACKUP_DIR/checksums_$TS.txt"Make it executable:
chmod +x ~/apps/n8n/backup.shSchedule daily at 02:30:
crontab -eAdd:
30 2 * * * /home/deploy/apps/n8n/backup.sh >/home/deploy/apps/n8n/backups/cron.log 2>&1💡 Tip: Apply a retention policy. A common baseline is 14 daily backups plus 8 weekly backups. Without retention, backups eventually become the outage.
Offsite storage#
At minimum, copy backups off the VPS daily. Options include S3-compatible storage, Backblaze B2, or another server.
If you use rclone, you can push the backup folder after creation. Keep credentials restricted to only that bucket and path.
# Step 11: Restore Procedure (Test This on Staging)#
Restoring n8n is straightforward if you keep the encryption key and restore in the right order.
Restore checklist#
| Step | Action | Why |
|---|---|---|
| 1 | Stop services | Prevent writes during restore |
| 2 | Restore Postgres dump | Brings back workflows and credentials blobs |
| 3 | Restore n8n data volume | Restores config and binaries |
| 4 | Ensure .env includes original encryption key | Enables credential decryption |
| 5 | Start services and validate | Confirms workflows, webhooks, and executions |
Stop containers:
cd ~/apps/n8n
docker compose downRestore Postgres:
gzip -dc backups/postgres_YYYY-MM-DD_HH-MM.sql.gz | \
docker run -i --rm --network n8n_internal postgres:16-alpine \
psql "postgresql://n8n:YOURPASSWORD@postgres:5432/n8n"Restore n8n volume archive:
docker run --rm \
-v n8n_n8n_data:/volume \
-v "$HOME/apps/n8n/backups":/backup \
alpine:3.20 \
sh -c "rm -rf /volume/* && cd /volume && tar -xzf /backup/n8n_data_YYYY-MM-DD_HH-MM.tar.gz"Start again:
docker compose up -d
docker compose logs -n 200 n8nValidate restore#
- Can you log in to the UI
- Do workflows appear
- Do credentials show as valid and not “missing encryption key”
- Do at least two critical workflows run successfully
- Do webhooks return the expected status codes
⚠️ Warning: A successful restore that cannot decrypt credentials is still a failed restore. Your restore drill must include credential validation.
# Step 12: Maintenance Best Practices#
A self-hosted automation platform is part of your production stack. Maintenance is what makes it cheaper than SaaS long-term.
Update routine you can keep#
- 1Clone your production Compose directory to staging.
- 2Upgrade n8n image version in staging and run workflow smoke tests.
- 3Schedule a maintenance window.
- 4Backup first, then upgrade production.
Upgrade example:
cd ~/apps/n8n
docker compose pull
docker compose up -d
docker compose psMonitor the right signals#
| Signal | What to watch | Tooling options |
|---|---|---|
| Disk | volumes and logs growing | df -h, log rotation, pruning |
| Memory | OOM kills during executions | docker stats, VPS metrics |
| Queue latency | executions backlogged | n8n executions view, external monitoring |
| Errors | workflow failures, credential issues | email alerts, log aggregation |
Log growth and retention#
Containers can generate large logs over months. Ensure Docker log rotation is enabled on the host. A common safe baseline is 10 MB per file times 3 files.
Database health#
If you run many executions, Postgres performance matters. At minimum:
- Keep enough disk headroom, target 20 percent free space.
- Vacuum is handled automatically, but monitor bloat if you store many execution records.
- Consider pruning execution data more aggressively if you do not need long retention.
# When Self-Hosting n8n Is Worth It#
Self-hosting has a cost, but it also removes a lot of variable pricing. Teams often switch to self host n8n when:
- They run high-volume workflows and want predictable infrastructure spend.
- They need data residency or private networking to internal services.
- They want custom nodes or deeper integrations.
If you need help choosing the best approach, start with n8n vs Zapier vs Make in 2026 and then map it to your data and compliance requirements.
If you want this deployed, secured, and monitored without burning internal engineering time, see our automation services.
# Key Takeaways#
- Put n8n behind a reverse proxy with HTTPS and expose only ports 80 and 443 publicly.
- Use Postgres on a private Docker network and never publish database ports to the internet.
- Store and protect
N8N_ENCRYPTION_KEYlike a master key, and include it in your disaster recovery plan. - Back up both Postgres and the n8n data volume, automate daily, enforce retention, and test restores monthly.
- Reduce operational risk by pinning versions, upgrading on a schedule, and validating critical workflows after every update.
# Conclusion#
To self host n8n safely, you need a production mindset: private networking, strong authentication, TLS, reliable backups, and a repeatable upgrade process. Once those pieces are in place, n8n becomes a stable automation backbone that can scale with your business without SaaS pricing surprises.
If you want Samioda to deploy this setup for you, harden it, and add monitoring and automated backups, contact us via our automation services page.
FAQ
More in Business Automation
All →n8n Error Handling in Production: Retries, Dead-Letter Flows, and Alerting
A practical guide to n8n error handling in production — including retry strategies, idempotency, partial failure patterns, dead-letter flows, and Slack or email alerting you can reuse.
Workflow Automation ROI: How to Calculate Your Savings (Formulas + Examples)
Learn how to calculate workflow automation ROI with practical formulas and examples covering time savings, error reduction, and scalability.
Email Automation Best Practices for Business Growth (Welcome, Drip, and Transactional)
Learn email automation best practices for growth: welcome sequences, drip campaigns, and transactional emails—plus how to build reliable workflows with n8n.
Need help with your project?
We build custom solutions using the technologies discussed in this article. Senior team, fixed prices.
Related Articles
n8n Error Handling in Production: Retries, Dead-Letter Flows, and Alerting
A practical guide to n8n error handling in production — including retry strategies, idempotency, partial failure patterns, dead-letter flows, and Slack or email alerting you can reuse.
n8n Webhook Tutorial: Automate Anything with Webhooks (2026 Step-by-Step)
A practical n8n webhook tutorial that shows how to capture webhook events, transform data, handle errors, and ship reliable automations with real examples.
How to Automate Your CRM with n8n: Practical Guide (Lead Scoring, Follow-ups, Reporting)
A practical 2026 guide to CRM automation n8n: connect HubSpot or Pipedrive, build lead scoring, automated follow-ups, and reporting workflows with copy-pasteable examples.