{"id":16403,"date":"2026-07-16T11:15:39","date_gmt":"2026-07-16T11:15:39","guid":{"rendered":"https:\/\/hostnoc-revamp.branex.org\/blog\/?p=16403"},"modified":"2026-07-16T13:32:21","modified_gmt":"2026-07-16T13:32:21","slug":"how-to-deploy-n8n-on-linux","status":"publish","type":"post","link":"https:\/\/hostnoc-revamp.branex.org\/blog\/how-to-deploy-n8n-on-linux\/","title":{"rendered":"How to Deploy n8n on Linux: A Complete Beginner&#8217;s Guide"},"content":{"rendered":"<h2><b>Why this guide is for you&#8230;\u00a0<\/b><\/h2>\n<p>You&#8217;ve decided you want to run<a href=\"https:\/\/n8n.io\/\" target=\"_blank\" rel=\"nofollow noopener\"> n8n<\/a> \u2014 the workflow automation tool \u2014 on your own Linux server instead of paying for n8n Cloud. Maybe you found a tutorial that assumed you already know what a &#8220;reverse proxy&#8221; is, or one that skipped straight past the parts that actually broke on your machine. This guide doesn&#8217;t do that.<\/p>\n<p>You don&#8217;t need to be a system administrator to follow this. You do need to be comfortable typing commands into a terminal and copy-pasting carefully. Everywhere a decision has more than one right answer, this guide tells you <i>why<\/i> it recommends what it recommends, so you&#8217;re not just following steps blindly.<\/p>\n<p>By the end, you&#8217;ll have:<\/p>\n<ul>\n<li aria-level=\"1\"><a href=\"https:\/\/hostnoc-revamp.branex.org\/blog\/how-to-install-n8n-on-vps\/\" target=\"_blank\" rel=\"noopener\">n8n running in VPS<\/a> or Docker or a Linux VPS<\/li>\n<li aria-level=\"1\">Your own domain pointing at it, with a free, auto-renewing SSL certificate (so it&#8217;s https:\/\/n8n.yourdomain.com, not a scary unencrypted address)<\/li>\n<li aria-level=\"1\">A PostgreSQL database backing it (more on why this matters for production)<\/li>\n<li aria-level=\"1\">A basic understanding of how to update it, back it up, and fix the two or three things that most commonly go wrong<\/li>\n<\/ul>\n<h2><b>What you&#8217;ll need before starting<\/b><\/h2>\n<table>\n<tbody>\n<tr>\n<td><b>Requirement<\/b><\/td>\n<td><b>Notes<\/b><\/td>\n<\/tr>\n<tr>\n<td>A cloud VPS<\/td>\n<td>Any provider works \u2014 DigitalOcean, Hetzner, Linode, Contabo, AWS Lightsail, etc. 1 vCPU \/ 2GB RAM is enough to start.<\/td>\n<\/tr>\n<tr>\n<td>Ubuntu 24.04 LTS (or similar)<\/td>\n<td>This guide uses Ubuntu commands. Debian is nearly identical.<\/td>\n<\/tr>\n<tr>\n<td>A domain name<\/td>\n<td>You need a domain (or subdomain) you can point at your server, e.g. n8n.yourdomain.com.<\/td>\n<\/tr>\n<tr>\n<td>SSH access to your server<\/td>\n<td>You should be able to run ssh root@your-server-ip from your computer&#8217;s terminal.<\/td>\n<\/tr>\n<tr>\n<td>About 45\u201360 minutes<\/td>\n<td>Slower the first time; most of it is waiting for things to install.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>If you don&#8217;t have a domain yet, get one before you start \u2014 DNS changes can take a little time to spread across the internet, and it&#8217;s the one step you can&#8217;t easily rush at the end.<\/p>\n<h2><b>Prequisites for setting up n8n on Linux<\/b><\/h2>\n<p>Before typing anything, it helps to see the finished picture. Here&#8217;s what you&#8217;re building:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-16432\" src=\"https:\/\/hostnoc-revamp.branex.org\/blog\/wp-content\/uploads\/2026\/07\/8b53be0e-1223-408b-b46f-78f8e4afcdc6.png\" alt=\"n8n on Linux\" width=\"1024\" height=\"566\" title=\"\" srcset=\"https:\/\/hostnoc-revamp.branex.org\/blog\/wp-content\/uploads\/2026\/07\/8b53be0e-1223-408b-b46f-78f8e4afcdc6.png 1024w, https:\/\/hostnoc-revamp.branex.org\/blog\/wp-content\/uploads\/2026\/07\/8b53be0e-1223-408b-b46f-78f8e4afcdc6-768x425.png 768w, https:\/\/hostnoc-revamp.branex.org\/blog\/wp-content\/uploads\/2026\/07\/8b53be0e-1223-408b-b46f-78f8e4afcdc6-260x144.png 260w, https:\/\/hostnoc-revamp.branex.org\/blog\/wp-content\/uploads\/2026\/07\/8b53be0e-1223-408b-b46f-78f8e4afcdc6-50x28.png 50w, https:\/\/hostnoc-revamp.branex.org\/blog\/wp-content\/uploads\/2026\/07\/8b53be0e-1223-408b-b46f-78f8e4afcdc6-136x75.png 136w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>Four moving parts, each with a specific job:<\/p>\n<ol>\n<li aria-level=\"1\"><b>Nginx<\/b> sits at the &#8220;front door&#8221; of your server. It&#8217;s the only thing listening on the public internet (ports 80\/443). It receives requests and quietly hands them off to n8n.<\/li>\n<li aria-level=\"1\"><b>n8n itself<\/b> runs inside a Docker container, only reachable from inside the server \u2014 not directly from the internet. This is a deliberate security choice, explained below.<\/li>\n<li aria-level=\"1\"><b>PostgreSQL<\/b> is n8n&#8217;s database \u2014 where your workflows, credentials, and execution history live.<\/li>\n<li aria-level=\"1\"><a href=\"https:\/\/certbot.eff.org\/\" target=\"_blank\" rel=\"noopener nofollow\"><b>Certbot<\/b><\/a> is a small tool that gets you a free SSL certificate from Let&#8217;s Encrypt and quietly renews it every 90 days so you never have to think about it again.<\/li>\n<\/ol>\n<p>Why not just expose n8n directly on port 5678 and skip Nginx entirely? You <i>can<\/i> \u2014 n8n works fine that way for quick local testing. But for a real, public-facing deployment, putting Nginx in front gets you HTTPS (browsers refuse to trust automation tools without it, and some services won&#8217;t send webhooks to an insecure endpoint), a stable port 443 instead of an unusual one, and a place to add protections later (rate limiting, IP allowlists) without touching n8n&#8217;s own configuration.<\/p>\n<h2><b>Step 1: Connect to your server and do basic setup<\/b><\/h2>\n<p>SSH into your fresh server:<\/p>\n<pre><code>ssh root@YOUR_SERVER_IP<\/code><\/pre>\n<p>Update the system so you&#8217;re building on current packages, not outdated ones:<\/p>\n<pre><code>sudo apt update &amp;&amp; sudo apt upgrade -y<\/code><\/pre>\n<p>It&#8217;s good practice to avoid working as root day-to-day. Create a regular user with admin rights:<\/p>\n<pre><code>adduser deploy\r\nusermod -aG sudo deploy\r\n<\/code><\/pre>\n<p>Follow the prompts to set a password, then switch to that user for the rest of this guide:<\/p>\n<pre><code>su - deploy<\/code><\/pre>\n<p><b>Why this matters:<\/b> if a workflow or a compromised dependency ever executes something unexpected, running as a non-root user limits the damage it can do to the system. It&#8217;s a five-minute step that&#8217;s genuinely worth doing.<\/p>\n<h2><b>Step 2: Point your domain at the server<\/b><\/h2>\n<p>Log into wherever you manage your domain&#8217;s DNS (your registrar, or Cloudflare, etc.) and create an <b>A record<\/b>:<\/p>\n<table>\n<tbody>\n<tr>\n<td><b>Type<\/b><\/td>\n<td><b>Name<\/b><\/td>\n<td><b>Value<\/b><\/td>\n<\/tr>\n<tr>\n<td>A<\/td>\n<td>n8n (for n8n.yourdomain.com)<\/td>\n<td>Your server&#8217;s public IP address<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Do this now, even though you won&#8217;t need it for a few more steps \u2014 DNS changes can take anywhere from a few minutes to a few hours to fully propagate. Starting the clock early means it&#8217;s usually ready by the time you get to the SSL certificate step.<\/p>\n<p>You can check whether it&#8217;s propagated with:<\/p>\n<pre><code>nslookup n8n.yourdomain.com<\/code><\/pre>\n<p>If it returns your server&#8217;s IP, you&#8217;re good.<\/p>\n<h2><b>Step 3: Set up a basic firewall<\/b><\/h2>\n<p>Your VPS is publicly reachable the moment it&#8217;s created, which means it&#8217;s also being scanned by bots within minutes. A firewall that only allows the traffic you actually need is cheap insurance:<\/p>\n<pre><code>sudo ufw allow OpenSSH\r\nsudo ufw allow 'Nginx Full'\r\nsudo ufw enable<\/code><\/pre>\n<p>Nginx Full opens both port 80 (HTTP, needed briefly for the certificate step) and port 443 (HTTPS). Notice that port 5678 \u2014 n8n&#8217;s own port \u2014 is <i>not<\/i> on this list. That&#8217;s intentional: nothing outside the server needs to reach it directly, since Nginx will forward traffic to it internally.<\/p>\n<p>Check it&#8217;s active:<\/p>\n<pre><code>sudo ufw status<\/code><\/pre>\n<h2><b>Step 4: Install Docker and Docker Compose<\/b><\/h2>\n<p>n8n&#8217;s own documentation recommends Docker for self-hosting, and for good reason: it bundles n8n with the exact Node.js version and dependencies it expects, so you avoid the classic &#8220;works on their machine, not mine&#8221; problem. Docker Compose then lets you describe your entire stack (n8n + database) in one file instead of running a string of manual commands.<\/p>\n<p>Install Docker using the official repository (more reliable long-term than the quick-install scripts you&#8217;ll see elsewhere):<\/p>\n<p>If you don&#8217;t have it yet, follow <b data-path-to-node=\"25,3,1,0\" data-index-in-node=\"74\"><a class=\"ng-star-inserted\" href=\"https:\/\/docs.docker.com\/engine\/install\/\" target=\"_blank\" rel=\"noopener nofollow\" data-hveid=\"0\" data-ved=\"0CAAQ_4QMahgKEwi-xuWEo9eVAxUAAAAAHQAAAAAQhAM\">Docker\u2019s official installation guide<\/a><\/b> for your specific Linux instance.<\/p>\n<pre><code>sudo apt install -y ca-certificates curl gnupg\r\nsudo install -m 0755 -d \/etc\/apt\/keyrings\r\ncurl -fsSL https:\/\/download.docker.com\/linux\/ubuntu\/gpg | sudo gpg --dearmor -o \/etc\/apt\/keyrings\/docker.gpg\r\nsudo chmod a+r \/etc\/apt\/keyrings\/docker.gpg\r\n\r\necho \\\r\n  \"deb [arch=$(dpkg --print-architecture) signed-by=\/etc\/apt\/keyrings\/docker.gpg] https:\/\/download.docker.com\/linux\/ubuntu \\\r\n  $(. \/etc\/os-release &amp;&amp; echo \"$VERSION_CODENAME\") stable\" | \\\r\n  sudo tee \/etc\/apt\/sources.list.d\/docker.list &gt; \/dev\/null\r\n\r\nsudo apt update\r\nsudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin<\/code><\/pre>\n<p>Let your user run Docker without typing sudo every time:<\/p>\n<pre><code>sudo usermod -aG docker $USER\r\nnewgrp docker<\/code><\/pre>\n<p>Confirm it worked:<\/p>\n<pre><code>docker --version\r\ndocker compose version<\/code><\/pre>\n<p>You should see version numbers for both, with no errors.<\/p>\n<h2><b>Step 5: Choose your database \u2014 SQLite vs PostgreSQL<\/b><\/h2>\n<p>n8n needs somewhere to store your workflows, credentials, and execution logs. By default it uses <b>SQLite<\/b> \u2014 a database that&#8217;s just a single file, with zero setup required. For local testing, that&#8217;s genuinely fine.<\/p>\n<p>For a production deployment reachable from the internet, this guide uses <b>PostgreSQL<\/b> instead. Here&#8217;s the actual reasoning, not just &#8220;best practice&#8221;:<\/p>\n<ul>\n<li aria-level=\"1\">SQLite writes to a single file with limited concurrent-write handling. Under real traffic \u2014 several workflows firing at once, webhooks arriving while you&#8217;re editing in the UI \u2014 that can lead to &#8220;database is locked&#8221; errors.<\/li>\n<li aria-level=\"1\">If you ever want to scale n8n beyond one instance (n8n&#8217;s &#8220;queue mode,&#8221; for handling heavier workloads), Postgres is a hard requirement, not an option. Starting with it now avoids a painful migration later.<\/li>\n<li aria-level=\"1\">Backups are more straightforward and standard with Postgres \u2014 you can use conventional tools like pg_dump rather than carefully copying a live SQLite file.<\/li>\n<\/ul>\n<p>If your use case is genuinely light (a personal instance with a handful of workflows), SQLite will work and you can skip the Postgres parts of the compose file below. This guide includes Postgres because you told us this is for real production use.<\/p>\n<h2><b>Step 6: Create your project folder and environment file<\/b><\/h2>\n<p>Create a directory to hold everything:<\/p>\n<pre><code>mkdir ~\/n8n-server &amp;&amp; cd ~\/n8n-server\r\nmkdir local-files\r\n<\/code><\/pre>\n<p>local-files will be shared between n8n and your server&#8217;s filesystem \u2014 useful if you ever use n8n&#8217;s &#8220;Read\/Write Files from Disk&#8221; node.<\/p>\n<p>Now generate an encryption key. n8n uses this to encrypt every credential (API keys, passwords) you store in it:<\/p>\n<pre><code>openssl rand -hex 32<\/code><\/pre>\n<p>Copy the output somewhere safe right now \u2014 <b>write it down outside the server too<\/b> (a password manager is ideal). This is the single most important value in this whole setup: if you lose it, every stored credential becomes permanently unreadable, with no recovery path. This isn&#8217;t a scare tactic \u2014 it&#8217;s how the encryption is designed to work, and it&#8217;s worth the thirty seconds of caution.<\/p>\n<p>Create a .env file to hold your settings and secrets in one place:<\/p>\n<pre><code>nano .env<\/code><\/pre>\n<p>Paste this in, replacing the placeholder values with your own:<\/p>\n<pre><code># Your domain (must match the DNS A record you created in Step 2)\r\nDOMAIN_NAME=n8n.yourdomain.com\r\n\r\n# Timezone \u2014 find yours at https:\/\/en.wikipedia.org\/wiki\/List_of_tz_database_time_zones\r\nTIMEZONE=Etc\/UTC\r\n\r\n# Postgres credentials \u2014 pick your own strong password\r\nPOSTGRES_USER=n8n\r\nPOSTGRES_PASSWORD=change-this-to-a-strong-password\r\nPOSTGRES_DB=n8n\r\n\r\n# The key you generated with `openssl rand -hex 32` above\r\nN8N_ENCRYPTION_KEY=paste-your-generated-key-here<\/code><\/pre>\n<p>Save and exit (in nano: Ctrl+O, Enter, then Ctrl+X).<\/p>\n<p>Lock the file down so only your user can read it \u2014 it contains passwords:<\/p>\n<pre><code>chmod 600 .env<\/code><\/pre>\n<h2><b>Step 7: Write the Docker Compose file<\/b><\/h2>\n<p>This is the file that tells Docker what to run and how the pieces connect. Create it:<\/p>\n<pre><code>nano docker-compose.yml<\/code><\/pre>\n<p>Paste in the following:<\/p>\n<pre><code class=\"language-yaml\">services:\r\n  postgres:\r\n    image: postgres:16\r\n    restart: unless-stopped\r\n    environment:\r\n      - POSTGRES_USER=${POSTGRES_USER}\r\n      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}\r\n      - POSTGRES_DB=${POSTGRES_DB}\r\n    volumes:\r\n      - postgres_data:\/var\/lib\/postgresql\/data\r\n    healthcheck:\r\n      test: [\"CMD-SHELL\", \"pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}\"]\r\n      interval: 5s\r\n      timeout: 5s\r\n      retries: 10\r\n\r\n  n8n:\r\n    image: docker.n8n.io\/n8nio\/n8n\r\n    restart: unless-stopped\r\n    ports:\r\n      - \"127.0.0.1:5678:5678\"\r\n    environment:\r\n      - N8N_HOST=${DOMAIN_NAME}\r\n      - N8N_PROTOCOL=https\r\n      - N8N_PORT=5678\r\n      - WEBHOOK_URL=https:\/\/${DOMAIN_NAME}\/\r\n      - N8N_PROXY_HOPS=1\r\n      - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}\r\n      - GENERIC_TIMEZONE=${TIMEZONE}\r\n      - TZ=${TIMEZONE}\r\n      - DB_TYPE=postgresdb\r\n      - DB_POSTGRESDB_HOST=postgres\r\n      - DB_POSTGRESDB_PORT=5432\r\n      - DB_POSTGRESDB_DATABASE=${POSTGRES_DB}\r\n      - DB_POSTGRESDB_USER=${POSTGRES_USER}\r\n      - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}\r\n    volumes:\r\n      - n8n_data:\/home\/node\/.n8n\r\n      - .\/local-files:\/files\r\n    depends_on:\r\n      postgres:\r\n        condition: service_healthy\r\n\r\nvolumes:\r\n  postgres_data:\r\n  n8n_data:<\/code><\/pre>\n<p>A few lines deserve explanation, because copy-pasting without understanding them is exactly how people get stuck later:<\/p>\n<ul>\n<li aria-level=\"1\"><b>&#8220;127.0.0.1:5678:5678&#8221;<\/b> \u2014 this binds n8n&#8217;s port to localhost only, not to the public internet. Combined with the firewall from Step 3, this means the <i>only<\/i> path into n8n is through Nginx. Someone can&#8217;t bypass your SSL certificate and login screen by hitting your-ip:5678 directly.<\/li>\n<li aria-level=\"1\"><b>WEBHOOK_URL<\/b> \u2014 this is the single most common source of confusion in n8n self-hosting. Without it, n8n guesses its own public address and frequently gets it wrong when running behind a proxy, which means webhook nodes generate URLs containing localhost \u2014 completely unreachable from the outside. Setting it explicitly avoids that entirely.<\/li>\n<li aria-level=\"1\"><b>N8N_PROXY_HOPS=1<\/b> \u2014 tells n8n that requests pass through exactly one reverse proxy (your Nginx) before reaching it, so it correctly reads the visitor&#8217;s real IP and protocol instead of Nginx&#8217;s internal ones.<\/li>\n<li aria-level=\"1\"><b>depends_on: condition: service_healthy<\/b> \u2014 makes sure n8n waits for Postgres to actually be ready to accept connections, not just for the container to have started. This avoids a race condition on first boot.<\/li>\n<li aria-level=\"1\"><b>The <\/b><b>n8n_data<\/b><b> volume<\/b> \u2014 this is where n8n stores things that live outside the database: your encryption key backup, installed community nodes, and binary file data. Losing this volume without a backup means losing that data, so it&#8217;s included in the backup steps later.<\/li>\n<\/ul>\n<h2><b>Step 8: Install and configure Nginx<\/b><\/h2>\n<p>Install Nginx:<\/p>\n<pre><code>sudo apt install -y nginx<\/code><\/pre>\n<p>Create a configuration file for your n8n site:<\/p>\n<pre><code>sudo nano \/etc\/nginx\/sites-available\/n8n<\/code><\/pre>\n<p>Paste this in (replace n8n.yourdomain.com with your actual domain):<\/p>\n<pre><code class=\"language-nginx\">server {\r\n    listen 80;\r\n    server_name n8n.yourdomain.com;\r\n\r\n    client_max_body_size 50M;\r\n\r\n    location \/ {\r\n        proxy_pass http:\/\/127.0.0.1:5678;\r\n        proxy_http_version 1.1;\r\n        proxy_set_header Upgrade $http_upgrade;\r\n        proxy_set_header Connection \"upgrade\";\r\n        proxy_set_header Host $host;\r\n        proxy_set_header X-Real-IP $remote_addr;\r\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\r\n        proxy_set_header X-Forwarded-Proto $scheme;\r\n        proxy_read_timeout 300s;\r\n    }\r\n}<\/code><\/pre>\n<p>Two details worth calling out:<\/p>\n<ul>\n<li aria-level=\"1\"><b>The <\/b><b>Upgrade<\/b><b>\/<\/b><b>Connection &#8220;upgrade&#8221;<\/b><b> headers<\/b> are there for WebSockets. n8n&#8217;s editor uses a WebSocket connection for live updates while you&#8217;re building a workflow \u2014 without these two lines, the interface loads but silently loses real-time features, which is a confusing bug to track down after the fact.<\/li>\n<li aria-level=\"1\"><b>client_max_body_size 50M<\/b> raises Nginx&#8217;s default upload limit, since workflows that handle files or larger payloads can otherwise get rejected before they even reach n8n.<\/li>\n<\/ul>\n<p>Enable the site and check the config for typos:<\/p>\n<pre><code class=\"language-bash\">sudo ln -s \/etc\/nginx\/sites-available\/n8n \/etc\/nginx\/sites-enabled\/\r\nsudo nginx -t<\/code><\/pre>\n<p>If it says syntax is ok and test is successful, reload Nginx:<\/p>\n<pre><code>sudo systemctl reload nginx<\/code><\/pre>\n<h2><b>Step 9: Get a free SSL certificate with Let&#8217;s Encrypt<\/b><\/h2>\n<p>Install Certbot along with its Nginx plugin, which will edit your config automatically:<\/p>\n<pre><code>sudo apt install -y certbot python3-certbot-nginx<\/code><\/pre>\n<p>Request and install the certificate:<\/p>\n<pre><code>sudo certbot --nginx -d n8n.yourdomain.com<\/code><\/pre>\n<p>Certbot will ask for an email (for renewal notices) and whether to redirect HTTP to HTTPS \u2014 say yes. If this step fails with a &#8220;connection refused&#8221; or timeout error, it almost always means your DNS record from Step 2 hasn&#8217;t propagated yet. Wait a while and try again.<\/p>\n<p>Certbot&#8217;s certificates expire every 90 days, but it installs an automatic renewal job for you. You can double-check it&#8217;s set up correctly with a dry run:<\/p>\n<pre><code>sudo certbot renew --dry-run<\/code><\/pre>\n<h2><b>Step 10: Launch n8n<\/b><\/h2>\n<p>Back in your project folder, start everything:<\/p>\n<pre><code>cd ~\/n8n-server\r\ndocker compose up -d<\/code><\/pre>\n<p>The -d runs it in the background. Watch the logs to make sure both containers start cleanly:<\/p>\n<pre><code>docker compose logs -f<\/code><\/pre>\n<p>You should see Postgres report it&#8217;s ready to accept connections, followed by n8n starting up and listening on port 5678. Press Ctrl+C to stop watching the logs (this doesn&#8217;t stop the containers).<\/p>\n<p>Check both containers are actually running:<\/p>\n<pre><code>docker compose ps<\/code><\/pre>\n<p>Both n8n-server-postgres-1 and n8n-server-n8n-1 should show a status of Up (and healthy for Postgres).<\/p>\n<h2><b>Step 11: Open n8n and create your account<\/b><\/h2>\n<p>Visit https:\/\/n8n.yourdomain.com in your browser. You should see a valid padlock icon (thanks to Certbot) and n8n&#8217;s setup screen.<\/p>\n<p>This first screen creates your <b>owner account<\/b> \u2014 n8n&#8217;s built-in login system, not a separate password you configure through environment variables. (Older guides mention a N8N_BASIC_AUTH_ACTIVE variable; that mechanism was removed from current n8n versions in favor of this proper account system, so don&#8217;t be confused if you see it referenced elsewhere.)<\/p>\n<p>Enter your email and a password (at least 8 characters, with one number and one capital letter), and you&#8217;re in. From here you can invite additional users under <b>Settings \u2192 Users<\/b> if others need access.<\/p>\n<h2><b>Step 12: Updating n8n safely<\/b><\/h2>\n<p>n8n ships new releases frequently. To update:<\/p>\n<pre><code>cd ~\/n8n-server\r\ndocker compose pull\r\ndocker compose up -d\r\n<\/code><\/pre>\n<p>This pulls the newest image and recreates only the containers that changed, leaving your data volumes untouched. Before updating across a <i>major<\/i> version (e.g. 1.x to 2.x), skim n8n&#8217;s release notes for breaking changes \u2014 most minor updates are safe to apply without reading anything, but major versions occasionally require a manual step.<\/p>\n<h2><b>Backing up your data<\/b><\/h2>\n<p>Two things need backing up regularly: the Postgres database, and the n8n_data volume (which holds your encryption key backup and any binary file data).<\/p>\n<p>A simple backup script:<\/p>\n<pre><code class=\"language-bash\">#!\/bin\/bash\r\nBACKUP_DIR=~\/n8n-backups\/$(date +%Y-%m-%d)\r\nmkdir -p \"$BACKUP_DIR\"\r\n\r\ncd ~\/n8n-server\r\n\r\n# Dump the Postgres database\r\ndocker compose exec -T postgres pg_dump -U n8n n8n &gt; \"$BACKUP_DIR\/n8n-db.sql\"\r\n\r\n# Back up the n8n_data volume\r\ndocker run --rm -v n8n-server_n8n_data:\/data -v \"$BACKUP_DIR\":\/backup alpine \\\r\n  tar czf \/backup\/n8n_data.tar.gz -C \/data .<\/code><\/pre>\n<p>Save this as backup.sh, make it executable with chmod +x backup.sh, and consider scheduling it with cron to run daily. Also keep a copy of your .env file somewhere safe outside the server \u2014 it holds your encryption key, and without it a restored database is useless.<\/p>\n<h2><b>Troubleshooting common issues<\/b><\/h2>\n<table>\n<tbody>\n<tr>\n<td><b>Symptom<\/b><\/td>\n<td><b>Likely cause<\/b><\/td>\n<td><b>Fix<\/b><\/td>\n<\/tr>\n<tr>\n<td>Nginx shows &#8220;502 Bad Gateway&#8221;<\/td>\n<td>n8n container isn&#8217;t running, or Nginx is pointing at the wrong port<\/td>\n<td>Run docker compose ps to confirm n8n is Up; check docker compose logs n8n for startup errors<\/td>\n<\/tr>\n<tr>\n<td>Webhook URLs contain localhost instead of your domain<\/td>\n<td>WEBHOOK_URL wasn&#8217;t set, or you changed it without restarting<\/td>\n<td>Confirm it&#8217;s in your .env\/compose file, then docker compose up -d again<\/td>\n<\/tr>\n<tr>\n<td>Browser shows a certificate warning<\/td>\n<td>DNS hadn&#8217;t propagated when Certbot ran, or you&#8217;re visiting via IP instead of domain<\/td>\n<td>Re-run sudo certbot &#8211;nginx -d n8n.yourdomain.com once DNS is confirmed with nslookup<\/td>\n<\/tr>\n<tr>\n<td>&#8220;Too many redirects&#8221; in the browser<\/td>\n<td>Mismatch between N8N_PROTOCOL=https and how Nginx is actually forwarding traffic<\/td>\n<td>Double-check the X-Forwarded-Proto header is set in your Nginx config, and that N8N_PROXY_HOPS=1 is set<\/td>\n<\/tr>\n<tr>\n<td>docker: permission denied<\/td>\n<td>Your user isn&#8217;t in the docker group, or the group membership hasn&#8217;t taken effect yet<\/td>\n<td>Re-run sudo usermod -aG docker $USER then log out and back in<\/td>\n<\/tr>\n<tr>\n<td>Editor loads but feels &#8220;stuck&#8221; \/ no live updates<\/td>\n<td>WebSocket headers missing from Nginx config<\/td>\n<td>Confirm the Upgrade and Connection &#8220;upgrade&#8221; lines are present, then sudo systemctl reload nginx<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2><b>What&#8217;s the next step?\u00a0<\/b><\/h2>\n<p>You now have a working, secured, production-oriented n8n deployment: HTTPS by default, a proper database, an isolated container that isn&#8217;t directly exposed to the internet, and a repeatable way to update and back it up.<\/p>\n<p>A few sensible next steps once you&#8217;re comfortable:<\/p>\n<ul>\n<li aria-level=\"1\"><b>Set up email (SMTP)<\/b> in n8n&#8217;s settings so you can invite teammates and use password resets, rather than managing every account by hand.<\/li>\n<li aria-level=\"1\"><b>Enable two-factor authentication<\/b> on your owner account under Settings \u2192 Personal.<\/li>\n<li aria-level=\"1\"><b>If you ever outgrow a single instance<\/b> \u2014 heavy workflow volume, many concurrent webhooks \u2014 look into n8n&#8217;s &#8220;queue mode,&#8221; which distributes execution across worker containers using Redis. It&#8217;s a natural next step from this Postgres-backed setup, not a rebuild.<\/li>\n<\/ul>\n<p>That&#8217;s the full path from an empty VPS to a secured, self-hosted n8n instance running on your own domain.<\/p>\n<p>While self-hosting is powerful, maintaining servers, running security updates, and scaling databases can take up valuable development time.<\/p>\n<p>Save yourself the DevOps overhead by scaling your workflows on <b data-path-to-node=\"9,6,1,0\" data-index-in-node=\"217\"><a class=\"ng-star-inserted\" href=\"https:\/\/hostnoc-revamp.branex.org\/blog\/n8n-hosting\/\" target=\"_blank\" rel=\"noopener\" data-hveid=\"0\" data-ved=\"0CAAQ_4QMahgKEwi-xuWEo9eVAxUAAAAAHQAAAAAQgAM\">HostNoc&#8217;s n8n AI hosting solutions<\/a><\/b>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Why this guide is for you&#8230;\u00a0 You&#8217;ve decided you want to run n8n \u2014 the workflow automation tool \u2014 on your own Linux server instead of<span class=\"excerpt-hellip\"> [\u2026]<\/span><\/p>\n","protected":false},"author":1,"featured_media":16404,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"content-type":"","footnotes":""},"categories":[330],"tags":[],"class_list":["post-16403","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ai-hosting"],"acf":[],"_links":{"self":[{"href":"https:\/\/hostnoc-revamp.branex.org\/blog\/wp-json\/wp\/v2\/posts\/16403","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/hostnoc-revamp.branex.org\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/hostnoc-revamp.branex.org\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/hostnoc-revamp.branex.org\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/hostnoc-revamp.branex.org\/blog\/wp-json\/wp\/v2\/comments?post=16403"}],"version-history":[{"count":12,"href":"https:\/\/hostnoc-revamp.branex.org\/blog\/wp-json\/wp\/v2\/posts\/16403\/revisions"}],"predecessor-version":[{"id":16444,"href":"https:\/\/hostnoc-revamp.branex.org\/blog\/wp-json\/wp\/v2\/posts\/16403\/revisions\/16444"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/hostnoc-revamp.branex.org\/blog\/wp-json\/wp\/v2\/media\/16404"}],"wp:attachment":[{"href":"https:\/\/hostnoc-revamp.branex.org\/blog\/wp-json\/wp\/v2\/media?parent=16403"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/hostnoc-revamp.branex.org\/blog\/wp-json\/wp\/v2\/categories?post=16403"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/hostnoc-revamp.branex.org\/blog\/wp-json\/wp\/v2\/tags?post=16403"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}