// DevOps
Installing n8n on FastPanel with Docker Compose: A Clear Guide
Published on 2026-09-22
In this guide we install n8n on a server with the FastPanel control panel using Docker Compose. n8n itself runs in containers together with PostgreSQL, Redis and a separate worker, while FastPanel handles the domain, certificate and reverse proxy. The configuration mirrors the official n8n withPostgresAndWorker example from the n8n-hosting repository — with adjustments for FastPanel. Installation without a panel, behind HAProxy, is described in the article “Installing n8n in Docker with HAProxy”.
Requirements
- A server with FastPanel installed and Docker with the
docker composeplugin. - A domain or subdomain for n8n, e.g.
n8n.example.com, with an A record pointing to the server. - SSH access.
A worker and Redis are needed when there are many processes and they are long-running: the main n8n process accepts webhooks and shows the editor, while execution is handled by workers. For a small installation a single n8n container with PostgreSQL is enough. How the queue mode works is covered in the article “n8n: Part 5 — Scaling”.
Step 1: Prepare the server
sudo mkdir -p /opt/n8n_stack
cd /opt/n8n_stackStep 2: Configuration files
You will need three files: docker-compose.yml, .env and init-data.sh.
2.1. .env
N8N_VERSION=2.40.5
N8N_DOMAIN=n8n.example.com
# PostgreSQL administrator
POSTGRES_USER=postgres_admin
POSTGRES_PASSWORD=replace-with-a-long-password
POSTGRES_DB=n8n
# separate database user for n8n
POSTGRES_NON_ROOT_USER=n8n_user
POSTGRES_NON_ROOT_PASSWORD=replace-with-a-different-password
# openssl rand -hex 32 — for each of the two keys
ENCRYPTION_KEY=replace
RUNNERS_AUTH_TOKEN=replaceENCRYPTION_KEY encrypts credentials in the n8n database. Generate it once and store it separately from the server: without it a database backup is useless. RUNNERS_AUTH_TOKEN is the shared secret between n8n and the task runner containers that execute the Code node.
2.2. docker-compose.yml
The version: line at the start of the file is not needed: modern Docker Compose ignores it.
volumes:
db_storage:
n8n_storage:
redis_storage:
x-shared: &shared
restart: always
image: docker.n8n.io/n8nio/n8n:${N8N_VERSION}
environment:
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_PORT=5432
- DB_POSTGRESDB_DATABASE=${POSTGRES_DB}
- DB_POSTGRESDB_USER=${POSTGRES_NON_ROOT_USER}
- DB_POSTGRESDB_PASSWORD=${POSTGRES_NON_ROOT_PASSWORD}
- EXECUTIONS_MODE=queue
- QUEUE_BULL_REDIS_HOST=redis
- QUEUE_HEALTH_CHECK_ACTIVE=true
- N8N_ENCRYPTION_KEY=${ENCRYPTION_KEY}
- N8N_RUNNERS_MODE=external
- N8N_RUNNERS_AUTH_TOKEN=${RUNNERS_AUTH_TOKEN}
- N8N_RUNNERS_BROKER_LISTEN_ADDRESS=0.0.0.0
- OFFLOAD_MANUAL_EXECUTIONS_TO_WORKERS=true
- N8N_HOST=${N8N_DOMAIN}
- N8N_PROTOCOL=https
- WEBHOOK_URL=https://${N8N_DOMAIN}/
- N8N_PROXY_HOPS=1
- GENERIC_TIMEZONE=Europe/Moscow
- TZ=Europe/Moscow
volumes:
- n8n_storage:/home/node/.n8n
depends_on:
redis:
condition: service_healthy
postgres:
condition: service_healthy
x-runner: &runner
restart: always
image: n8nio/runners:${N8N_VERSION}
services:
postgres:
image: postgres:18
restart: always
environment:
- POSTGRES_USER
- POSTGRES_PASSWORD
- POSTGRES_DB
- POSTGRES_NON_ROOT_USER
- POSTGRES_NON_ROOT_PASSWORD
- PGDATA=/var/lib/postgresql/data
volumes:
- db_storage:/var/lib/postgresql/data
- ./init-data.sh:/docker-entrypoint-initdb.d/init-data.sh
healthcheck:
test: ['CMD-SHELL', 'pg_isready -h localhost -U ${POSTGRES_USER} -d ${POSTGRES_DB}']
interval: 5s
timeout: 5s
retries: 10
redis:
image: redis:7-alpine
restart: always
volumes:
- redis_storage:/data
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
interval: 5s
timeout: 5s
retries: 10
n8n:
<<: *shared
ports:
- "127.0.0.1:5678:5678" # n8n is published externally by FastPanel
n8n-runner:
<<: *runner
environment:
- N8N_RUNNERS_AUTH_TOKEN=${RUNNERS_AUTH_TOKEN}
- N8N_RUNNERS_TASK_BROKER_URI=http://n8n:5679
depends_on:
- n8n
n8n-worker:
<<: *shared
command: worker
depends_on:
- n8n
n8n-worker-runner:
<<: *runner
environment:
- N8N_RUNNERS_AUTH_TOKEN=${RUNNERS_AUTH_TOKEN}
- N8N_RUNNERS_TASK_BROKER_URI=http://n8n-worker:5679
depends_on:
- n8n-workerWhat changed compared to older instructions:
- Versions. The n8n image is from the 2.x branch instead of 1.91, PostgreSQL 18 instead of 11 — support for PostgreSQL 11 ended in 2023, while 18 is supported until November 2030. The
PGDATAline is needed so Postgres 18 stores data in the mounted volume. If you already run an older version, you cannot just change the tag: first runpg_dumpall, then restore into the new version. - Queue mode is enabled explicitly:
EXECUTIONS_MODE=queueandQUEUE_BULL_REDIS_HOST=redis. Without these lines the worker won’t receive jobs and Redis will idle. - Task runners. Since n8n 2.0 the Code node’s code runs in separate
n8nio/runnerscontainers: the main process and each worker have their own runner. - There is no custom
healthcheck.jsanymore: n8n has a built-in/healthzcheck, and for the worker it’s enabled via theQUEUE_HEALTH_CHECK_ACTIVE=truevariable.
2.3. init-data.sh
The script runs once at the first PostgreSQL startup and creates a separate user for n8n:
#!/bin/bash
set -e;
if [ -n "${POSTGRES_NON_ROOT_USER:-}" ] && [ -n "${POSTGRES_NON_ROOT_PASSWORD:-}" ]; then
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
CREATE USER ${POSTGRES_NON_ROOT_USER} WITH PASSWORD '${POSTGRES_NON_ROOT_PASSWORD}';
GRANT ALL PRIVILEGES ON DATABASE ${POSTGRES_DB} TO ${POSTGRES_NON_ROOT_USER};
GRANT CREATE ON SCHEMA public TO ${POSTGRES_NON_ROOT_USER};
EOSQL
else
echo "SETUP INFO: No Environment variables given!"
fichmod +x init-data.shThe GRANT CREATE ON SCHEMA public line is mandatory: starting with PostgreSQL 15 a normal user cannot create tables in the public schema without explicit permission.
Step 3: Start
cd /opt/n8n_stack
docker compose up -d
docker compose psAll services should switch to running, and postgres and redis to healthy. You can check n8n itself like this:
curl -sf http://127.0.0.1:5678/healthz && echo OKStep 4: FastPanel configuration
- In FastPanel open Sites → Add Site and specify the domain from
.env. - In the site’s settings enable the reverse proxy to
http://127.0.0.1:5678. - Issue a Let’s Encrypt certificate for the domain and enable HTTP to HTTPS redirect.
- Add WebSocket support — the n8n editor keeps a persistent connection to the server. If the panel didn’t add it automatically, add the following in the additional nginx directives for this proxy:
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;More about the panel’s capabilities in the article “Hosting Control Panels: Part 4 — FASTPANEL”.
Step 5: First login
Open https://n8n.example.com. n8n will prompt you to create the owner account — do it immediately before the address is discovered by others. There are no longer login and password environment variables: basic auth support was removed in n8n 1.0.
After logging in check the Webhook node: the webhook URL should start with https://n8n.example.com/.
Backup and upgrade
- Backup: database dump (
docker compose exec postgres pg_dump -U postgres_admin n8n > n8n.sql), then8n_storagevolume and the encryption key from.env. - Upgrade: make a backup, change
N8N_VERSIONin.envto the new version and rundocker compose pull && docker compose up -d. The main process, workers and runners must run on the same version.
// Reviews
Related reviews
As always, prompt and high-quality! I turn to Mikhail for server issues.
As always, prompt and high-quality! For server-related issues, I turn to Mikhail.
// Contact
Need help?
Get in touch with me and I'll help solve the problem
I reply within one business day (03:00-13:00 GMT)
Или оставьте заявку здесь:
// Related