3 min read
Self-Hosting n8n on a VPS with Docker & Nginx Reverse Proxy

After setting up my VPS with Nginx and HTTPS and automating deployments with GitHub Actions, I wanted to go beyond static hosting.

Prerequisites: This post assumes you already have a VPS running Ubuntu with Nginx, HTTPS (Let’s Encrypt), and Docker installed. If not, check out Bootstrapping My Personal Website on a VPS first.

The goal:

rayeen.in        → website
n8n.rayeen.in    → automation engine

Final Architecture

graph TD
    A[GoDaddy DNS] --> B[Ubuntu VPS]
    B --> C[Nginx]
    C --> D[rayeen.in → Static Site]
    C --> E[n8n.rayeen.in → Docker Container]
    E --> F[n8n on port 5678]

Step 1 — Add DNS Record

In GoDaddy DNS Management, add:

TypeHostPoints to
An8nVPS_IP

Verify:

ping n8n.rayeen.in

Step 2 — Setup n8n with Docker

Create the directory:

mkdir /opt/n8n
cd /opt/n8n

Create docker-compose.yml:

version: "3"

services:
  n8n:
    image: n8nio/n8n
    restart: always
    environment:
      - N8N_HOST=n8n.rayeen.in
      - N8N_PROTOCOL=https
      - WEBHOOK_URL=https://n8n.rayeen.in/
      - N8N_BASIC_AUTH_ACTIVE=true
    volumes:
      - ./n8n_data:/home/node/.n8n
    expose:
      - "5678"

Fix permissions:

chown -R 1000:1000 n8n_data

Start the container:

docker-compose up -d

Note that we use expose instead of ports — this means the container is not directly accessible from the internet. Only Nginx can reach it.


Step 3 — Nginx Reverse Proxy

Create the config:

nano /etc/nginx/sites-available/n8n

Add:

server {
    listen 80;
    server_name n8n.rayeen.in;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name n8n.rayeen.in;

    ssl_certificate /etc/letsencrypt/live/rayeen.in/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/rayeen.in/privkey.pem;

    location / {
        proxy_pass http://localhost:5678;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
    }
}

Enable it:

ln -s /etc/nginx/sites-available/n8n /etc/nginx/sites-enabled/
nginx -t
systemctl reload nginx

Step 4 — Expand SSL

Expand your Let’s Encrypt certificate to cover the new subdomain:

certbot --nginx \
  -d rayeen.in \
  -d www.rayeen.in \
  -d n8n.rayeen.in

Now https://n8n.rayeen.in is live and secured.


Final Thoughts

This journey taught me:

  • DNS issues look like server issues — always verify DNS propagation first
  • SSL errors are often browser cache — try incognito or curl to test
  • Remove default Nginx config — it causes silent routing conflicts
  • Never expose Docker ports publicly — use expose + reverse proxy
  • Automate deployments early — manual SSH deploys don’t scale
  • Infrastructure is part of engineering maturity — it’s not a separate discipline

This isn’t about hosting a website. It’s about owning the stack.