Technology

The €5/Month n8n Setup That Holds Up in Production

I moved an n8n instance off managed cloud eight months ago. The paid tier was hitting $50/month for 200 daily workflow runs. A Hetzner CX21 at €4.20/month handles the same load without breaking a sweat. This guide covers the exact Docker Compose setup I run, why you should start with PostgreSQL and not SQLite, how to stop memory from killing your process at 2am, and when queue mode actually makes sense on a single cheap box. No theory, just what works.

· 8 mins read
Summarize and analyze this article with:
Share this

I moved an n8n instance off a managed cloud service about eight months ago. The paid tier was costing around $50/month for workflows that ran maybe 200 times a day. Nothing heavy. A Hetzner CX21 at 4.19 euros/month handles the same load comfortably, with room to spare.

This is what I learned doing that migration, and a few things I got wrong the first time.


Why Self-Host in the First Place

The honest answer is money. n8n Cloud is priced around execution volume, and once you have real automations running on real schedules, the math gets uncomfortable fast. You also get no control over the runtime environment, no ability to install custom nodes without a higher plan, and zero visibility into what happens when a workflow times out.

Self-hosting trades that monthly fee for your own time and a cheap VPS bill. Whether that trade makes sense depends on how much you hate downtime versus how much you hate paying recurring fees.


Picking a VPS That Won't Embarrass You

For a typical n8n setup running 10-20 active workflows, you do not need much.

What actually matters:

  • At least 2 GB RAM. n8n's Node.js process idles around 200-300 MB but spikes during execution, especially with large data payloads. 1 GB boxes tend to OOM-kill the process at the worst time.
  • SSD storage. The SQLite database (default) does fine on any SSD. If you plan to switch to PostgreSQL, it matters more.
  • A European or Asian datacenter if your workflows hit APIs with rate limits by origin. Latency is not usually the bottleneck, but it affects webhook response times.

Providers worth considering in 2025:

ProviderPlanRAMStoragePrice (approx)
HetznerCX214 GB40 GB SSD~€4.20/mo
ContaboVPS S SSD8 GB50 GB NVMe~€5.99/mo
DigitalOceanBasic Droplet2 GB50 GB SSD~$12/mo
VultrRegular2 GB55 GB NVMe~$12/mo

Hetzner is the default recommendation in most communities right now, mostly because of the price-to-spec ratio in Germany and Finland. Contabo is cheaper if you can tolerate slower support and occasional network jitter.


The Setup: Docker Compose or Bare Node?

I prefer Docker Compose. Not because it is technically superior for this use case, but because it makes upgrades and rollbacks a single command.

Here is the setup I run in production:

yaml

version: '3.8'

services:
  n8n:
    image: n8nio/n8n:latest
    restart: unless-stopped
    ports:
      - "5678:5678"
    environment:
      - N8N_HOST=your-domain.com
      - N8N_PORT=5678
      - N8N_PROTOCOL=https
      - N8N_ENCRYPTION_KEY=your-random-key-here
      - WEBHOOK_URL=https://your-domain.com/
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_USER=n8n
      - DB_POSTGRESDB_PASSWORD=your-db-password
      - EXECUTIONS_DATA_PRUNE=true
      - EXECUTIONS_DATA_MAX_AGE=168
    volumes:
      - n8n_data:/home/node/.n8n
    depends_on:
      - postgres

  postgres:
    image: postgres:15
    restart: unless-stopped
    environment:
      - POSTGRES_DB=n8n
      - POSTGRES_USER=n8n
      - POSTGRES_PASSWORD=your-db-password
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  n8n_data:
  postgres_data:

A few things worth explaining:

EXECUTIONS_DATA_PRUNE=true with EXECUTIONS_DATA_MAX_AGE=168 keeps execution history to 7 days. Without this, the database grows without bound and eventually slows everything down. On a small VPS, this matters.

N8N_ENCRYPTION_KEY protects credentials at rest. Generate something random and store it somewhere safe. If you lose it, all saved credentials become unreadable.

Switch to PostgreSQL from SQLite early if you plan to scale or run multiple workers. SQLite works fine for low-volume setups, but migrating later is annoying.


Reverse Proxy: Nginx or Caddy

You need HTTPS, and you need a reverse proxy in front of n8n to handle it. Two reasonable options:

Caddy is simpler. It handles SSL via Let's Encrypt automatically and the config is minimal:

your-domain.com {
    reverse_proxy localhost:5678
}

That is the entire config. It does TLS, handles renewals, and works with wildcard certs if you configure DNS challenge.

Nginx gives you more control if you need custom headers, rate limiting, or want to run multiple services on the same server:

nginx

server {
    listen 443 ssl;
    server_name your-domain.com;

    ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;

    location / {
        proxy_pass http://localhost:5678;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_cache_bypass $http_upgrade;
        client_max_body_size 100M;
    }
}

The client_max_body_size 100M line matters. n8n workflows that process files or large JSON payloads will fail silently on the default Nginx limit (1MB) and it takes a while to diagnose.


Memory: The Thing That Will Kill You

The single biggest issue with budget VPS hosting for n8n is memory pressure. Node.js will use what it can get, and when there is nothing left, the OS kills the process.

Three things help:

1. Add swap. Most VPS providers give you no swap by default. 2 GB of swap on an SSD gives you a buffer:

bash

fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab

SSD-backed swap is slow compared to RAM, but it keeps the process alive instead of crashing it.

2. Set Node.js memory limits. Add this to the n8n service environment:

yaml

- NODE_OPTIONS=--max-old-space-size=512

This caps V8's heap at 512 MB per process. It will throw errors for genuinely large workloads, but those errors are catchable and debuggable. OOM kills are neither.

3. Monitor it. Set up a cron job or a simple uptime monitor. n8n has a built-in health endpoint at /healthz. Ping it from an external service like UptimeRobot (free tier works) so you know when the process dies before your client does.


Scaling Without Buying More Servers

At some point the single-process setup hits a ceiling. n8n supports a queue mode that distributes execution across worker processes. You can run multiple workers on the same server (vertical scaling) or spread them across cheap VPS boxes (horizontal scaling).

To enable queue mode, you need Redis and a separate worker container:

yaml

  redis:
    image: redis:7-alpine
    restart: unless-stopped
    volumes:
      - redis_data:/data

  n8n-worker:
    image: n8nio/n8n:latest
    restart: unless-stopped
    command: worker
    environment:
      - QUEUE_BULL_REDIS_HOST=redis
      - DB_TYPE=postgresdb
      # ... same DB config as main service
    depends_on:
      - postgres
      - redis

And on the main n8n service:

yaml

    environment:
      - EXECUTIONS_MODE=queue
      - QUEUE_BULL_REDIS_HOST=redis

Two workers on a 4 GB VPS can handle a decent amount of parallel execution. Add a third box later if you need it. Each worker is stateless and picks jobs from Redis, so horizontal scaling is mostly a matter of spinning up another VPS and pointing it at the same database and Redis.


Backups: The Part Everyone Skips

If you run this without backups and the VPS provider has an incident, you lose everything. Workflows, credentials, execution history.

The minimum viable backup is a daily pg_dump piped to an S3-compatible bucket. AWS S3, Cloudflare R2, or Backblaze B2 all work:

bash

#!/bin/bash
DATE=$(date +%Y%m%d_%H%M%S)
docker exec postgres pg_dump -U n8n n8n | gzip > /tmp/n8n_backup_$DATE.sql.gz
aws s3 cp /tmp/n8n_backup_$DATE.sql.gz s3://your-bucket/n8n-backups/
rm /tmp/n8n_backup_$DATE.sql.gz

Put this in a cron job at 3am. Cloudflare R2 has a free tier that easily covers a few months of compressed database dumps.


What This Actually Costs

A realistic production setup for a small team or indie developer, handling 1000-5000 workflow executions per day:

  • Hetzner CX21 (4 GB RAM): ~€4.20/month
  • Cloudflare R2 storage for backups: free tier (covers up to 10 GB)
  • UptimeRobot monitoring: free
  • Domain (if you don't have one): ~$10/year

Total: under €6/month running, maybe €10/month once you account for the domain.

n8n Cloud starts at $20/month for the Starter plan, capped at 2500 executions. If you are running more than that, the savings add up quickly.


Things I Got Wrong the First Time

I switched to PostgreSQL three months in, after the SQLite database grew to 800 MB and query times got noticeably slow. Should have started with Postgres.

I also did not set EXECUTIONS_DATA_PRUNE early enough. The execution history table had millions of rows by the time I noticed. Running the prune on a live instance locked the table for about 40 minutes.

The last one: I put the n8n instance on port 443 directly without a reverse proxy for the first week because I wanted to skip the setup. When I eventually added Nginx, three webhooks broke because the URL structure changed slightly and two of the external services did not retry.

Set up the full stack from the start. It takes an extra 30 minutes and saves a lot of debugging later.


Read next

The AI Copilot Cost Crisis: When Coding Assistants Become More Expensive Than Developers

GitHub Copilot dropped its flat-rate subscription on June 1 and switched every interaction to token-based billing. For developers running agentic workflows, the math got brutal fast. Monthly bills are jumping 10x to 50x. One developer's projected cost went from $29 to $750. A three-person team is looking at $1,800 to $3,600 a month. Here is what changed, why it happened, and what developers are actually switching to.

· 8 mins read