Samen Steeve
MY SERVICES.
Back to blog
DevOpsDockerAutomationn8nInfrastructure
Deploying n8n in Production on a Shared VPS: Field Retrospective
July 21, 20267 min read

Deploying n8n in Production on a Shared VPS: Field Retrospective

An autopilot automation tool can go wrong: poor isolation on a shared VPS can compromise your production database. Here is how I deployed n8n in production for the TribuneJustice project — a legaltech platform I built from scratch and lead — without disrupting existing infrastructure.

The Context: Making the Most of an Existing VPS

On TribuneJustice, I needed a workflow orchestrator for recurring tasks: notifications, service syncs, and scheduled jobs. Rather than paying for execution-based cloud subscriptions, I chose to self-host n8n on a VPS already running for the project — maximizing existing resources rather than multiplying infrastructure costs.

Architecture Choice: Complete Isolation

— Dedicated PostgreSQL 16 container (officially recommended by n8n over MySQL)
— Isolated Docker network connecting only n8n and its PostgreSQL instance
— n8n exposed exclusively on 127.0.0.1:5678, never directly to the public web
— Existing Nginx configured with a dedicated server_block for n8n.samensteeve.com
— CPU and memory limits on containers to protect main project resources

Step-by-Step Deployment

1. Prepare Dedicated Directory

Terminal — Bash
Bash
sudo mkdir -p /opt/n8n
cd /opt/n8n
sudo mkdir -p n8n_data postgres_data

2. Environment Variables (.env)

.env
Config
N8N_HOST=n8n.samensteeve.com
N8N_PROTOCOL=https
N8N_WEBHOOK_URL=https://n8n.samensteeve.com/
N8N_ENCRYPTION_KEY=<generated_key>
GENERIC_TIMEZONE=Africa/Douala
TZ=Africa/Douala

DB_TYPE=postgresdb
DB_POSTGRESDB_HOST=postgres
DB_POSTGRESDB_DATABASE=n8n
DB_POSTGRESDB_USER=n8n_user
DB_POSTGRESDB_PASSWORD=<strong_password>

POSTGRES_USER=n8n_user
POSTGRES_PASSWORD=<same_strong_password>
POSTGRES_DB=n8n

3. Write docker-compose.yml

docker-compose.yml
Docker
services:
  postgres:
    image: postgres:16-alpine
    container_name: n8n_postgres
    restart: always
    env_file: .env
    volumes:
      - ./postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 5s
      timeout: 5s
      retries: 10
    networks:
      - n8n_net

  n8n:
    image: n8nio/n8n:latest
    container_name: n8n
    restart: always
    env_file: .env
    ports:
      - "127.0.0.1:5678:5678"
    volumes:
      - ./n8n_data:/home/node/.n8n
    depends_on:
      postgres:
        condition: service_healthy
    networks:
      - n8n_net

networks:
  n8n_net:
    driver: bridge

Real-World Debugging Lessons

Three specific issues arose during deployment: volume file permissions (resolved via chown -R 1000:1000), initial Postgres credential mismatch, and a Google Safe Browsing false positive on the newly created login subdomain.

Google Safe Browsing Warning - Dangerous Site on n8n.samensteeve.com
Figure 1: Heuristic Chrome / Google Safe Browsing warning upon first accessing the n8n subdomain.

n8n Up & Running

Once the Google false positive was cleared via Search Console review, the n8n instance became fully accessible and ready for workflow automation.

n8n Dashboard Live - What do you want to build Sam?
Figure 2: Secured, fully operational n8n instance on n8n.samensteeve.com.

What's Running in Production Today

The n8n instance is deployed and secured on n8n.samensteeve.com (2FA, automated PostgreSQL backups). The Lead Qualification Agent runs there in production, connected to the WhatsApp CRM Assistant (built and tested) by a shared CRM Data Table — the core of the system.

Lead Qualification Agent (write) — in production

Autonomous AI agent that intercepts form submissions, enriches data via Tavily, scores the lead 1-10, and writesto the CRM Data Table (upsert by email). Personalized response email sent to the prospect in < 30s. Redis memory, strict Output Parser (constrained JSON schema), retry on Gmail and Tavily. Header-secured webhook + IP filtering — the workflow is published and receives form submissions.

WhatsApp CRM Assistant (read & operational) — awaiting credentials

WhatsApp agent that allows reading and querying the same CRM in natural language — check recent leads, look up by email, update status, send professional emails. Redis memory for conversation context. Built and tested in manual mode; publication only awaits the WhatsApp Business credentials.

The Complete Pipeline

Both workflows form a unified system: the Lead Agent populates the CRM with qualified, enriched leads, while the WhatsApp Assistant allows querying and acting on that data directly from WhatsApp — without opening the n8n interface. Shared Redis memory for conversation continuity between both agents.

— Each workflow has its own security model: Header Auth (n8n-webhook-secret) for the Lead Agent, WhatsApp Business authentication for the CRM Assistant (once its credentials are set).
retryOnFail configured on critical nodes (Gmail, Tavily, HTTP) with 3 attempts.
— Shared Redis memory for conversation persistence.

Takeaways

Proper self-hosting requires robust isolation, security hardening, and proper reverse proxy configuration. It ensures cost-effective workflow automation without compromising production reliability.

A critical technical project?

Looking for an experienced engineer to design your architecture, audit your code, or integrate AI?

Start a project