Run n8n with PostgreSQL on a private Docker network and put HTTPS in front of it. Create the owner account before exposing the service. Pass N8N_ENCRYPTION_KEY explicitly to both main and worker containers. Existing installations must preserve their original key. Back up that key and the database together, then test an isolated restore.
By LK Wood IV · 2026-06-09 · ~11 min read · St. Louis County, MO
n8n is a workflow automation tool with 400+ integrations — the self-hosted alternative to Zapier, Make, and IFTTT. The hosted version charges per workflow execution. Self-hosted runs on your own machine with no execution limits, full access to internal services, and no data leaving your network.
This guide sets up n8n with PostgreSQL, persistent storage, HTTPS via Nginx Proxy Manager, and automated backups.
What you’ll have at the end
- n8n running in Docker with PostgreSQL as the database
- Accessible at
n8n.yourdomain.comwith valid HTTPS - Automatic backups of the n8n database and workflows
- Ready to connect to internal homelab services (Proxmox API, Grafana, Home Assistant)
Prerequisites
- Docker installed and running on your homelab host
- Nginx Proxy Manager already set up with a wildcard SSL cert (or you’ll use direct IP access)
- A
proxyDocker network (created by the NPM guide):docker network create proxy
Step 1: Docker Compose setup
mkdir -p /opt/stacks/n8n && cd /opt/stacks/n8n
Create the compose file with n8n and PostgreSQL:
# /opt/stacks/n8n/docker-compose.yml
services:
postgres:
image: postgres:16-alpine
container_name: n8n-postgres
restart: unless-stopped
environment:
POSTGRES_USER: n8n
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: n8n
PGDATA: /var/lib/postgresql/data/pgdata
volumes:
- ./postgres:/var/lib/postgresql/data
networks:
- n8n-internal
healthcheck:
test: ["CMD-SHELL", "pg_isready -U n8n"]
interval: 5s
timeout: 5s
retries: 5
n8n:
image: n8nio/n8n:latest
container_name: n8n
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
environment:
# Database
DB_TYPE: postgresdb
DB_POSTGRESDB_HOST: postgres
DB_POSTGRESDB_PORT: 5432
DB_POSTGRESDB_DATABASE: n8n
DB_POSTGRESDB_USER: n8n
DB_POSTGRESDB_PASSWORD: ${DB_PASSWORD}
N8N_ENCRYPTION_KEY: "${N8N_ENCRYPTION_KEY:?Preserve the existing key or set a key for a new installation}"
# Server config
N8N_HOST: n8n.yourdomain.com
N8N_PORT: 5678
N8N_PROTOCOL: https
WEBHOOK_URL: https://n8n.yourdomain.com/
# Timezone
GENERIC_TIMEZONE: America/Chicago
TZ: America/Chicago
volumes:
- ./n8n:/home/node/.n8n
networks:
- n8n-internal
- proxy
networks:
n8n-internal:
driver: bridge
proxy:
external: true
For a new installation only, create the environment file below. It refuses to overwrite an existing .env or data directory. Run it before creating the data directories. Install OpenSSL first if openssl is unavailable.
(
set -e
umask 077
if [ -e .env ] || [ -e ./n8n ] || [ -e ./postgres ]; then
printf 'Existing settings or data found. Preserve the current keys; use the migration note below.\n' >&2
exit 1
fi
db_password=$(openssl rand -hex 32)
encryption_key=$(openssl rand -hex 32)
set -o noclobber
printf 'DB_PASSWORD=%s\nN8N_ENCRYPTION_KEY=%s\n' \
"$db_password" "$encryption_key" > .env
chmod 600 .env
)
For an existing installation, keep its key. First back up the database, .env, and ./n8n privately. If N8N_ENCRYPTION_KEY is already supplied to the running service, preserve that exact value. Otherwise retain the encryptionKey value from the existing ./n8n/config file. Put that same value in .env using a private editor, preserving the current DB_PASSWORD. Keep keys out of logs. Do not paste them into support requests.
The environment entries above and in the worker block below pass the key into both containers. A Compose .env file supplies interpolation values; it does not inject them into a container by itself. Stop the main process and any workers for the configuration change, then recreate them together with the unchanged key. Check an existing credential afterward. If the original key is lost, generating another cannot decrypt the old credentials.
Create the data directories:
mkdir -p ./postgres ./n8n
Start the stack:
docker compose up -d
# Watch logs to confirm startup
docker compose logs -f n8n
On first startup, n8n runs database migrations which takes 30–60 seconds. You’ll see “Workflow manager is now running” when it’s ready.
Access n8n at http://your-host-ip:5678 or configure NPM to serve it at https://n8n.yourdomain.com.
Create the owner account before anything else can reach it. n8n’s only built-in authentication is its own user management: the first browser to load the instance is offered the owner-account setup form, and until you complete it there is no login wall. The old
N8N_BASIC_AUTH_ACTIVE/_USER/_PASSWORDvariables are gone — they appear in a lot of older homelab compose files, and on a current image they are silently ignored rather than rejected, so a stack that looks password-protected is not. Keep port 5678 on the LAN, load the UI, set the owner e-mail and password, and only then point a public hostname at it.
Pin the n8n version — before going to production, pin to a specific tag:
image: n8nio/n8n:2.35.7 # current stable on 2026-08-22; check hub.docker.com/r/n8nio/n8n/tags
This prevents surprise breaking changes from :latest upgrades. Pick whatever the current stable is on the day you install rather than copying the number above — it ages.
Coming from an n8n 1.x install? 2.0 (released 2025-12-08) is a real major, not a point release. It enforces settings-file permissions by default, disables the
Execute CommandandLocal File Triggernodes unless you opt back in, drops theN8N_CONFIG_FILESvariable and the--tunneloption, and removes the in-memory binary-data mode. Back up./n8nand the database, then read n8n’s release notes before you bump the tag.
Step 2: Configure Nginx Proxy Manager
In NPM → Add Proxy Host:
- Domain:
n8n.yourdomain.com - Scheme:
http - Forward hostname:
n8n(container name, on the proxy network) - Port:
5678 - Websockets Support: ON (n8n uses websockets for the editor)
- SSL: wildcard cert, Force SSL on
Visit https://n8n.yourdomain.com — you should see the n8n sign-in page (or the owner-account setup form, if you skipped it above).
Step 3: First login and owner account
If you did not create the owner account in Step 1, n8n shows the setup form now. The owner is the n8n admin account and the main login wall the instance has, so give it a strong, unique password. n8n does support two-factor authentication with an authenticator app under Settings > Personal, and turning it on is worth the minute it costs. What there is no longer is a separate basic-auth layer to switch on or off — the N8N_BASIC_AUTH_* variables in older guides are ignored by current images. I would put any second layer in NPM, as an access list restricted to your LAN and Tailscale ranges, not in n8n.
Step 4: Connect your first integration
n8n’s integration list is at the bottom-left “Credentials” section. To connect a service:
- Click “Add Credential” → search for your service (Gmail, Slack, GitHub, Airtable, etc.)
- Follow the OAuth flow or paste the API key
- Credentials are stored encrypted in the PostgreSQL database
Connecting to homelab services:
n8n can reach any LAN IP directly. In an HTTP Request node:
- URL:
http://192.168.1.2:8006/api2/json/nodes(Proxmox API) - Authentication: Header Auth with
Authorization: PVEAPIToken=user@pam!name=token-value(pamis the default Proxmox login backend)
This works because n8n is running inside your LAN — the request goes directly to Proxmox without any internet hop.
Step 5: Example workflows
Workflow 1: Notify when a Proxmox VM stops (via Grafana alert webhook)
- Trigger: Webhook (n8n generates a URL you paste into Grafana Alertmanager)
- Node: IF → check if alert status is “firing”
- Node: Send notification to Discord/Telegram/email
Workflow 2: Daily Homelab health summary
- Trigger: Schedule → daily at 8am
- HTTP Request:
http://uptime-kuma:3001/api/status-page/...→ get service status - HTTP Request: Proxmox API → get VM/LXC status
- Function: format the data into a summary
- Notification: send to Discord/Telegram
Workflow 3: Auto-backup trigger after Immich import
- Trigger: Webhook (call from an immich-go post-import script)
- Execute Command node: run your restic backup script
- Notification: send completion status to Slack
Step 6: Queue mode (optional, for reliability)
Queue mode offloads workflow execution to separate worker processes via Redis. This prevents the main n8n process from being blocked by long-running workflows and recovers from crashes without losing in-progress jobs.
Add Redis to the compose file:
redis:
image: redis:7-alpine
container_name: n8n-redis
restart: unless-stopped
networks:
- n8n-internal
volumes:
- ./redis:/data
Update n8n’s environment in the compose file:
EXECUTIONS_MODE: queue
QUEUE_BULL_REDIS_HOST: redis
QUEUE_BULL_REDIS_PORT: 6379
Add a worker service:
n8n-worker:
image: n8nio/n8n:latest # same version as n8n
container_name: n8n-worker
restart: unless-stopped
command: worker
depends_on:
- n8n
- redis
- postgres
environment:
DB_TYPE: postgresdb
DB_POSTGRESDB_HOST: postgres
DB_POSTGRESDB_PORT: 5432
DB_POSTGRESDB_DATABASE: n8n
DB_POSTGRESDB_USER: n8n
DB_POSTGRESDB_PASSWORD: ${DB_PASSWORD}
N8N_ENCRYPTION_KEY: "${N8N_ENCRYPTION_KEY:?Preserve the existing key or set a key for a new installation}"
QUEUE_BULL_REDIS_HOST: redis
QUEUE_BULL_REDIS_PORT: 6379
volumes:
- ./n8n:/home/node/.n8n
networks:
- n8n-internal
Redeploy:
docker compose up -d
Step 7: Backups
n8n data lives in two places:
- PostgreSQL database — workflows, credentials, execution history
./n8nvolume — local files, SSH keys, custom nodes
Database backup:
# Manual backup
docker exec n8n-postgres pg_dump -U n8n n8n > /mnt/backups/n8n/n8n-$(date +%Y%m%d).sql
# Automated via cron
echo "0 4 * * * root docker exec n8n-postgres pg_dump -U n8n n8n > /mnt/backups/n8n/n8n-\$(date +\%Y\%m\%d).sql" >> /etc/crontab
Credentials in the database need the same encryption key after restoration. Back up the database, .env, and ./n8n/config together with access restricted to you. In this setup the explicitly supplied N8N_ENCRYPTION_KEY is part of the recovery material. A key n8n generated for an older installation may instead be in its config file.
n8n requires the main process and workers to share the same key. Restore that key before starting a recovery instance. Check an existing credential in an isolated restore; keeping a new random string in .env is not a substitute for the key that encrypted the backup.
Resource usage
On a Debian 12 LXC with PostgreSQL, n8n idle, 15 active workflows:
| Service | RAM |
|---|---|
| n8n | ~180 MB |
| PostgreSQL | ~60 MB |
| Redis (if using queue mode) | ~15 MB |
| Total | ~255 MB |
n8n is not resource-heavy at idle. CPU usage spikes during workflow execution proportional to the complexity of the workflow, not the number of workflows defined.
n8n pairs well with Uptime Kuma for monitoring-triggered workflows — the Docker Compose starter stack has both running on the same proxy network. For the broader self-hosted app ecosystem n8n fits into, see the 12 best self-hosted apps guide.
Sources
- Install n8n with Docker – official docs for the recommended self-hosted Docker deployment.
- Enable queue mode – official docs on Redis-backed workers, queue mode, and scaling.
- Use environment variables – official docs for the database, host, and encryption-key configuration variables.
Frequently asked questions
Why self-host n8n instead of using n8n cloud?
Do I need queue mode for n8n?
Can n8n access services inside my LAN?
How do I update self-hosted n8n?
What database does n8n use?
Evidence ledger
- Last updated
- Methodology
- See our methodology for research and review standards.
- Update log
- 2026-09-08 — Corrected encryption-key recovery advice: .env values were never passed into the n8n container. Main and worker environment blocks now explicitly require N8N_ENCRYPTION_KEY. New-install generation refuses existing settings/data; existing installations must preserve their effective key and database password. Docker environment and n8n shared-key documentation checked 2026-09-08. No real credentials were accessed and no full n8n restore was performed.
- 2026-08-22 — Version-pin example refreshed from 2.34.6 (dated 2026-08-14) to 2.35.7, published 2026-08-21 per the GitHub releases API, checked 2026-08-22. The surrounding advice is unchanged and still tells you to pin whatever is current on the day you install rather than copying this number.
- 2026-08-22 — Removed two leftover basic-auth references (Step 2’s closing line and all of Step 3) that survived the 2026-08-14 correction: they told readers to expect a basic-auth challenge and to ‘disable n8n’s built-in basic auth’, although the same page had already established that the N8N_BASIC_AUTH_* variables are gone from n8n’s environment-variable reference and are ignored by current images. Step 3 now describes the owner-account form as the only login wall and points a second layer at NPM access lists.
- 2026-08-14 — Removed the N8N_BASIC_AUTH_ACTIVE / _USER / _PASSWORD block that this guide shipped under a ‘# Security’ comment, and the two matching .env lines. n8n’s current environment-variable reference (docs.n8n.io/deploy/host-n8n/configure-n8n/basic-configuration/use-environment-variables/deployment, retrieved 2026-08-14) documents no BASIC_AUTH variable at all – the full variable index returns zero matches. A current n8n image ignores those variables silently rather than erroring, so the stack described here looked password-protected and was not, which mattered because the same guide then proxies it to a public hostname. Replaced with n8n’s actual auth model: the owner account created on first load. Also re-pinned the example image tag from 1.87.0 to 2.34.6 (n8n GitHub releases, retrieved 2026-08-14) and added the n8n 2.0 breaking-change note – 2.0 shipped 2025-12-08 and enforces settings-file permissions, disables Execute Command and Local File Trigger by default, drops N8N_CONFIG_FILES and –tunnel, and removes in-memory binary data mode, so a tag bump from a 1.x install is not a drop-in. Version strings removed from the diagram and its alt text so the picture cannot drift from the compose block again.
- Corrections
- Spotted an error or a stale number? Email hello@techfuelhq.com. Confirmed corrections are added to the update log above.