nonso.andre

Installing n8n with Docker and SSL on HestiaCP

September 27, 2025

A complete, slightly maddening, and ultimately triumphant journey to self-host n8n with Docker, SSL magic, and peaceful HestiaCP integration.

Introduction: The Quest for Automation

Ah, n8nβ€”the powerful workflow automation tool that promises to link your digital world. Setting it up on a server running HestiaCP (a control panel known for being a bit…opinionated) is a proper quest. Especially when you’re a responsible server owner who demands SSL security and absolutely, positively refuses to break existing websites.

This isn’t just a guide; it’s the chronicle of my personal, full-contact wrestling match with Docker, NGINX, and HestiaCP. Grab a snack; we’re diving into the digital trenches!

My mission seemed simple, but the devil was in the details. I had a trusty VPS running HestiaCP, already happily hosting several sites. The new requirements felt like asking a grumpy old wizard to adopt a new, energetic apprentice:

  • Self-host n8n using the glorious simplicity of Docker.
  • Wrap it in a shiny, secure SSL certificate. πŸ›‘οΈ
  • Run it on a neat little subdomain (n8n.example.com).
  • Crucially, achieve all this without causing a catastrophic server meltdown or making the existing websites weep.

🧱 Initial Setup: Laying the Docker Foundation

My first few attempts were, let’s just say, character-building. I kept getting the dreaded “n8n command not found” error, a sign of corrupted dreams and broken images. When in doubt, call in the digital bulldozers!

Starting Fresh (The Nuclear Option)

Sometimes, you just need a clean slate. I performed a full exorcism on the previous failed attempts:

Bash

# Don't be afraid to be thorough!
sudo rm -rf /opt/n8n
docker compose down --remove-orphans 2>/dev/null || true
docker rmi n8nio/n8n 2>/dev/null || true
docker system prune -f # Free up all that junk!
The Sacred Directory Structure

Following the ancient scrolls (and a helpful Hostinger guide), I set up the home for my new automation overlord:

Bash

# Making a cozy home for n8n
mkdir ~/n8n && cd ~/n8n
mkdir n8n_data

# The secret handshake: setting correct permissions (Docker's must-have)
sudo chown -R 1000:1000 n8n_data

The Holy Grail: Docker Compose Configuration

This is where the magic happens. The Docker Compose file is the blueprint for our container. Pay close attention to the ports lineβ€”it’s a security game-changer!

YAML

services:
  n8n:
    image: n8nio/n8n
    ports:
      - "127.0.0.1:5678:5678"  # 🚨 KEY INSIGHT: ONLY bind to localhost!
    environment:
      # Highly recommended for security!
      - N8N_BASIC_AUTH_ACTIVE=true
      - N8N_BASIC_AUTH_USER=admin
      - N8N_BASIC_AUTH_PASSWORD=yourpassword
      
      # Tell n8n how it will be seen by the outside world (HTTPS)
      - N8N_HOST=n8n.example.com
      - N8N_PORT=443
      - N8N_PROTOCOL=https
      - WEBHOOK_URL=https://n8n.example.com
    volumes:
      - ./n8n_data:/home/node/.n8n
    restart: unless-stopped

πŸ”‘ Key Insight: Binding to 127.0.0.1:5678 is a security masterstroke. It means n8n is only visible inside the server. Only our reverse proxy (NGINX) will be able to talk to it, keeping the rest of the world out.

The HestiaCP Integration Challenge: Taming the Beast

Now for the tricky part: making the friendly-but-stubborn HestiaCP play nicely with our new localhost-only service. HestiaCP has its own special way of doing things, and it doesn’t always like surprises.

My first instinct was to be a good admin and create a proper HestiaCP custom template.

Bash

# This felt right... but was wrong.
sudo cp /usr/local/hestia/data/templates/web/nginx/default.tpl /usr/local/hestia/data/templates/web/nginx/n8n-proxy.tpl

But the panel wouldn’t recognize it. It turns out HestiaCP often demands both NGINX and Apache templates exist simultaneously just to acknowledge a new entry. It’s high-maintenance!

Lesson Learned: Sometimes, elegance loses to pragmatism. When working with control panels, the fastest path is often the direct one.

The Direct Configuration Solution (A Swift Strike) πŸ—‘οΈ

Since HestiaCP already had an SSL certificate provisioned for n8n.example.com (a huge time-saver!), the most reliable path was to simply modify the existing SSL configuration file.

Bash

# We're going straight to the source!
sudo nano /home/user/conf/web/n8n.example.com/nginx.ssl.conf

We completely replace HestiaCP’s default location / block (which usually proxies to Apache) with our new NGINX Reverse Proxy setup. This is the heart of the solution:

Nginx

# Bye-bye, default HestiaCP block! Hello, n8n!
location / {
    # 🎯 This points NGINX to our Docker container on localhost
    proxy_pass http://127.0.0.1:5678; 
    
    # Standard security and header forwarding (crucial for n8n to know it's HTTPS)
    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_set_header X-Forwarded-Host $host;
    proxy_redirect off;
    
    # πŸ”— WebSockets are a must for n8n's UI and real-time updates!
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

After a quick sudo systemctl restart nginx, the connection was live! πŸŽ‰

Troubleshooting: The Inevitable Road Bumps

Even the best journeys have potholes. Here were the most common issues and how I crushed them:

1. The “n8n command not found” Zombie 🧟

If this returns, your Docker image is likely corrupt or the setup is messy.

  • The Fix: Go back to the “Clean Slate” approach. Kill the container, delete the image, and pull fresh!
2. Duplicate Location Block Errors

HestiaCP tries to be helpful, sometimes too helpful. If you try to create custom files (nginx.conf_custom or nginx.ssl.conf_custom), NGINX will complain about conflicting rules.

  • The Fix: Don’t use the custom include files for the main location block. Stick to editing the core HestiaCP-generated file (nginx.ssl.conf) and remove the conflicting custom files.
3. “We’re working on it!” Page

This is HestiaCP’s sad default page. If you see it, your reverse proxy isn’t doing its job.

  • The Fix: You didn’t properly replace the location / block in nginx.ssl.conf. Check the file, line by line, to ensure the NGINX configuration above is fully present and correct.

Final Configuration Verification (The Victory Lap)

Before cracking open the virtual champagne, we test every component. Trust but verify!

The Checklist
  1. NGINX Health:Bashsudo nginx -t # Must say "test is successful"
  2. Docker Security Check: Is n8n truly hidden?Bashsudo netstat -tulpn | grep 5678 # Must show: tcp 0 0 127.0.0.1:5678 - (Only localhost!)
  3. Local Container Response: Is n8n awake?Bashcurl http://127.0.0.1:5678 # Should get an HTML response from n8n
  4. External SSL Test: The moment of truth!Bashcurl -I https://n8n.example.com # Look for a 200 OK or 301 redirect.
The Result

βœ… NGINX is happy.

βœ… Docker is secure.

βœ… The reverse proxy is functional.

Our n8n instance is now live, secure with SSL, and accessible via https://n8n.example.com, all without ruffling any of HestiaCP’s feathers. Success!

Lessons Learned (The Scrolls of Wisdom)

  1. Don’t Expose the Baby! πŸ‘Ά Never bind a service like n8n directly to all interfaces (0.0.0.0). Bind it to 127.0.0.1 and let the secured NGINX handle public access.
  2. HestiaCP’s Will: When a control panel gets involved, forget the textbook and find the path of least resistance. Direct configuration editing often wins over struggling with custom templates.
  3. WebSockets are Non-Negotiable: The specific Upgrade and Connection headers in the NGINX block are vital for n8n’s interactive user interface. Don’t skip them!

nonso.andre.blog
Blogging about Tech, family, sports, and life from my point of view. Highly relevant for life engineers and managers, useful for those working to make life work.

Become an early subscriber

No thanks >