<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ali Ilyas</title>
    <description>The latest articles on DEV Community by Ali Ilyas (@ali_ilyas).</description>
    <link>https://dev.to/ali_ilyas</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4064723%2Ff4e6bf6f-f60b-433a-ae47-238bcf783ed1.png</url>
      <title>DEV Community: Ali Ilyas</title>
      <link>https://dev.to/ali_ilyas</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ali_ilyas"/>
    <language>en</language>
    <item>
      <title>How to Self-Host n8n: A Complete Beginner's Guide (2026)</title>
      <dc:creator>Ali Ilyas</dc:creator>
      <pubDate>Wed, 05 Aug 2026 18:19:46 +0000</pubDate>
      <link>https://dev.to/ali_ilyas/how-to-self-host-n8n-a-complete-beginners-guide-2026-412f</link>
      <guid>https://dev.to/ali_ilyas/how-to-self-host-n8n-a-complete-beginners-guide-2026-412f</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://noderecipe.com/self-host-n8n" rel="noopener noreferrer"&gt;noderecipe.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Self-hosting n8n gives you &lt;strong&gt;unlimited workflow executions&lt;/strong&gt;, full ownership of your data, and no per-task fees — all for the cost of a small server (about &lt;strong&gt;$5/month&lt;/strong&gt;). This guide takes you from an empty VPS to a secured, HTTPS-enabled n8n instance running 24/7, using Docker and Docker Compose. You'll also get the environment variables that actually matter, a backup routine, and fixes for the errors that trip up most beginners.&lt;/p&gt;

&lt;p&gt;No prior Docker experience is assumed. If you can copy a command into a terminal, you can finish this in about 30 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-hosted vs. n8n Cloud: which do you need?
&lt;/h2&gt;

&lt;p&gt;Before you spin up a server, be honest about which path fits you. n8n offers a paid cloud plan and a free, source-available self-hosted option (the Community Edition). Here's the trade-off:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Factor&lt;/th&gt;
&lt;th&gt;Self-hosted (Community)&lt;/th&gt;
&lt;th&gt;n8n Cloud&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Price&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~$5/mo server, unlimited executions&lt;/td&gt;
&lt;td&gt;From ~$24/mo, metered executions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data location&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Your server, your control&lt;/td&gt;
&lt;td&gt;n8n's managed infrastructure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Maintenance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;You handle updates &amp;amp; backups&lt;/td&gt;
&lt;td&gt;Fully managed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Setup time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~30 min (this guide)&lt;/td&gt;
&lt;td&gt;Minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Custom nodes / npm packages&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Full control&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best for&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Developers, high-volume, privacy needs&lt;/td&gt;
&lt;td&gt;Non-technical users, teams who want zero ops&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Choose self-hosting if&lt;/strong&gt; you run a lot of executions, want your data on infrastructure you control, or need custom community nodes. &lt;strong&gt;Choose &lt;a href="https://n8n.io/" rel="noopener noreferrer"&gt;Cloud&lt;/a&gt; if&lt;/strong&gt; you never want to think about servers — the honest version is that if your automations make you money and your time is worth more than $20/month, managed hosting is the cheaper option however it looks on the invoice. The rest of this guide covers self-hosting.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll need
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;VPS&lt;/strong&gt; (virtual private server) — roughly $5/month. See the comparison below.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSH access&lt;/strong&gt; to that server (a terminal on Mac/Linux, or a client like PuTTY/Windows Terminal on Windows).&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;domain or subdomain&lt;/strong&gt; you can point at the server (e.g. &lt;code&gt;n8n.yourdomain.com&lt;/code&gt;). This is required for HTTPS and for webhooks to work reliably.&lt;/li&gt;
&lt;li&gt;About &lt;strong&gt;30 minutes&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You do &lt;em&gt;not&lt;/em&gt; need to know Docker beforehand — every command is provided.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to host n8n (VPS comparison)
&lt;/h2&gt;

&lt;p&gt;Any Linux server with 1 GB of RAM will run a small n8n instance, but 2 GB is more comfortable once you have a few active workflows. These are the popular, budget-friendly options:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Entry price&lt;/th&gt;
&lt;th&gt;RAM&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://www.hetzner.com/cloud" rel="noopener noreferrer"&gt;Hetzner Cloud&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~€4.5/mo&lt;/td&gt;
&lt;td&gt;2–4 GB&lt;/td&gt;
&lt;td&gt;Best price-to-performance (EU/US regions)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://www.digitalocean.com/" rel="noopener noreferrer"&gt;DigitalOcean&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$6/mo&lt;/td&gt;
&lt;td&gt;1 GB&lt;/td&gt;
&lt;td&gt;Beginner-friendly UI, great docs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://railway.com/" rel="noopener noreferrer"&gt;Railway&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Usage-based&lt;/td&gt;
&lt;td&gt;Scales&lt;/td&gt;
&lt;td&gt;Fastest setup, no server management&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Home server / Raspberry Pi&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;Varies&lt;/td&gt;
&lt;td&gt;Tinkering, LAN-only automations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For most people starting out, &lt;strong&gt;&lt;a href="https://www.hetzner.com/cloud" rel="noopener noreferrer"&gt;Hetzner's&lt;/a&gt; CX22&lt;/strong&gt; (2 GB RAM) or a &lt;strong&gt;&lt;a href="https://www.digitalocean.com/" rel="noopener noreferrer"&gt;DigitalOcean&lt;/a&gt; basic droplet&lt;/strong&gt; is the sweet spot. Pick Ubuntu 24.04 LTS as the operating system when you create the server.&lt;/p&gt;

&lt;p&gt;Two notes on picking between them, since this is the one decision that costs money. &lt;strong&gt;Hetzner is roughly half the price for double the RAM&lt;/strong&gt; — a CX22 gives you 2 GB for about €4.5, where DigitalOcean's $6 droplet gives you 1 GB. If you are comfortable in a terminal, Hetzner is the better machine for the money. &lt;strong&gt;DigitalOcean is worth the premium if you are new to servers&lt;/strong&gt;: the control panel is friendlier, and their documentation is genuinely the best in the business for the exact moment when something breaks at 11pm. Both give new accounts a signup credit that covers your first month or two.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick start: run n8n in one command
&lt;/h2&gt;

&lt;p&gt;Want to &lt;em&gt;see&lt;/em&gt; n8n before committing to the full setup? SSH into your server and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker volume create n8n_data

docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--restart&lt;/span&gt; unless-stopped &lt;span class="nt"&gt;--name&lt;/span&gt; n8n &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 5678:5678 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; n8n_data:/home/node/.n8n &lt;span class="se"&gt;\&lt;/span&gt;
  docker.n8n.io/n8nio/n8n
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;http://your-server-ip:5678&lt;/code&gt; in your browser and create your owner account. That's a working n8n — but it has &lt;strong&gt;no HTTPS, no database, and no domain&lt;/strong&gt;, so webhooks and OAuth logins will misbehave. Treat this as a test drive, then move to the production setup below.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Don't have Docker yet?&lt;/strong&gt; Install it in one line: &lt;code&gt;curl -fsSL https://get.docker.com | sh&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Production setup with Docker Compose
&lt;/h2&gt;

&lt;p&gt;Docker Compose lets you define n8n, a PostgreSQL database, and a reverse proxy in a single file so they start together and survive reboots. This is the setup you actually want to keep.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Install Docker and Compose
&lt;/h3&gt;

&lt;p&gt;On a fresh Ubuntu server, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://get.docker.com | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Compose plugin ships with modern Docker. Verify both:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nt"&gt;--version&lt;/span&gt;
docker compose version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Point your domain at the server
&lt;/h3&gt;

&lt;p&gt;In your DNS provider (Cloudflare, Namecheap, etc.), create an &lt;strong&gt;A record&lt;/strong&gt; for &lt;code&gt;n8n.yourdomain.com&lt;/code&gt; pointing to your server's public IP. DNS can take a few minutes to propagate — you can continue while it does.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Create the project folder
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/n8n &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; ~/n8n
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Create the &lt;code&gt;.env&lt;/code&gt; file
&lt;/h3&gt;

&lt;p&gt;This holds your secrets and settings. Create &lt;code&gt;~/n8n/.env&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# --- Your domain ---&lt;/span&gt;
&lt;span class="nv"&gt;DOMAIN_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;n8n.yourdomain.com

&lt;span class="c"&gt;# --- Database (Postgres) ---&lt;/span&gt;
&lt;span class="nv"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;n8n
&lt;span class="nv"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;change-this-to-a-long-random-string
&lt;span class="nv"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;n8n

&lt;span class="c"&gt;# --- n8n security ---&lt;/span&gt;
&lt;span class="c"&gt;# Generate with: openssl rand -hex 32&lt;/span&gt;
&lt;span class="nv"&gt;N8N_ENCRYPTION_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;paste-a-32-byte-random-hex-string-here
&lt;span class="nv"&gt;GENERIC_TIMEZONE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;America/New_York
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generate strong values before saving:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 32   &lt;span class="c"&gt;# use for N8N_ENCRYPTION_KEY&lt;/span&gt;
openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 16   &lt;span class="c"&gt;# use for POSTGRES_PASSWORD&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Keep &lt;code&gt;N8N_ENCRYPTION_KEY&lt;/code&gt; safe.&lt;/strong&gt; It encrypts all your saved credentials. If you lose it, every stored credential becomes unreadable and you'll have to re-enter them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 5: Create &lt;code&gt;docker-compose.yml&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;~/n8n/docker-compose.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_USER=${POSTGRES_USER}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD=${POSTGRES_PASSWORD}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_DB=${POSTGRES_DB}&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres_data:/var/lib/postgresql/data&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD-SHELL"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pg_isready&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-U&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;${POSTGRES_USER}&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-d&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;${POSTGRES_DB}"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;

  &lt;span class="na"&gt;n8n&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker.n8n.io/n8nio/n8n:latest&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DB_TYPE=postgresdb&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DB_POSTGRESDB_HOST=postgres&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DB_POSTGRESDB_DATABASE=${POSTGRES_DB}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DB_POSTGRESDB_USER=${POSTGRES_USER}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;N8N_HOST=${DOMAIN_NAME}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;N8N_PROTOCOL=https&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;N8N_PORT=5678&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;WEBHOOK_URL=https://${DOMAIN_NAME}/&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;GENERIC_TIMEZONE=${GENERIC_TIMEZONE}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;N8N_RUNNERS_ENABLED=true&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;n8n_data:/home/node/.n8n&lt;/span&gt;
    &lt;span class="na"&gt;expose&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;5678&lt;/span&gt;

  &lt;span class="na"&gt;caddy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;caddy:2&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;80:80"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;443:443"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./Caddyfile:/etc/caddy/Caddyfile&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;caddy_data:/data&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;caddy_config:/config&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;n8n&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;postgres_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;n8n_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;caddy_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;caddy_config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that n8n uses &lt;code&gt;expose&lt;/code&gt; (internal only) rather than publishing port 5678 to the public internet — Caddy is the only service that faces the outside world.&lt;/p&gt;

&lt;h2&gt;
  
  
  The environment variables that matter
&lt;/h2&gt;

&lt;p&gt;Most self-hosting problems come from missing or wrong environment variables. These are the ones you should always set on a production instance:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;Why it matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;N8N_ENCRYPTION_KEY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Encrypts stored credentials. Set it explicitly and back it up.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;N8N_HOST&lt;/code&gt; / &lt;code&gt;WEBHOOK_URL&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Must match your real domain, or webhooks and OAuth callbacks break.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;N8N_PROTOCOL=https&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tells n8n it's served over HTTPS (behind the proxy).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DB_TYPE=postgresdb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Uses Postgres instead of the default SQLite — required for reliability at scale.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GENERIC_TIMEZONE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Makes Schedule/Cron nodes fire at the times you expect.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;N8N_RUNNERS_ENABLED=true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Enables task runners, the recommended way to execute Code nodes. Required on n8n 1.x; &lt;strong&gt;deprecated from version 2.0&lt;/strong&gt;, where it's on by default and can be omitted.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One more worth knowing about if you import workflows you didn't write: &lt;code&gt;N8N_BLOCK_ENV_ACCESS_IN_NODE&lt;/code&gt; defaults to &lt;code&gt;false&lt;/code&gt;, which means Code nodes and expressions &lt;strong&gt;can read your environment variables&lt;/strong&gt; — including the encryption key and database password above. Setting it to &lt;code&gt;true&lt;/code&gt; closes that off. &lt;a href="https://noderecipe.com/n8n-workflow-templates" rel="noopener noreferrer"&gt;Importing templates safely&lt;/a&gt; covers why that matters and what else to check before running someone else's workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding HTTPS with Caddy
&lt;/h2&gt;

&lt;p&gt;Caddy provisions and renews a free Let's Encrypt certificate automatically — no manual certbot steps. Create &lt;code&gt;~/n8n/Caddyfile&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;n8n.yourdomain.com&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;reverse_proxy&lt;/span&gt; &lt;span class="nf"&gt;n8n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;5678&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;n8n.yourdomain.com&lt;/code&gt; with your real subdomain (it must match &lt;code&gt;DOMAIN_NAME&lt;/code&gt;). That's the entire config — Caddy handles the TLS certificate the first time someone hits the domain.&lt;/p&gt;

&lt;p&gt;Now start everything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/n8n
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Give it a minute (the certificate is issued on first request), then open &lt;code&gt;https://n8n.yourdomain.com&lt;/code&gt;. Create your owner account and you're live — with a valid HTTPS padlock.&lt;/p&gt;

&lt;p&gt;Check that all three containers are healthy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose ps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Keeping n8n running, updated, and backed up
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Auto-restart:&lt;/strong&gt; &lt;code&gt;restart: unless-stopped&lt;/code&gt; in the Compose file means Docker brings your containers back after a crash or server reboot. Nothing else needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Updating n8n:&lt;/strong&gt; pull the newest image and recreate the container. Your data lives in named volumes, so it survives the upgrade:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/n8n
docker compose pull
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Backups — do not skip this.&lt;/strong&gt; Your workflows and credentials live in Postgres and the n8n volume. Back up the database regularly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-T&lt;/span&gt; postgres &lt;span class="se"&gt;\&lt;/span&gt;
  pg_dump &lt;span class="nt"&gt;-U&lt;/span&gt; n8n n8n &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/n8n-backup-&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%F&lt;span class="si"&gt;)&lt;/span&gt;.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy that &lt;code&gt;.sql&lt;/code&gt; file off the server (to your machine, S3, or a backup service) and keep your &lt;code&gt;.env&lt;/code&gt; alongside it — remember, without &lt;code&gt;N8N_ENCRYPTION_KEY&lt;/code&gt; the backup's credentials can't be decrypted. Automate it with a daily &lt;code&gt;cron&lt;/code&gt; job for peace of mind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Securing your instance
&lt;/h2&gt;

&lt;p&gt;A public n8n instance is a target. Lock it down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Firewall:&lt;/strong&gt; allow only ports 22 (SSH), 80, and 443. On Ubuntu: &lt;code&gt;ufw allow 22 &amp;amp;&amp;amp; ufw allow 80 &amp;amp;&amp;amp; ufw allow 443 &amp;amp;&amp;amp; ufw enable&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never expose port 5678 directly.&lt;/strong&gt; Let Caddy terminate TLS; keep n8n internal (as configured above).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use strong, unique secrets&lt;/strong&gt; for the database password and encryption key.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep the server updated:&lt;/strong&gt; &lt;code&gt;apt update &amp;amp;&amp;amp; apt upgrade -y&lt;/code&gt; periodically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Restrict SSH&lt;/strong&gt; to key-based auth and disable root password login.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common errors and fixes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;"This site can't provide a secure connection" / no HTTPS.&lt;/strong&gt;&lt;br&gt;
DNS isn't pointing at your server yet, or port 80/443 is blocked. Confirm the A record resolves (&lt;code&gt;dig n8n.yourdomain.com&lt;/code&gt;) and that your firewall/cloud security group allows 80 and 443. Caddy needs port 80 reachable to issue the certificate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Webhooks return the wrong URL or don't fire.&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;WEBHOOK_URL&lt;/code&gt; and &lt;code&gt;N8N_HOST&lt;/code&gt; must match your public HTTPS domain exactly. If they still show &lt;code&gt;localhost&lt;/code&gt; or an IP, you edited the values but didn't recreate the container — run &lt;code&gt;docker compose up -d&lt;/code&gt; again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Command 'code' is not allowed" or Code node fails.&lt;/strong&gt;&lt;br&gt;
On n8n 1.x, enable task runners with &lt;code&gt;N8N_RUNNERS_ENABLED=true&lt;/code&gt; (already in the Compose file above) and restart. On 2.0 and later, task runners are enabled by default and that variable is deprecated — if the error persists there, check the container logs instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Credentials show as "unable to decrypt."&lt;/strong&gt;&lt;br&gt;
The &lt;code&gt;N8N_ENCRYPTION_KEY&lt;/code&gt; changed between runs. It must stay identical to the one used when the credentials were saved. Restore the original key from your backup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Containers keep restarting.&lt;/strong&gt;&lt;br&gt;
Check logs to see which one: &lt;code&gt;docker compose logs n8n --tail=50&lt;/code&gt; or &lt;code&gt;docker compose logs postgres --tail=50&lt;/code&gt;. The most common cause is a Postgres password mismatch after editing &lt;code&gt;.env&lt;/code&gt; without recreating the database volume.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schedule/Cron node fires at the wrong time.&lt;/strong&gt;&lt;br&gt;
Set &lt;code&gt;GENERIC_TIMEZONE&lt;/code&gt; to your IANA timezone (e.g. &lt;code&gt;Europe/London&lt;/code&gt;) and restart.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is self-hosting n8n free?&lt;/strong&gt;&lt;br&gt;
The n8n Community Edition is free to self-host, permanently and with no execution limit. Your only cost is the server (~$5/month) and your domain. One caveat worth knowing before you build a business on it: n8n is source-available, not open source, and its &lt;a href="https://docs.n8n.io/privacy-and-security/sustainable-use-license/" rel="noopener noreferrer"&gt;Sustainable Use License&lt;/a&gt; restricts hosting n8n for paying users. &lt;a href="https://noderecipe.com/is-n8n-free" rel="noopener noreferrer"&gt;What's free and what isn't&lt;/a&gt; breaks that down, including what it means for client work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need a domain?&lt;/strong&gt;&lt;br&gt;
For a real setup, yes. Webhooks, OAuth logins, and HTTPS all depend on a proper domain. You can test on a raw IP, but production needs a domain. Google's OAuth flow makes this concrete: the redirect URI is registered against one fixed address, so &lt;a href="https://noderecipe.com/n8n-google-sheets" rel="noopener noreferrer"&gt;connecting a service like Google Sheets&lt;/a&gt; means re-registering it every time your instance moves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SQLite or Postgres?&lt;/strong&gt;&lt;br&gt;
n8n defaults to SQLite, which is fine for testing. For anything you rely on, use Postgres (as in this guide) — it handles concurrent executions far more reliably.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How much RAM do I need?&lt;/strong&gt;&lt;br&gt;
1 GB runs a light instance; 2 GB is comfortable for regular use with several active workflows. Heavy AI or data workflows benefit from more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I move from n8n Cloud to self-hosted?&lt;/strong&gt;&lt;br&gt;
Export your workflows as JSON from Cloud and import them into your self-hosted instance, then re-enter credentials. Your workflows are portable JSON either way.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Ready to build?&lt;/strong&gt; The &lt;code&gt;docker-compose.yml&lt;/code&gt; and Caddyfile above are ready to copy as they stand. Once the instance is up, our &lt;a href="https://noderecipe.com/templates" rel="noopener noreferrer"&gt;templates page&lt;/a&gt; has eight importable workflows to run on it — error handling, monitoring, and AI starters. New to containers, or want the details on volumes, image tags, and safe updates? Start with the &lt;a href="https://noderecipe.com/n8n-docker-setup" rel="noopener noreferrer"&gt;n8n Docker setup guide&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>automation</category>
      <category>selfhosted</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
