Automation

Self-host n8n on a VPS: no-code automation with full control

Self-hosting n8n on a VPS: a terminal card with the Docker Compose file, an Nginx reverse proxy block with WebSocket support, the public webhook URL variable, automatic execution-history pruning and a first Telegram alert workflow on an hourly-billed cloud server

When you self-host n8n on a VPS, you hand your repetitive work to a machine: no-code automation, but this time on your own server, with data that never leaves your control. This guide starts from zero — sizing the server, the Docker Compose file, the domain and SSL, the webhook trap behind a reverse proxy, a first real workflow, and what n8n quietly does to your server's disk if you leave it unattended.

What is n8n, and why should you self-host n8n?

If you are wondering what n8n is, the shortest answer is: a self-hosted Zapier alternative. It is a tool for building workflows — "when this happens, do these things" — by dragging nodes onto a visual canvas and wiring them together. n8n's official integrations list now has more than two thousand entries (community-built nodes included): Telegram, Gmail, Google Sheets, databases, webhooks, language models, and any other API you like through the HTTP Request node.

But why install n8n on your own server instead of using the ready-made cloud service? Three serious reasons:

  • Data ownership: with the cloud service, your customers' data and your API tokens pass through a third-party company's servers. In the self-hosted edition everything stays on your own disk, and the key that encrypts your credentials stays in your hands too.
  • No tiered pricing: the cloud service limits the number of executions, and a workflow that runs every five minutes adds up to several thousand executions a month; on your own server, ten executions and ten thousand cost the same.
  • Access to internal services: an external cloud tool cannot reach a database that is only accessible from inside your network; n8n on your own server can reach that database and any public API as well.

One honest note for Persian-speaking teams: the n8n interface isn't available in Persian and Persian-language learning material is scarce — but the interface is visual enough that language stops being an obstacle after your first workflow.

The n8n fair-code license: why calling it "open source" isn't accurate

Many tutorials, plenty of Persian-language ones among them, describe n8n as "open source", and that isn't accurate. The code is available on GitHub, but it is released under the Sustainable Use License, which n8n itself presents as part of its fair-code model. The difference is practical, not philosophical: the license allows you to use and modify the software only for "your own internal business purposes" or for personal, non-commercial use; it allows redistribution only free of charge and for non-commercial purposes; and it forbids removing copyright notices. So running n8n on your own company's server and building workflows for a client is allowed; hosting n8n and selling access to it, or offering it under your own brand, is not. If your business model is a "shared automation panel", read the license text before you invest.

The second point concerns editions. The free edition you host yourself is called the Community edition, and it has almost every feature; what it lacks is specific and documented: SSO, version control with Git, projects and access roles, custom variables, log streaming and multi-main mode. On the other hand, the edition comparison page states explicitly that queue mode is included in the free edition. And if you register your email address, you get a free key that unlocks folders for organizing workflows and debugging in the editor.

n8n server requirements: how much RAM and CPU do you need?

The good news: n8n's core is lightweight. The official documentation gives a range of 320 MB to 2 GB of RAM and 512 MB to 4 GB of database disk for a single instance, and says n8n is not CPU-intensive; an idle instance uses about 100 MB of RAM. What really drives memory use is the amount of data each execution holds in memory.

ScenarioSuggested RAMCPUDatabaseNote
Testing and a few personal workflows1 GB1 coreSQLiteManual executions use more memory, because they copy the data for the interface
Team use, always-on execution2 GB1 to 2 coresSQLite or PostgreSQLThe usual starting point for serious work
Data-heavy workflows or large files4 GB and up2 coresPostgreSQLSplit the data into batches with Loop Over Items
Full stack with the n8n AI assistant4 GB (minimum)2 coresPostgreSQLThe sandbox runs with Docker-in-Docker

An NVMe disk matters more here than it might seem: both SQLite and PostgreSQL write a record for every execution, and write latency lands directly on the duration of each run. Mehran Host cloud servers are KVM machines with NVMe disks in a datacenter inside Iran.

Install n8n on a VPS with Docker Compose

The cleanest way to install n8n on a server is Docker: no Node.js dependencies to manage and no upgrade headaches. If Docker isn't installed yet, start with our guide to install Docker on a VPS — it explains that on a server in Iran both the installation step and the first docker pull fail with a 403 error, because Docker's services refuse Iranian IP addresses, and that each of the two has a different fix. Now create the compose.yaml file:

services:
  n8n:
    image: docker.n8n.io/n8nio/n8n
    restart: unless-stopped
    ports:
      - "127.0.0.1:5678:5678"
    environment:
      - N8N_HOST=n8n.example.com
      - N8N_PROTOCOL=https
      - N8N_WEBHOOK_URL=https://n8n.example.com/
      - N8N_PROXY_HOPS=1
      - N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
      - GENERIC_TIMEZONE=Asia/Tehran
      - TZ=Asia/Tehran
    volumes:
      - n8n_data:/home/node/.n8n

volumes:
  n8n_data:

Bring it up:

docker compose up -d
docker compose logs -f n8n

n8n ready on ::, port 5678
Version: 2.38.1
Editor is now accessible via:
https://n8n.example.com

This file makes several decisions. Port 5678 — n8n's default — is bound only to 127.0.0.1, so it cannot be reached from the internet. The data — workflows, credentials, execution history and the encryption key — lives in a volume named n8n_data at the path /home/node/.n8n, so it survives container updates. And there are two time zone variables: TZ sets the container's system clock, and GENERIC_TIMEZONE is the time zone that nodes such as Schedule Trigger work with (the example uses Asia/Tehran; set both to your own time zone).

The one-line route, and news you should know

If you would rather not write the file yourself, n8n has an official install script that checks for Docker, creates the configuration files and brings the service up:

curl -fsSL https://get.n8n.io | sh

This method brings up a more complete stack (including the AI assistant's sandbox); for a server you control yourself, the compose file is more transparent. The bigger news: installing with npm is being retired — the official documentation has announced that from version 3, n8n will be distributed only through Docker.

First login, the owner account and deprecated variables

The first time you open the web interface, n8n asks you to create an owner account; user management is built in, and the old N8N_BASIC_AUTH_* variables were removed in version 1. N8N_RUNNERS_ENABLED has a similar story: on the 1.x branch you had to set it to true, but since version 2 it is deprecated. Choose a strong password for the owner account; anyone who logs in has access to all of your tokens and workflows.

⚠️ If you try to log in at http://IP:5678 before you set up the domain and SSL, the page loads but the login never completes and you are silently sent back to the login page. It isn't a bug: N8N_SECURE_COOKIE defaults to true, and the session cookie is only sent over HTTPS. The right fix is to finish the domain and certificate, not to switch this protection off.
💡 Write down the encryption key today. On first start, n8n generates a random key, stores it in the .n8n folder and encrypts every credential with it. If that folder is lost, a new key is generated and the old credentials can no longer be decrypted. To keep the key in your own hands, define N8N_ENCRYPTION_KEY in the compose file from the start.

Connect a domain: Nginx reverse proxy, WebSockets and an SSL certificate

Point a subdomain such as n8n.example.com at the server's IP address with an A record — if you are not comfortable with records, see our guide to DNS record types (the minimum TTL in the Mehran Host DNS panel is 60 seconds). Then define a reverse proxy in Nginx:

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 80;
    server_name n8n.example.com;

    location / {
        proxy_pass http://127.0.0.1:5678;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 300s;
    }
}

The Upgrade and Connection lines are vital: the n8n interface uses WebSocket, and the Nginx WebSocket documentation explains why these two headers are "hop-by-hop" and must be passed on explicitly; without them the page loads, but execution status doesn't update live. The map block is there for a reason too: a fixed value of "upgrade" would also be sent on ordinary requests, whereas this mapping sends it only when the browser has actually asked for an upgrade.

The proxy_read_timeout value isn't arbitrary either: Nginx's default is 60 seconds, and if the backend sends nothing within that time, the connection is closed. And one recent change: since version 1.29.7, released in March 2026, the default for proxy_http_version is 1.1 itself; on older versions you also need to add the line proxy_http_version 1.1;, or the WebSocket connection won't be established.

Get a free certificate:

certbot --nginx -d n8n.example.com

Issuance and automatic renewal are covered in our guide to install free SSL with Certbot. Once SSL is in place, tidy up the firewall — only ports 22, 80 and 443 should be open; the full checklist is in our Linux server hardening checklist. Port 5678 must not be on that list: the only way in from outside is through Nginx.

Why do n8n webhooks break behind a reverse proxy?

This is the most common failure for newcomers: the interface opens, but the webhook URL that n8n displays is http://localhost:5678/webhook/… and the external service never sends anything. n8n builds this URL by combining N8N_PROTOCOL, N8N_HOST and N8N_PORT, whose defaults are http, localhost and 5678: behind a proxy, n8n listens on 5678, but the world sees it on 443.

The official documentation on webhooks behind a reverse proxy lists three requirements, and all three are already handled in the compose file and Nginx block above: setting the URL manually with N8N_WEBHOOK_URL, setting N8N_PROXY_HOPS to the number of proxies on the path (1 here; the default is zero), and passing the X-Forwarded-For, X-Forwarded-Host and X-Forwarded-Proto headers.

There is a naming trap here as well: the old WEBHOOK_URL variable has been deprecated since version 2.35 and replaced by N8N_WEBHOOK_URL. Current versions still accept the old name but write a deprecation warning to the log. And if there is another layer in front of Nginx — for example a CDN edge node, which in the panel is switched on with one toggle next to the A record — the hop count goes up by one, and the webhook path must not get caught in any redirect rule.

First workflow: a Telegram alert when your site goes down

The most useful example for anyone who runs a server: call the site every five minutes and, if it doesn't answer, send me an alert on Telegram.

1. The bot and the chat ID

In Telegram, send a message to BotFather, create a bot with /newbot and copy the token. Then send your bot a message and take the chat ID from the output of the getUpdates method; according to the Bot API documentation, every call is made over HTTPS in this format:

curl -s "https://api.telegram.org/bot<TOKEN>/getUpdates"

{"ok":true,"result":[{"update_id":874250,
  "message":{"message_id":3,"from":{"id":123456789,"is_bot":false},
  "chat":{"id":123456789,"type":"private"},"text":"/start"}}]}

The chat.id number is what the Telegram node needs. Settle one network fact before you deploy, too: the bot has to reach api.telegram.org from the server itself, and that path is not the same on every network. In Iran this is anything but theoretical: Telegram has been blocked in the country since 2018, and from a server in an Iranian datacenter the API may well not answer at all. So don't guess; test:

curl -sS -m 10 -o /dev/null -w "%{http_code}\n" https://api.telegram.org
200

2. The monitoring workflow

In n8n, create a workflow with two nodes: first a Schedule Trigger set to run every five minutes (because we set GENERIC_TIMEZONE to Tehran, the times are local), and second an HTTP Request node using the GET method on your site's URL. That's it; if the site is up, the execution finishes green.

3. The error workflow that sends the message

Now we use n8n's official pattern for alerts: create a separate workflow whose first node is Error Trigger and whose second node is Telegram → Send Message, and name it Error Handler. Then, in the monitoring workflow's settings, select it as the Error workflow. From then on, every time a monitoring execution fails, the Error Trigger node receives this data:

[{
  "execution": {
    "id": "231",
    "url": "https://n8n.example.com/execution/231",
    "error": { "message": "Example Error Message", "stack": "Stacktrace" },
    "lastNodeExecuted": "Node With Error",
    "mode": "manual"
  },
  "workflow": { "id": "1", "name": "Example Workflow" }
}]

Reference these fields in the text of the Telegram message, so that the alert you receive includes the workflow name, the error text and a direct link to the execution. The advantage of this pattern is that the same error workflow can be selected for dozens of other workflows as well.

⚠️ If a tutorial tells you to check the server's disk usage with the Execute Command node and the df -h command, it has left out two things. First, this node has been disabled by default since version 2, and to bring it back you have to set NODES_EXCLUDE to "[]". Second, and more important: when n8n runs in Docker, the command runs inside the container, not on the host — so the number you see is the container's disk, not your server's. To monitor the server itself, Linux server monitoring commands are the right tool.

This same skeleton (trigger → processing → action) is the backbone of most workflows: a website form to Google Sheets, price monitoring, new-order notifications.

How much disk does execution history use, and when should you move to PostgreSQL?

n8n stores data for every execution, and that data piles up on disk. Contrary to what older tutorials say, automatic pruning is on by default: EXECUTIONS_DATA_PRUNE defaults to true, EXECUTIONS_DATA_MAX_AGE to 336 hours (two weeks) and EXECUTIONS_DATA_PRUNE_MAX_COUNT to 10,000 executions. If your disk is small or you run high-frequency workflows, lower these two numbers:

      - EXECUTIONS_DATA_MAX_AGE=168
      - EXECUTIONS_DATA_PRUNE_MAX_COUNT=2000

Two more settings are worth knowing, and both default to -1, meaning "unlimited": EXECUTIONS_TIMEOUT, without which a stuck workflow holds on to resources forever, and N8N_CONCURRENCY_PRODUCTION_LIMIT, which on a small server, once you set a limit, prevents the kind of concurrency that runs the server out of RAM.

As for the database: the default is SQLite, which is entirely sufficient for personal use, especially on NVMe. Move to PostgreSQL when several users work at the same time or workflows run around the clock. A migration warning: after you change DB_TYPE, n8n starts from scratch on the new database, and the SQLite data is not moved over automatically — this route is for a fresh install, not an in-place upgrade. The next step in scaling is queue mode, which requires Redis.

Back up and update n8n without losing your credentials

Three things make restoring n8n painful if you lose them: the database file, the encryption key and the compose file itself. A sound backup means stopping the container so the SQLite file isn't captured mid-write, archiving the volume and bringing the container back up. One trap: Compose prefixes the volume name with the project folder's name, so find the real name first, or you will end up backing up a freshly created, empty volume:

docker compose down
docker volume ls --format '{{.Name}}' | grep n8n_data
n8n_n8n_data

docker run --rm -v n8n_n8n_data:/data -v "$PWD":/backup alpine \
  tar czf /backup/n8n-$(date +%F).tar.gz -C /data .
docker compose up -d

ls -lh n8n-2026-09-08.tar.gz
-rw-r--r-- 1 root root 4.6M Sep  8 09:12 n8n-2026-09-08.tar.gz

Don't keep this archive on the same server; the 3-2-1 rule is explained in our Linux server backup guide. Updating takes three commands, and because the data stays in the volume, the new container sees the same workflows:

docker compose pull
docker compose down
docker compose up -d

Take a backup before every update, and once the container is back up, check the instance's health. n8n has two endpoints for this: /healthz only says that the service is reachable, while /healthz/readiness returns 200 only when the database is also connected and migrated:

curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:5678/healthz/readiness
200

Should you run n8n on a server in Iran or abroad?

That depends on what your workflows talk to, and you need the answer before you buy a server. If n8n is going to work with services inside Iran — a database on another of your servers, an Iranian payment gateway, an SMS panel, your own store's API — a server inside Iran is both faster and more stable. If the workflows connect to services that don't respond to Iranian IP addresses, a server abroad is the right choice; Mehran Host's international servers run in Hetzner datacenters and are paid in Toman from the same wallet, and the full comparison is in our guide on how to buy a VPS outside Iran. Don't decide by guesswork either: test every service your workflows call with the same simple check we ran above for api.telegram.org.

And a real saving: with hourly billing, you can run this experiment on a real server. Create a server, finish installing n8n in half an hour, build workflows for a day or two and, if you don't like it, delete the server. We stress the word delete: under the hourly model, a powered-off server still consumes a percentage of the hourly rate — because its resources and IP address remain reserved for you — and the only thing that stops the meter is deleting the machine. If your wallet balance reaches zero, the service is suspended as well. The full mechanics are in our guide to the hourly billing cloud server.

n8n is up but not working? A troubleshooting table

SymptomLikely causeNext step
The interface opens, but execution status doesn't update liveThe WebSocket headers aren't reaching the backendAdd the Upgrade and Connection lines to the Nginx block
You log in and are silently sent back to the login pageAccess over HTTP, with N8N_SECURE_COOKIE onFinish the domain and SSL, then log in over HTTPS
The webhook URL is shown with localhost:5678N8N_WEBHOOK_URL is not setSet the public URL and configure N8N_PROXY_HOPS
The Execute Command node is missing from the node listDisabled by default since version 2Set NODES_EXCLUDE to "[]" and accept the risk
A JavaScript heap out of memory error in the logOne execution's data is larger than the available RAMSplit the data into batches or add RAM to the server
Credentials stop working after the container is recreatedThe volume was deleted and a new encryption key was generatedRestore the .n8n folder from a backup
A workflow execution stays "running" foreverNo time limit is defined (EXECUTIONS_TIMEOUT is -1)Define a limit in seconds

What to do after you install n8n

That is all it takes to install n8n on a VPS: one compose file, one Nginx block, one SSL certificate and a handful of variables whose differences you now understand. Create an hourly server and switch on your first workflow today — and because n8n is where your tokens live, do the backup and the security checklist on day one, not on a bad day.

Frequently asked questions

What server specs do I need to install n8n on a VPS?

A server with 1 GB of RAM and 1 CPU core is enough to get started and to run personal workflows; the official documentation gives a range of 320 MB to 2 GB of RAM for a single n8n instance, and for team use 2 GB is the safer choice. What actually determines memory use is the amount of data each execution holds in memory, not the number of workflows.

Is n8n open source and free?

Not in the strict sense: n8n's code is publicly available, but its license is the Sustainable Use License, which falls under the fair-code model rather than being a conventional open-source license. Using it for your own internal business purposes, and offering consulting and building workflows for others, is allowed; what you may not do is host n8n and sell access to it, or offer it under your own brand. The Community edition is free and has almost every feature.

Why doesn't my n8n webhook URL respond to external services?

Your n8n webhook URL fails because n8n builds it from three variables, N8N_PROTOCOL, N8N_HOST and N8N_PORT, whose defaults point to a local address and port 5678. Behind a reverse proxy, you have to set the public URL explicitly with the N8N_WEBHOOK_URL variable, set N8N_PROXY_HOPS to the number of proxies on the path, and pass the X-Forwarded-Proto and X-Forwarded-Host headers from the proxy.

If I delete my n8n container, do my workflows and credentials come back?

Yes, as long as the Docker volume survives: workflows and credentials are stored in the /home/node/.n8n folder inside the Docker volume, so deleting and recreating the container causes no problems while that volume is intact. But the credentials are encrypted with a key kept in the same folder; if the volume is lost, n8n generates a new key and the old credentials can no longer be decrypted. So backing up this folder is mandatory, not optional.

Does n8n's execution history fill up the disk?

Not by default, because automatic pruning is switched on: n8n deletes executions older than 336 hours and caps retention at 10,000 executions. For small servers or high-frequency workflows, lower these two numbers with the EXECUTIONS_DATA_MAX_AGE and EXECUTIONS_DATA_PRUNE_MAX_COUNT variables. Not saving data for successful executions is also an option, but it makes troubleshooting harder and is best kept as a last resort.

Related tutorials

Ready for some hands-on practice?

A Mehran Host hourly cloud server is ready in under 60 seconds. Practice as much as you like and pay only for the hours you use. Estimate the cost with the pricing calculator before you sign up.