# n8n self hosting on a Frankfurt VPS: Docker, HTTPS and scaling

URL: https://cloudj.net/blog/n8n-self-hosting-vps-germany
Published: 2026-11-11 · Updated: 2026-11-11

> A practical guide to n8n self hosting on a Frankfurt VPS: Docker Compose with PostgreSQL, HTTPS via reverse proxy, backups, and when to switch to queue mode.

n8n self hosting puts your workflow automation, its credentials and its execution history on a server you control. For teams in the European Union, running it on a Frankfurt VPS also keeps that data stored in Germany. This guide is built around the order in which a real deployment grows: a single-container start, a production layout with PostgreSQL and HTTPS, routine backups, and finally queue mode when one process is no longer enough.

## What "data in the EU" does and does not mean

Hosting n8n in Frankfurt means the workflow definitions, encrypted credentials and execution logs live on a disk in Germany. That simplifies conversations about where data is stored. It does not make every workflow stay in the EU: a node that calls a US-hosted API sends the payload there. Map which services your workflows touch, and treat hosting location as one part of your data-protection work, not the whole of it.

Cloudj's [Frankfurt servers](https://cloudj.net/cloud-servers/germany) include 5 TB of traffic on monthly plans. If you are comparing German cities and neighbours, [best VPS in Germany](https://cloudj.net/blog/best-vps-germany) covers the location and the [Europe VPS guide](https://cloudj.net/blog/europe-vps-guide) compares all seven European countries.

## Sizing for n8n self hosting: start small

n8n's own process is modest. Memory use grows with the size of the data your workflows carry, such as large JSON responses, files and binary data. A practical path:

| Stage | Server | Layout |
|---|---|---|
| Trying it out | 1 vCPU, 2 GB RAM | Single container, SQLite |
| Team production | 2 vCPU, 4 GB RAM | n8n plus PostgreSQL, reverse proxy |
| Heavy or many parallel workflows | 4 vCPU, 8 GB RAM or more | Queue mode with Redis and workers |

Shared CPU is fine for most automation, which waits on APIs more than it computes. Move to general purpose or dedicated CPU if workflows transform large datasets. You can resize CPU and RAM later from the console, so pick the stage you are in today.

## Production setup with Docker Compose

Start from a server with SSH keys, a firewall and Docker installed; the [Docker install guide](https://cloudj.net/blog/install-docker-vps) covers that. Point a DNS record such as `n8n.example.com` at the server's IP, then create a project folder:

```bash
mkdir -p ~/n8n && cd ~/n8n
```

Create `.env` with your values. Generate the encryption key once and store a copy somewhere safe: without it, saved credentials cannot be decrypted.

```bash
cat > .env <<'EOF'
N8N_HOST=n8n.example.com
POSTGRES_PASSWORD=change-me-long-random
EOF
echo "N8N_ENCRYPTION_KEY=$(openssl rand -hex 32)" >> .env
```

Create `docker-compose.yml`:

```yaml
services:
  postgres:
    image: postgres:16
    restart: always
    environment:
      - POSTGRES_USER=n8n
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=n8n
    volumes:
      - pg_data:/var/lib/postgresql/data

  n8n:
    image: docker.n8n.io/n8nio/n8n   # pin a version tag in production
    restart: always
    depends_on:
      - postgres
    environment:
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_USER=n8n
      - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
      - N8N_HOST=${N8N_HOST}
      - N8N_PROTOCOL=https
      - N8N_PORT=5678
      - WEBHOOK_URL=https://${N8N_HOST}/
      - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
      - GENERIC_TIMEZONE=Europe/Berlin
      - TZ=Europe/Berlin
    ports:
      - "127.0.0.1:5678:5678"
    volumes:
      - n8n_data:/home/node/.n8n

  caddy:
    image: caddy:2
    restart: always
    network_mode: host
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data

volumes:
  pg_data:
  n8n_data:
  caddy_data:
```

n8n listens only on `127.0.0.1`, so the editor and webhooks are reachable only through the reverse proxy. Caddy obtains and renews a Let's Encrypt certificate automatically and passes WebSocket connections through, which the n8n editor needs. Create `Caddyfile`:

```text
n8n.example.com {
    reverse_proxy 127.0.0.1:5678
}
```

Open the web ports and start everything:

```bash
sudo ufw allow 80,443/tcp
docker compose up -d
docker compose logs -f n8n
```

Visit `https://n8n.example.com` and create the owner account straight away, before anyone else finds the page. If you already run Nginx on the server, use it instead of Caddy; the [Nginx reverse proxy and SSL guide](https://cloudj.net/blog/nginx-reverse-proxy-ssl-vps) shows the configuration, and remember to pass the `Upgrade` and `Connection` headers for WebSockets.

`WEBHOOK_URL` matters more than it looks. Without it, n8n may display webhook URLs with the internal port, and external services will call an address that does not work.

## Backups and updates

Back up three things: the PostgreSQL database, the encryption key in `.env`, and an export of workflows as a readable second copy.

```bash
cd ~/n8n
docker compose exec -T postgres pg_dump -U n8n -Fc n8n > n8n-$(date +%F).dump
docker compose exec -T n8n mkdir -p /home/node/.n8n/backups
docker compose exec -T n8n n8n export:workflow --backup --output=/home/node/.n8n/backups/
docker compose cp n8n:/home/node/.n8n/backups ./workflow-backups
```

Copy the dump, the `.env` file and the `workflow-backups` folder off the server on a schedule. To update n8n, read the release notes, take a backup, then:

```bash
docker compose pull n8n
docker compose up -d
```

Pinning a version tag instead of using the default tag means updates happen when you decide, not whenever the container restarts.

## Queue mode: when one process is not enough

In the default mode, a single n8n process serves the editor, receives webhooks and runs every execution. When long or numerous executions start slowing the editor or delaying webhooks, switch to queue mode. A Redis instance holds the queue, the main process accepts triggers, and separate worker containers run the executions.

The core changes are a Redis service and a few variables shared by the main process and the workers:

```yaml
  redis:
    image: redis:7
    restart: always

  n8n-worker:
    image: docker.n8n.io/n8nio/n8n
    restart: always
    command: worker
    depends_on:
      - redis
      - postgres
    environment:
      - EXECUTIONS_MODE=queue
      - QUEUE_BULL_REDIS_HOST=redis
      # plus the same DB_* and N8N_ENCRYPTION_KEY values as the main n8n service
```

Add `EXECUTIONS_MODE=queue` and `QUEUE_BULL_REDIS_HOST=redis` to the main `n8n` service as well. Workers need the same encryption key and database settings, otherwise they cannot read credentials. Scale with `docker compose up -d --scale n8n-worker=3`, and size the server for the number of workers you run. Check the [n8n documentation](https://docs.n8n.io/hosting/scaling/queue-mode/) for the current options before you switch, since configuration details change between releases.

## Related setups on the same server

A Frankfurt server running n8n often grows into a small internal platform. A [self-hosted Git server](https://cloudj.net/blog/self-hosted-git-server-vps) for automation scripts fits well next to it, and the [Minecraft on a German VPS](https://cloudj.net/blog/minecraft-server-vps-germany) guide shows the same location used for a very different workload. Keep separate concerns on separate servers once any one of them becomes important.

## FAQ

### Can I use SQLite instead of PostgreSQL?

For testing and light personal use, yes. For a team, PostgreSQL handles concurrent executions and execution history more reliably and is easier to back up consistently.

### Is self-hosted n8n free to use?

n8n is distributed under its own fair-code licence, which allows self-hosting for internal use. Read the licence terms if you plan to offer n8n to customers as a service.

### Do I need queue mode from the start?

No. Start with the single-process layout and switch when executions begin to slow the editor or webhooks. The database stays the same.

### What happens if I lose the encryption key?

Workflows survive, but stored credentials cannot be decrypted and must be entered again. Keep the key with your backups.

## Run n8n in Frankfurt

Start with the Compose layout above on a small Frankfurt server, add backups on day one, and move to queue mode when the workload asks for it. Check live prices on the [pricing page](https://cloudj.net/pricing) and deploy from the [Cloudj console](https://app.cloudj.net).
