BuildWithMatija
  1. Home
  2. Blog
  3. Docker
  4. How to Self-Host n8n on Your VPS (Simple, Secure, and Production-Ready)

How to Self-Host n8n on Your VPS (Simple, Secure, and Production-Ready)

Docker deployment secured with Cloudflare Tunnel, hardened access, and production-ready tweaks

10th October 2025·Updated on:3rd June 2026··
Docker
How to Self-Host n8n on Your VPS (Simple, Secure, and Production-Ready)

🐳 Docker & DevOps Implementation Guides

Complete Docker guides with optimization techniques, deployment strategies, and automation prompts to streamline your containerization workflow.

No spam. Unsubscribe anytime.

📄View markdown version
0

Frequently Asked Questions

About the author

Matija Žiberna

Matija Žiberna

Full-stack developer, co-founder

AboutResume

Self-taught full-stack developer sharing lessons from building software and startups.

I'm Matija Žiberna, a self-taught full-stack developer and co-founder passionate about building products, writing clean code, and figuring out how to turn ideas into businesses. I write about web development with Next.js, lessons from entrepreneurship, and the journey of learning by doing. My goal is to provide value through code—whether it's through tools, content, or real-world software.

Contents

  • Why Self-Host n8n?
  • 1. Set Up Your VPS
  • 2. Run n8n in Docker
  • 3. Expose n8n Securely with Cloudflare Tunnel
  • Install `cloudflared`
  • Authenticate with Cloudflare
  • Create a Named Tunnel
  • Route a Subdomain
  • 4. Start n8n Again with Your Correct Domain
  • 5. Connect the Tunnel to n8n
  • 6. Keep the Tunnel Running with systemd
  • 7. Add Basic Authentication
  • 8. (Optional) Use PostgreSQL for Production
  • 9. Verify Everything Works
  • 10. Conclusion
On this page:
  • Why Self-Host n8n?
  • 1. Set Up Your VPS
  • 2. Run n8n in Docker
  • 3. Expose n8n Securely with Cloudflare Tunnel
  • 4. Start n8n Again with Your Correct Domain
Build with Matija logo

Build with Matija

Modern websites, content systems, and AI workflows built for long-term growth.

Services

  • Headless CMS Websites
  • Next.js & Headless CMS Advisory
  • AI Systems & Automation
  • Website & Content Audit

Resources

  • Case Studies
  • How I Work
  • Blog
  • CMS Hub
  • E-commerce Hub
  • Dashboard

Headless CMS

  • Payload CMS Developer
  • CMS Migration
  • Multi-Tenant CMS
  • Payload vs Sanity
  • Payload vs WordPress
  • Payload vs Contentful

Get in Touch

Ready to modernize your stack? Let's talk about what you're building.

Book a discovery callContact me →
© 2026Build with Matija•All rights reserved•Privacy Policy•Terms of Service
BuildWithMatija
Get In Touch

If you’ve been using n8n Cloud but want full control of your automations (and to stop paying monthly fees), self-hosting n8n on your own VPS is a great move.

In this guide, I’ll show you how to install n8n on any VPS using Docker, expose it securely to the public internet using Cloudflare Tunnel (no Nginx, no Let’s Encrypt, no open ports), and configure it for production with your real domain.


Why Self-Host n8n?

Running n8n yourself gives you:

  • Full data ownership — credentials and workflows stay on your server
  • Unlimited workflows for a fixed VPS cost
  • Better integration with private systems and APIs
  • More control over scaling, logging, and automation triggers

Even a small 2 GB VPS can handle dozens of n8n workflows comfortably.


1. Set Up Your VPS

You can use any provider — Hetzner, DigitalOcean, AWS Lightsail, etc. Make sure it’s running Ubuntu 22.04 LTS or newer.

Update and install Docker:

bash
sudo apt update && sudo apt upgrade -y
sudo apt install docker.io curl -y
sudo systemctl enable --now docker

Verify Docker is running:

bash
docker ps

2. Run n8n in Docker

Create a Docker volume to persist data:

bash
docker volume create n8n_data

Start n8n for the first time:

bash
docker run -d \
  --name n8n \
  -p 5678:5678 \
  -e GENERIC_TIMEZONE="CET" \
  -v n8n_data:/home/node/.n8n \
  docker.n8n.io/n8nio/n8n

At this point, n8n is running locally on port 5678. Instead of exposing that port to the public, we’ll secure it through Cloudflare.


3. Expose n8n Securely with Cloudflare Tunnel

You could use Nginx and Let’s Encrypt manually, but Cloudflare Tunnel makes it simpler and safer — no ports, no certificates, and automatic HTTPS.

If you want a detailed explanation of how tunnels work and why they replace reverse proxies, check out: Expose Any Docker Container to the Public with Cloudflare Tunnel (No Nginx, No Open Ports)

Install cloudflared

bash
sudo mkdir -p --mode=0755 /usr/share/keyrings
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared bullseye main' | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt update && sudo apt install cloudflared -y

Authenticate with Cloudflare

bash
cloudflared login

Choose your domain and authorize through the browser. Credentials are saved at ~/.cloudflared/cert.pem.

Create a Named Tunnel

bash
cloudflared tunnel create n8n-tunnel

Route a Subdomain

For example, route automate.we-hate-copy-pasting.com:

bash
cloudflared tunnel route dns n8n-tunnel automate.we-hate-copy-pasting.com

4. Start n8n Again with Your Correct Domain

Now that your tunnel and domain are ready, restart n8n with the proper production configuration.

bash
docker stop n8n && docker rm n8n

docker run -d \
  --name n8n \
  -p 5678:5678 \
  -e GENERIC_TIMEZONE="CET" \
  -e TZ="CET" \
  -e N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true \
  -e N8N_RUNNERS_ENABLED=true \
  -e WEBHOOK_URL="https://automate.we-hate-copy-pasting.com/" \
  -v n8n_data:/home/node/.n8n \
  docker.n8n.io/n8nio/n8n

The WEBHOOK_URL variable ensures that external triggers, webhooks, and integrations use your real HTTPS domain — essential for production deployments.


5. Connect the Tunnel to n8n

Run this to connect the Cloudflare Tunnel to your local n8n instance:

bash
cloudflared tunnel --url http://localhost:5678 run n8n-tunnel

After a few seconds, you can open:

code
https://automate.we-hate-copy-pasting.com

You’ll see the n8n editor live, served securely through Cloudflare.


6. Keep the Tunnel Running with systemd

Create a persistent configuration file.

File: ~/.cloudflared/config.yml

yaml
tunnel: n8n-tunnel
credentials-file: /home/ubuntu/.cloudflared/<tunnel-id>.json
ingress:
  - hostname: automate.we-hate-copy-pasting.com
    service: http://localhost:5678
  - service: http_status:404

Then create a systemd service:

File: /etc/systemd/system/cloudflared-n8n.service

ini
[Unit]
Description=Cloudflared Tunnel for n8n
After=network.target

[Service]
User=ubuntu
ExecStart=/usr/bin/cloudflared --config /home/ubuntu/.cloudflared/config.yml tunnel run n8n-tunnel
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

Enable and start it:

bash
sudo systemctl daemon-reload
sudo systemctl enable --now cloudflared-n8n.service

Check status:

bash
sudo systemctl status cloudflared-n8n

Now your tunnel reconnects automatically after reboots or connection drops.


7. Add Basic Authentication

Protect your n8n dashboard by enabling Basic Auth:

bash
docker stop n8n && docker rm n8n

docker run -d \
  --name n8n \
  -p 5678:5678 \
  -e GENERIC_TIMEZONE="CET" \
  -e TZ="CET" \
  -e WEBHOOK_URL="https://automate.we-hate-copy-pasting.com/" \
  -e N8N_BASIC_AUTH_ACTIVE=true \
  -e N8N_BASIC_AUTH_USER=admin \
  -e N8N_BASIC_AUTH_PASSWORD=strongpassword \
  -v n8n_data:/home/node/.n8n \
  docker.n8n.io/n8nio/n8n

Now you’ll be prompted for credentials before accessing the UI.


8. (Optional) Use PostgreSQL for Production

SQLite is fine for small setups, but PostgreSQL is better for reliability and multi-user workflows. You can easily launch a Dockerized database right next to n8n — I’ve written a separate article for that: How to Quickly Launch a Docker Instance of PostgreSQL on Your VPS

Then update your n8n container with:

bash
-e DB_TYPE=postgresdb \
-e DB_POSTGRESDB_DATABASE=n8n \
-e DB_POSTGRESDB_HOST=localhost \
-e DB_POSTGRESDB_PORT=5432 \
-e DB_POSTGRESDB_USER=n8nuser \
-e DB_POSTGRESDB_PASSWORD=supersecret \

9. Verify Everything Works

Visit your domain:

code
https://automate.we-hate-copy-pasting.com

You should see your self-hosted n8n instance — encrypted, authenticated, and backed by Cloudflare’s network. No open ports, no Nginx, no manual certificates.


10. Conclusion

You now have your own production-ready n8n Cloud running on your VPS:

  • Docker handles isolation and persistence
  • Cloudflare Tunnel provides secure HTTPS without opening ports
  • WEBHOOK_URL ensures all workflows use your real domain
  • systemd keeps it online automatically
  • PostgreSQL (optional) prepares it for scale

You fully own your automation stack — and you can reuse this exact setup for any self-hosted service.

If you want to dive deeper into the Cloudflare setup or connect a database next, check out:

  • Expose Any Docker Container to the Public with Cloudflare Tunnel (No Nginx, No Open Ports)
  • How to Quickly Launch a Docker Instance of PostgreSQL on Your VPS
  • Enable External npm Packages and Built-in Node Modules in n8n's Code Node — the default Docker setup blocks them, and this covers the exact env vars to set in your compose.yml

Thanks, Matija