Skip to Content

Production Guide: Deploy n8n with Docker Compose + Nginx + PostgreSQL on Ubuntu

A production-ready n8n deployment pattern with TLS, PostgreSQL durability, secret hygiene, backup runbooks, and operational verification.

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_KEY and store it in a password manager. If lost, credential decryption can fail.
  • Restrict file permissions on .env and 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.com and 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 healthy in docker 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

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.

Contact Us

Production Guide: Deploy Meilisearch with Docker Compose + Caddy on Ubuntu
A production-first Meilisearch deployment with TLS, snapshots, health checks, backups, and practical runbooks.