Workflow automation becomes mission-critical faster than most teams expect. What starts as a handful of webhook handlers and scheduled jobs often grows into an internal integration backbone that handles lead routing, support escalations, invoice enrichment, and provisioning tasks. At that point, a fragile single-container setup is no longer enough. This guide shows how to run n8n in a production-oriented way on Ubuntu using Docker Compose, Nginx, and PostgreSQL, with practical controls for secrets, TLS, backups, and day-2 operations.
The goal is not a demo instance. The goal is a deployment your team can operate confidently: one that survives reboots, supports safe upgrades, keeps credentials out of source control, and gives clear verification checkpoints before you connect real business workflows.
Architecture and flow overview
We will deploy n8n and PostgreSQL as isolated services on a private Docker network. Nginx will terminate HTTPS and reverse-proxy traffic to n8n on the internal network. PostgreSQL will store workflow state, execution metadata, credentials, and user data. Persistent volumes will protect data across container recreations. Backups will export database dumps and encryption-related environment configuration on a schedule.
Request flow is straightforward: browser/API client → Nginx (TLS + security headers) → n8n web service → PostgreSQL for persistence. This pattern keeps n8n off the public network and gives you a familiar edge layer for hardening, rate controls, and certificate management.
Prerequisites
- Ubuntu 22.04 or 24.04 host with sudo access.
- A DNS record (for example
automation.example.com) pointing to the server public IP. - Ports 80 and 443 reachable from the internet.
- Docker Engine + Docker Compose plugin installed.
- At least 2 vCPU, 4 GB RAM, and fast SSD storage for stable execution under moderate workload.
- A secure password manager to store generated secrets.
Step-by-step deployment
1) Prepare host directories and environment file
Create a dedicated project directory and strict-permission environment file. Keep secrets in .env and never commit it to Git.
sudo mkdir -p /opt/n8n/{nginx,postgres,backups}
cd /opt/n8n
sudo touch .env
sudo chmod 600 .env
sudo chown $USER:$USER .env
If the copy button does not work in your browser, manually select the code block and copy it.
Generate strong secrets and write them into /opt/n8n/.env. Replace domain and email values with your own.
N8N_HOST=automation.example.com
N8N_PORT=5678
N8N_PROTOCOL=https
WEBHOOK_URL=https://automation.example.com/
N8N_EDITOR_BASE_URL=https://automation.example.com/
GENERIC_TIMEZONE=UTC
POSTGRES_DB=n8n
POSTGRES_USER=n8n
POSTGRES_PASSWORD=CHANGE_ME_STRONG_DB_PASSWORD
N8N_ENCRYPTION_KEY=CHANGE_ME_32_PLUS_CHAR_SECRET
N8N_BASIC_AUTH_ACTIVE=true
N8N_BASIC_AUTH_USER=opsadmin
N8N_BASIC_AUTH_PASSWORD=CHANGE_ME_STRONG_BASIC_AUTH_PASSWORD
If the copy button does not work in your browser, manually select the code block and copy it.
2) Create Docker Compose configuration
Use named services with explicit restart policies and persistent storage. n8n depends on PostgreSQL health so startup order is cleaner after host reboots.
cat > /opt/n8n/docker-compose.yml <<'YAML'
services:
postgres:
image: postgres:16-alpine
container_name: n8n-postgres
restart: unless-stopped
env_file: .env
environment:
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
volumes:
- /opt/n8n/postgres:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 10
networks:
- n8n_net
n8n:
image: n8nio/n8n:latest
container_name: n8n-app
restart: unless-stopped
env_file: .env
environment:
- 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}
- N8N_HOST=${N8N_HOST}
- N8N_PORT=${N8N_PORT}
- N8N_PROTOCOL=${N8N_PROTOCOL}
- WEBHOOK_URL=${WEBHOOK_URL}
- N8N_EDITOR_BASE_URL=${N8N_EDITOR_BASE_URL}
- GENERIC_TIMEZONE=${GENERIC_TIMEZONE}
- 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}
volumes:
- /opt/n8n/data:/home/node/.n8n
depends_on:
postgres:
condition: service_healthy
networks:
- n8n_net
networks:
n8n_net:
name: n8n_net
YAML
If the copy button does not work in your browser, manually select the code block and copy it.
3) Configure Nginx reverse proxy and TLS
Install Nginx and Certbot, then create a server block that forwards traffic to n8n only on localhost-mapped upstream. This keeps your app interface off direct internet exposure.
sudo apt update
sudo apt install -y nginx certbot python3-certbot-nginx
sudo tee /etc/nginx/sites-available/n8n > /dev/null <<'NGINX'
server {
listen 80;
server_name automation.example.com;
location / {
proxy_pass http://127.0.0.1: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_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 300;
}
}
NGINX
sudo ln -sf /etc/nginx/sites-available/n8n /etc/nginx/sites-enabled/n8n
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d automation.example.com --redirect --agree-tos -m [email protected] --non-interactive
If the copy button does not work in your browser, manually select the code block and copy it.
4) Start services and perform baseline checks
Bring up containers and verify process health before opening the editor to users.
cd /opt/n8n
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --tail=80 n8n
docker compose logs --tail=80 postgres
If the copy button does not work in your browser, manually select the code block and copy it.
5) Configuration and secrets handling best practices
Production incidents in automation platforms frequently trace back to secret handling mistakes rather than application bugs. Keep these controls in place from day one:
- Use a unique
N8N_ENCRYPTION_KEYand store it in a password manager. If lost, credential decryption can fail. - Restrict file permissions on
.envand backup artifacts to least privilege. - Avoid embedding API keys directly inside workflow nodes when team access is broad.
- Rotate database and integration credentials on a defined cadence (for example quarterly or after staff changes).
- Store exported workflow JSON in Git, but never commit credential material.
If your security requirements are stricter, move secrets to a dedicated manager (for example Vault or cloud secret services) and inject them at runtime.
6) Add backup and restore runbook
Backups are useful only when restore is tested. Start with automated PostgreSQL dumps and verify restore to a temporary database at least monthly.
mkdir -p /opt/n8n/backups
cat > /opt/n8n/backup.sh <<'BASH'
#!/usr/bin/env bash
set -euo pipefail
STAMP=$(date +%F-%H%M%S)
source /opt/n8n/.env
docker exec n8n-postgres pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB" | gzip > "/opt/n8n/backups/n8n-db-$STAMP.sql.gz"
find /opt/n8n/backups -type f -name 'n8n-db-*.sql.gz' -mtime +14 -delete
BASH
chmod +x /opt/n8n/backup.sh
(crontab -l 2>/dev/null; echo "15 2 * * * /opt/n8n/backup.sh") | crontab -
If the copy button does not work in your browser, manually select the code block and copy it.
Verification checklist
- Open
https://automation.example.comand confirm login page appears over valid HTTPS. - Run a test workflow with webhook trigger and confirm successful execution in n8n history.
- Confirm PostgreSQL health reports
healthyindocker compose ps. - Validate basic authentication challenge is enforced before editor access.
- Check that backup files are created and compressed in
/opt/n8n/backups. - Simulate container restart (
docker compose restart) and confirm workflows remain intact.
For production readiness, add external uptime checks for the web endpoint and internal alerting on failed executions above a threshold. n8n is often part of revenue-impacting flows; silent failure windows can be expensive.
Common issues and fixes
Issue: 502 Bad Gateway from Nginx
Cause: n8n container not ready or listening on a different port.
Fix: confirm N8N_PORT, inspect docker compose logs n8n, and verify proxy target matches 127.0.0.1:5678.
Issue: Webhooks fail after domain change
Cause: stale WEBHOOK_URL and editor base URL settings.
Fix: update environment variables, redeploy stack, and re-test external triggers.
Issue: Credential decryption errors
Cause: changed or missing N8N_ENCRYPTION_KEY.
Fix: restore original key from secure storage. Treat encryption key continuity as mandatory during migration and restore.
Issue: High latency on complex workflows
Cause: host resource contention, heavy node chains, or synchronous external API bottlenecks.
Fix: increase host resources, optimize workflow branching, and queue non-urgent tasks. Also review database I/O saturation.
FAQ
1) Is n8n suitable for production environments?
Yes, when deployed with persistent storage, secure secret handling, reliable database backing, and operational runbooks for backup and upgrade. The tooling is flexible enough for serious workloads if operated with platform discipline.
2) Should I expose n8n directly without a reverse proxy?
It is better to place n8n behind Nginx (or another hardened proxy) for TLS termination, security headers, centralized logging, and request controls. This architecture is easier to secure and troubleshoot over time.
3) How often should I back up the database?
For most teams, nightly backups are the baseline. Increase frequency if workflows change constantly or if automation failures carry high business impact. Always test restore; backup files alone are not proof of recoverability.
4) Can I scale beyond a single n8n container?
Yes, but scaling strategy depends on execution mode, queue design, and workload profile. Start with a stable single-node deployment, monitor throughput, then move to worker-based architecture with explicit capacity planning.
5) What is the safest way to upgrade n8n?
Pin current image, take a fresh database backup, pull the new image in staging first, run smoke tests on representative workflows, then promote to production during a maintenance window with rollback plan ready.
6) How do I manage API credentials safely inside workflows?
Use n8n credential storage with strong encryption key management, restrict admin access, and avoid pasting secrets into plain text nodes. Rotate keys periodically and revoke immediately when staff roles change.
7) Do I need PostgreSQL, or is SQLite enough?
SQLite is useful for local testing, but PostgreSQL is strongly recommended for production due to better concurrency behavior, data durability controls, and operational tooling for backup/restore and monitoring.
Internal links
- Production Guide: Deploy Appsmith with Docker Compose + Traefik + PostgreSQL on Ubuntu
- Production Guide: Deploy OpenProject with Docker Compose + Caddy + PostgreSQL on Ubuntu
- Production Guide: Deploy Meilisearch with Docker Compose + Caddy on Ubuntu
Talk to us
Need help deploying n8n in production, securing workflow credentials, or building backup and upgrade runbooks your team can trust? We can help with architecture, hardening, migration, and operational readiness.