Use private networking for n8n, Postgres and Redis on a VPS

Keep databases and queues private. Set up networking for n8n, Postgres and Redis with firewalls, Docker and VPNs.
Abstract 3D wave background featuring vibrant, colorful swirls creating a dynamic and fluid visual effect.

I learned this the hard way. The first time I put n8n in production I left Redis listening on 0.0.0.0 with no password because it “was just for a minute.” A week later I saw connection attempts from random hosts. Nothing happened, but it was a good reminder: the safest service is the one that never touches the public internet.

This guide shows how I isolate Postgres and Redis behind private networking, expose only the reverse proxy to the outside world, then connect instances cleanly across a private subnet or a zero-trust mesh.

If you are still planning the full setup the big picture lives in the self-host n8n on VPS guide.

If you are already running queue mode you may also want the details in n8n queue mode: scaling with Redis and workers and Scaling n8n with Redis.

What private networking means here

  • Only the reverse proxy is public. n8n’s editor and webhooks sit behind Nginx or Caddy on port 443.
  • Datastores live on private interfaces. Postgres and Redis bind to 127.0.0.1 or a private RFC1918 subnet.
  • Firewall denies by default. Allow 80/443 for the proxy, 22 for SSH from a trusted IP, and nothing else.
  • Inter-VPS traffic runs over a private subnet or a mesh like WireGuard or Tailscale.
  • Docker creates isolated networks. Services talk on user-defined networks with internal: true.

For a bigger security picture, see n8n security best practices.

Reference topology

  • edge-1: reverse proxy, public, HTTPS only
  • n8n-main: editor and webhook receiver, private network
  • postgres-1: Postgres on private IP 10.10.0.10
  • redis-1: Redis on private IP 10.10.0.11
  • worker-*: n8n workers, private only

Docker Compose with internal networks

yaml
version: "3.9"

networks:
public:
driver: bridge
private:
driver: bridge
internal: true

volumes:
pgdata:
n8n_data:

services:
caddy:
image: caddy:2
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
depends_on:
- n8n-main
networks:
- public
- private

n8n-main:
image: n8nio/n8n
restart: unless-stopped
environment:
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_DATABASE=n8n
- DB_POSTGRESDB_USER=n8n
- DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
- QUEUE_BULL_REDIS_HOST=redis
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
- N8N_HOST=automation.example.com
- N8N_PROTOCOL=https
- WEBHOOK_URL=https://automation.example.com/

- N8N_PORT=5678
volumes:
- n8n_data:/home/node/.n8n
depends_on:
- postgres
- redis
networks:
- private

n8n-worker:
image: n8nio/n8n
restart: unless-stopped
environment:
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_DATABASE=n8n
- DB_POSTGRESDB_USER=n8n
- DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
- QUEUE_BULL_REDIS_HOST=redis
- EXECUTIONS_PROCESS=queue
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
depends_on:
- postgres
- redis
deploy:
replicas: 2
networks:
- private

postgres:
image: postgres:15
restart: unless-stopped
environment:
- POSTGRES_USER=n8n
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=n8n
command: ["postgres","-c","listen_addresses=127.0.0.1,postgres"]
volumes:
- pgdata:/var/lib/postgresql/data
networks:
private:
aliases: [ "postgres" ]

redis:
image: redis:7
restart: unless-stopped
command: ["redis-server","--bind","127.0.0.1,redis","--appendonly","yes","--requirepass","${REDIS_PASSWORD}"]
networks:
private:
aliases: [ "redis" ]

Notes: Postgres and Redis bind only to loopback or service name. The proxy touches both networks. The private network has internal: true.

Firewall setup

Example with UFW:

ufw --force reset
ufw default deny incoming
ufw default allow outgoing
ufw allow 443/tcp
ufw allow 80/tcp
ufw allow from 203.0.113.10 to any port 22 proto tcp
ufw enable

On database hosts, allow only the private subnet:

ufw allow from 10.10.0.0/24 to any port 5432 proto tcp
ufw allow from 10.10.0.0/24 to any port 6379 proto tcp

Validate with ss -tulpn and nmap from outside.

Postgres configuration

postgresql.conf:

listen_addresses = '127.0.0.1,10.10.0.10'

pg_hba.conf:

host    n8n     n8n     10.10.0.0/24     scram-sha-256

Restart Postgres and point n8n to the private IP. For database choices see PostgreSQL vs SQLite for n8n.

Redis configuration

redis.conf minimal:

bind 127.0.0.1 10.10.0.11
protected-mode yes
requirepass strongpassword
appendonly yes

Check with:

ss -ltnp | grep -E '5432|6379'

Workers connect with:

QUEUE_BULL_REDIS_HOST=10.10.0.11
QUEUE_BULL_REDIS_PASSWORD=strongpassword

For scaling details, see n8n queue mode: scaling with Redis and workers.

Zero-trust mesh: WireGuard or Tailscale

If your VPS provider has no VPC, build your own.

WireGuard example:
Assign 10.13.0.0/24, configure peers, allow only required IPs. Bind Postgres to the WireGuard IP.

Tailscale:
Easier to operate, gives you stable private IPs and ACLs. Works fine for Postgres and Redis. Just use the Tailscale IPs in your n8n .env.

Environment variables for private networking

N8N_HOST=automation.example.com
N8N_PROTOCOL=https
WEBHOOK_URL=https://automation.example.com/
N8N_PORT=5678

DB_TYPE=postgresdb
DB_POSTGRESDB_HOST=10.10.0.10
DB_POSTGRESDB_DATABASE=n8n
DB_POSTGRESDB_USER=n8n
DB_POSTGRESDB_PASSWORD=secret

QUEUE_BULL_REDIS_HOST=10.10.0.11
QUEUE_BULL_REDIS_PASSWORD=secret

N8N_ENCRYPTION_KEY=32-byte-secret

If webhook URLs look wrong in the UI, check the reverse proxy article: Fix webhook URL issues in n8n behind a reverse proxy.

Monitoring and health checks

Migrating an existing public setup

  1. Map exposed services with ss and docker ps.
  2. Add a private subnet or Tailscale.
  3. Move Postgres and Redis to private bind addresses.
  4. Adjust firewall, leaving only 80/443 public.
  5. Test from outside with nmap.

When you actually need public listeners

Only the reverse proxy should be public. Webhooks, editor access, SSL termination. Postgres and Redis must never be public.

If you want to explore adding AI integrations, see Integrating AI into n8n on a VPS.

FAQ

Should I split Postgres and Redis onto their own VPS?

For small installs you can keep them together. As load grows, split Postgres first, then Redis. Always keep them on the same private subnet or mesh.

Do I still need a firewall if services only bind to 127.0.0.1?

Yes. A firewall catches mistakes and enforces intent.

How do I reach Postgres if it has no public IP?

Use SSH port forwarding or a Tailscale/WireGuard connection. Do not expose port 5432.

What about queue mode, does it change anything?

No. Only n8n-main needs to be reachable by the proxy. Workers and datastores stay private.

How do I test that nothing is exposed?

From outside, run nmap -Pn your.server.ip -p 1-65535. You should only see 80, 443 and 22 (if you left SSH public).