Deployment guide

Install n8n on VPS with Docker: complete 2026 guide

Deploy on a VPS Cloud →

Automation11 min read

Install n8n on VPS with Docker: complete 2026 guide

n8n is an open source automation orchestrator: it connects your APIs, triggers workflows on events and, when self-hosted, runs without execution limits or cloud subscription. This guide covers the complete installation on VPS with Docker, reverse proxy configuration, critical environment variables, two critical production errors — V8 crash with more than 30 simultaneous workflows and 502 Bad Gateway nginx — and migration from npm mode, unsupported since n8n v3.0.

Why host n8n on your VPS

The cloud version of n8n charges per execution beyond the plan and limits the number of active workflows. On your VPS, the only cost is that of the server, regardless of the number of automations or API calls. You also keep full control over credentials, webhook payloads and execution history — no data transits through a third-party infrastructure.

The concrete benefits of a self-hosted n8n

  • Unlimited executions: no monthly cap, no cost at scale.
  • Credentials and webhook payloads confined to your server, without external transit.
  • Access to community nodes and custom code execution without restriction.
  • Webhooks on your own domain, configurable for each inbound integration.
  • Predictable cost: a fixed VPS price, independent of automation volume.
  • Controlled persistence of workflows and history in volumes you back up.
  • Compatibility with Ollama, Flowise or any other Docker service on the same internal network.

Numbered prerequisites

Before starting, verify your VPS meets the following requirements. For moderate usage, 1 vCPU and 1 GB of RAM are sufficient; plan for 2 vCPU and 2 GB as soon as you run heavy or concurrent workflows, and 4 GB if you connect a dedicated PostgreSQL database or integrate a local AI model. Allow 10 GB of disk minimum for Docker, volumes and logs. Port 5678 must not be exposed directly — n8n only listens on 127.0.0.1:5678 and all requests go through the reverse proxy on port 443. A subdomain (for example n8n.your-domain.com) pointing to the VPS IP is required for HTTPS and inbound webhooks. Docker Engine 24+ and Docker Compose v2 are required.

Step-by-step installation

01

Update the VPS and install Docker

Connect via SSH and update packages: apt update && apt upgrade -y. Then install Docker via the official script: curl -fsSL https://get.docker.com | sh. Verify the installation: docker --version && docker compose version. Create the working directory: mkdir -p /opt/n8n && cd /opt/n8n.

02

Create the docker-compose.yaml file

Create docker-compose.yaml with the n8n service, a named volume for persistence and the essential environment variables. Declare image: n8nio/n8n:latest, restart: unless-stopped, and mount n8n_data:/home/node/.n8n. Expose only on 127.0.0.1:5678:5678 to avoid any direct public exposure of the port.

03

Set critical environment variables

In the service environment section, declare at minimum: N8N_HOST=n8n.your-domain.com, N8N_WEBHOOK_URL=https://n8n.your-domain.com/, N8N_PROXY_HOPS=1 (so n8n accepts X-Forwarded-* headers from the reverse proxy), N8N_BASIC_AUTH_ACTIVE=true, N8N_BASIC_AUTH_USER=<your-login> and N8N_BASIC_AUTH_PASSWORD=<strong-password>. For production use, store these values in an adjacent .env file and reference it via env_file: .env.

04

Start the container

Run: docker compose up -d. Verify the container is running: docker compose ps. Check logs: docker compose logs -f n8n. n8n is ready when the line Editor is now accessible via: http://localhost:5678/ appears in the logs. At this point the interface is only accessible from 127.0.0.1 — this is intentional.

05

Configure the reverse proxy with Caddy (recommended)

Caddy is the simplest reverse proxy for n8n: it handles the Let's Encrypt certificate and renewal without additional configuration. Install Caddy (apt install caddy) then edit /etc/caddy/Caddyfile to add: n8n.your-domain.com { reverse_proxy localhost:5678 }. Reload: systemctl reload caddy. With nginx, add in your server block: proxy_set_header X-Forwarded-Host $host;, proxy_set_header X-Forwarded-Proto $scheme; and proxy_set_header X-Real-IP $remote_addr; — without these headers, n8n rebuilds incorrect webhook URLs.

06

Verify inbound webhooks

In the n8n editor, create a test workflow with a Webhook node. The URL displayed in production mode must be exactly https://n8n.your-domain.com/webhook/<your-path>. Trigger the call from your local machine: curl -X POST https://n8n.your-domain.com/webhook/test -d '{}'. If the displayed URL contains localhost or port 5678, the N8N_WEBHOOK_URL variable is not being picked up — verify the container restarted after adding the variable.

07

Connect PostgreSQL for production

SQLite is fine for testing; in production, prefer PostgreSQL. Add a postgres:15 service to the same docker-compose.yaml, with a dedicated pg_data volume. In the n8n service, add: DB_TYPE=postgresdb, DB_POSTGRESDB_HOST=postgres, DB_POSTGRESDB_DATABASE=n8n, DB_POSTGRESDB_USER=n8n and DB_POSTGRESDB_PASSWORD=<password>. Restart with docker compose up -d; n8n automatically migrates the schema on first startup.

Hardening and queue mode

Three production reflexes: (1) Never expose port 5678 on the public interface — keep the 127.0.0.1:5678 binding. (2) Enable authentication: in v1.x, N8N_BASIC_AUTH_ACTIVE=true; in v1.27+, prefer native authentication via the interface (Settings → Security). (3) For AI or long-running workflows, switch to queue mode with a separate worker instance and a Redis queue: this isolates long executions from the interface and allows horizontal scaling without reconfiguring webhooks.

Fatal V8 crash with more than 30 active workflows

Symptom. The n8n instance crashes with the error FATAL ERROR: invalid-mark-compact are transition in the Docker logs. The container restarts if restart: unless-stopped is configured, but the crash recurs as soon as load passes the same threshold again. No error message in the interface: the container disappears without warning.

Cause. This error is a panic in the V8 engine (the JavaScript engine embedded in Node.js) during a garbage collection cycle. It is triggered when the V8 heap is saturated — typically starting at 30 workflows executing simultaneously on a VPS with less than 2 GB of RAM allocated to the process. Each active workflow maintains an execution context in memory; beyond a certain threshold, the GC attempts a mark-compact transition on an already corrupted heap, causing the fatal crash. The problem has no automatic recovery: n8n has no graceful degradation mechanism at this level.

Immediate fix. Increase the V8 heap limit by adding the following environment variable in the n8n service in your docker-compose.yaml: NODE_OPTIONS=--max-old-space-size=2048. This allocates 2 GB to the V8 heap. Restart the container: docker compose restart n8n. Watch the logs for a few minutes to confirm the absence of crashes.

Structural fix. The container memory limit must match the V8 limit. If your docker-compose.yaml declares mem_limit: 1g and you pass --max-old-space-size=2048, the OOM killer kills the container before V8 can use it. Rule: allocate at least 2 GB of RAM to the VM beyond 30 active workflows, and pass NODE_OPTIONS=--max-old-space-size=1536 (leaving 512 MB for the rest of the system). For installations with more than 50 workflows, prefer queue mode (separate Redis worker instance) which decouples executions from the main process and distributes memory load. Source: community.n8n.io thread #308425.

Persistent 502 Bad Gateway behind nginx

Symptom. Short requests succeed but some workflows return 502 Bad Gateway from nginx — particularly workflows that call slow external APIs, process large data volumes, or chain many nodes. The 502 occurs exactly 60 seconds after execution starts, even though n8n continues working in the background.

Cause. nginx waits by default 60 seconds for a response from the backend before closing the connection (proxy_read_timeout = 60 s). For n8n, the backend is the process executing the workflow: if execution takes more than 60 seconds, nginx cuts the connection and returns a 502. n8n continues execution in the background (the workflow finishes server-side), but the client never receives the response — making it appear as a failure even though the result was produced. The behavior is worsened with synchronous webhooks that wait for the workflow to finish before responding (Respond to Webhook node at the end of the flow).

Fix. Add these two directives in the location block of your nginx configuration proxying to n8n:

proxy_read_timeout 300;
proxy_send_timeout 300;

The value of 300 seconds (5 minutes) covers the vast majority of long workflows. For exceptionally long workflows (massive data import, multi-step AI chains), raise it to 600 s. Reload nginx: nginx -t && systemctl reload nginx. Note that this value does not replace the connection timeout (proxy_connect_timeout, keep it at 60 s — it only applies to the initial TCP connection establishment).

Verification. Run an intentionally slow workflow (a Wait node set to 90 s, for example) and verify the response arrives beyond 60 seconds without error. If the 502 persists after the change, verify you edited the correct location block — a nginx configuration split across several include files may have a more specific block that overrides the timeout. Source: community.n8n.io thread #274581.

Migrating from npm to Docker before n8n v3.0

If your instance runs via npx n8n or npm install -g n8n, act before October 2026. n8n v3.0 definitively drops npm/npx mode: instances installed this way will stop receiving updates and security patches. Migration takes four steps, with no data loss.

Step 1 — Export your workflows. In the interface, go to Settings → Import/Export or use the CLI: n8n export:workflow --all --output=workflows.json. Also export your credentials if you wish to migrate them: n8n export:credentials --all --output=credentials.json.

Step 2 — Stop the npm instance. Stop the process (pm2 stop n8n or systemctl stop n8n depending on your process manager) and note the path to the current data folder (usually ~/.n8n).

Step 3 — Deploy the Docker container following the steps above. The n8n_data Docker volume is mounted at /home/node/.n8n in the container. If you copy ~/.n8n directly into the volume, n8n reuses workflows and credentials without manual import.

Step 4 — Import and verify. If you chose JSON export, import from the interface or via: docker exec n8n n8n import:workflow --input=workflows.json. Verify that each active workflow triggers correctly and that credentials are valid in their respective nodes.

Deadline: October 2026. An npm instance still active after v3.0 release continues to work short-term, but receives no more updates — including security patches. Plan the migration now.

Breaking changes to know (v1.27–v1.31)

Three changes in recent versions can silently break an existing instance.

OAuth 2.0 parameter rename in HTTP Request. The oauthTokenData field was renamed in versions 1.27-1.31. Requests authenticated via OAuth 2.0 in the HTTP Request node may stop working without an explicit error message — n8n sends an unauthenticated request rather than raising an exception. Check every workflow using this node with OAuth credentials after an update.

Webhook URL format. The format of the URL generated by Webhook nodes changed in this version range. If you have hardcoded webhook URLs in third-party services (Stripe, GitHub, Slack...), re-verify them after the update.

Deprecation of $item(). The $item() function available in expressions and the Function node is marked deprecated. Its replacement is $input.item for the current item or $('Node Name').item for items from a previous node. It still works in v1.x but will be removed in v3.0.

The full list of breaking changes is available on the official n8n documentation.

Troubleshooting common errors

Port 5678 is unreachable. Verify the container is running (docker compose ps) and that you are listening on 127.0.0.1:5678. If testing from the local machine, curl http://127.0.0.1:5678/ should respond.

Webhooks display localhost instead of the domain. The N8N_WEBHOOK_URL variable is missing or misconfigured. Add N8N_WEBHOOK_URL=https://n8n.your-domain.com/ (with trailing slash) and restart: docker compose restart n8n.

Missing N8N_PROXY_HOPS: 502 error or loop. Without this variable, n8n rejects or loops on X-Forwarded-* headers. Add N8N_PROXY_HOPS=1 in the container environment.

Volume permissions error. The n8n container runs under UID 1000. If the data folder was created by root, permissions are incorrect: chown -R 1000:1000 /opt/n8n/data then docker compose restart n8n.

PostgreSQL refuses the connection. Verify the postgres service is on the same Docker network as n8n (docker network inspect <name>) and that DB_POSTGRESDB_HOST, DB_POSTGRESDB_USER and DB_POSTGRESDB_PASSWORD match exactly those defined in the postgres service.

V8 fatal crash (FATAL ERROR: invalid-mark-compact). The V8 memory heap is saturated: more than 30 concurrent workflows with insufficient RAM. Add NODE_OPTIONS=--max-old-space-size=2048 in the n8n service environment and increase VPS RAM to at least 2 GB. See the dedicated section above.

Persistent 502 Bad Gateway (exactly 60 s). The nginx proxy_read_timeout is at its default (60 s). Raise it to 300 s in the nginx location block pointing to n8n. See the dedicated section above.

Maintaining and scaling the instance

For updates, two approaches: Watchtower (automatic image monitoring and restart) or a manual cron job (docker pull n8nio/n8n:latest && docker compose up -d). In both cases, read the release notes before a major update — the breaking changes listed above are representative of the pace of change. Keep your workflow exports up to date in a git repository: this is the fastest backup to restore in case of an incident.

Your VPS for n8n, in minutes

ServOrbit offers Linux VPS with root access, dedicated IPv4 and NVMe SSD — ready to host Docker and n8n without additional configuration. Visit the developers page to choose the configuration suited to your workflows.

Need help?

Browse our help center and FAQ, or reach our team — callback, WhatsApp or email. Support in French, English and Arabic.