Provider clusterDay-two operationsUpdated 2026-11-05

Run n8n on RackNerd after one-click: limits, updates, and safe changes

The installer is day zero. This guide owns day two: know your topology, bound the workload, and update without erasing the VPS.

Workload before headline price — verify the plan, terms and recovery path
How to use this. Steps reflect common operator problem-handling patterns and public RackNerd/ctrl flows. Follow your live panel labels; discover actual OS, services, and paths on your host before production changes.

Establish ownership after one-click installation

This is a day-two guide. If the VPS is new or you have not inventoried the image, start with fresh ctrl setup. Never click the one-click app again to repair or upgrade n8n: RackNerd states that it reinstalls Ubuntu and permanently erases existing data.

You need sudo on the VPS, the private inventory created during setup, the public n8n hostname if one exists, and a maintenance window. Changes below apply only after identifying whether n8n is managed by systemd, Docker Compose, another container mechanism, or a different installer. Do not create a second installation merely because the expected path is absent.

Record a redacted topology before editing:

printf '%s\n' '== Time and capacity ==' 
date --iso-8601=seconds
free -h
df -hT /
printf '%s\n' '== n8n processes ==' 
ps -eo pid,comm,args | grep -E '[n]8n|[d]ocker|[c]ontainerd' || true
printf '%s\n' '== Services ==' 
systemctl list-units --all --type=service 'n8n*' --no-pager
printf '%s\n' '== Compose definitions (paths only) ==' 
sudo find /opt /srv /root -maxdepth 4 -type f \
  \( -name 'compose.yml' -o -name 'compose.yaml' \
     -o -name 'docker-compose.yml' -o -name 'docker-compose.yaml' \) \
  -print 2>/dev/null
if command -v docker >/dev/null 2>&1; then
  sudo docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}'
  sudo docker compose ls
fi
sudo ss -lntp

Process arguments can contain sensitive values in a poorly configured system. Keep the output private and redact before sharing. Do not print .env, container JSON, env, credentials, or database connection strings.

Classify exactly one primary path:

  • Compose: record the project directory and canonical compose file. Inspect only non-secret keys such as services, image, ports, volumes and restart; edits must preserve existing secret interpolation.
  • systemd/native: record the unit name, unit-file path, working directory, and configured state path without exposing its environment values.
  • Unknown/other: stop and consult the actual image documentation or support for infrastructure identification. Application administration remains your responsibility on an unmanaged VPS.

The acceptance test is a named owner, restart mechanism, persistent paths, public exposure and current version. All later branches refer back to this inventory.

Preserve state, encryption material and stable URLs

A workflow JSON export is not a disaster-recovery backup. n8n’s .n8n directory includes configuration and credential encryption material; the database holds workflows, credentials, users and execution state depending on configuration. Custom nodes, binary data and external storage may live elsewhere. Follow the canonical complete backup and isolated restore runbook before changing versions or URLs.

At minimum, locate and record the state/database type without reading secrets:

# Only after discovery: replace with the real state directory.
N8N_STATE='/srv/REPLACE_WITH_DISCOVERED_N8N_HOME'
sudo test -d "$N8N_STATE"
sudo find "$N8N_STATE" -maxdepth 1 -mindepth 1 -printf '%f\n'
if sudo test -f "$N8N_STATE/database.sqlite"; then
  printf '%s\n' 'SQLite database candidate found; use the SQLite backup branch.'
else
  printf '%s\n' 'No SQLite file at this path; inspect documented DB_TYPE via a secret-safe config review.'
fi

Do not copy an active SQLite database with plain cp. Do not move or regenerate encryptionKey to “fix” credentials. Loss of that material can make stored credentials unreadable even when workflows remain.

For a public instance, choose one canonical HTTPS name such as n8n.example.com. Point DNS to the intended VPS, terminate TLS at one reverse proxy, and configure the discovered n8n deployment with these values, not a second .env source:

N8N_HOST=n8n.example.com
N8N_PROTOCOL=https
N8N_EDITOR_BASE_URL=https://n8n.example.com/
N8N_WEBHOOK_URL=https://n8n.example.com/
N8N_PROXY_HOPS=1

N8N_WEBHOOK_URL is the current name; WEBHOOK_URL is deprecated from n8n 2.35. N8N_PROXY_HOPS=1 is appropriate only for one trusted proxy hop. If your chain has more, recalculate from the real architecture. Forward X-Forwarded-For, X-Forwarded-Host and X-Forwarded-Proto; do not trust arbitrary forwarded headers directly from the internet.

With Nginx as the chosen host proxy, adapt this complete site skeleton to the discovered n8n loopback port. This example assumes n8n listens on 127.0.0.1:5678; do not paste it if Docker networking or an existing proxy differs.

server {
    listen 80;
    listen [::]:80;
    server_name n8n.example.com;
    # Use your ACME client to provision TLS, then enable its HTTPS server.
    location / { return 404; }
}

# Enable this server only after a certificate exists for n8n.example.com.
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name n8n.example.com;
    ssl_certificate     /etc/letsencrypt/live/n8n.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/n8n.example.com/privkey.pem;

    client_max_body_size 16m;
    location / {
        proxy_pass http://127.0.0.1:5678;
        proxy_http_version 1.1;
        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_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 3600s;
    }
}

The certificate paths must be created by your ACME client; Nginx will fail its test if they do not exist. Keep the original site configuration for rollback. Validate with sudo nginx -t, reload only on success, and test a new HTTPS session plus one test webhook. Roll back both the proxy site and n8n URL variables together if generated links or webhooks become inconsistent.

Bound executions before they exhaust a small VPS

n8n’s regular-mode production concurrency limit is disabled by default. It limits active production executions, not every source of work: manual, subworkflow, error and CLI executions are excluded. A limit is therefore not a complete memory sandbox.

For a 2–4 GB single-host editorial starting point, set N8N_CONCURRENCY_PRODUCTION_LIMIT=5 only after measuring workflow payload and external API behavior. This is an AiXVPS starting value, not a RackNerd/n8n default. Raise or lower it from observed memory and queue latency. Large binary payloads may require 1; I/O-light API calls may tolerate more.

Also apply provider-side API rate limits, workflow timeouts and idempotency. Do not let retrying webhooks create an unbounded loop. Test one controlled webhook at the chosen limit while watching free -h, container/service health and failed units. If memory falls into your reserved recovery margin, reduce the limit and stop the test.

Queue mode is a different architecture with Redis and workers; do not add it just to solve a small-host memory problem. Split the workload first when a single box cannot protect the database and editor.

Prune execution data without deleting audit evidence

n8n documentation lists pruning enabled by default, maximum age 336 hours (14 days), and maximum stored count 10,000. Those defaults may still exceed a small disk when executions contain large payloads. Tighter values below are editorial examples, not official defaults:

EXECUTIONS_DATA_PRUNE=true
EXECUTIONS_DATA_MAX_AGE=168
EXECUTIONS_DATA_PRUNE_MAX_COUNT=2500
EXECUTIONS_DATA_SAVE_ON_ERROR=all
EXECUTIONS_DATA_SAVE_ON_SUCCESS=all
EXECUTIONS_DATA_SAVE_MANUAL_EXECUTIONS=false

Review current n8n execution environment documentation before applying. Saving successful/manual executions less aggressively reduces storage but also destroys troubleshooting and audit history. Legal or business retention requirements override the sample. Binary/external data has its own lifecycle; pruning database rows does not prove every external artifact was deleted.

Apply environment changes through the discovered Compose/systemd configuration, validate syntax, and restart through that same mechanism. Before restarting, confirm no critical production execution is active. Afterward, record disk before/after only after the pruning cycle runs; restarting alone does not promise immediate reclamation of SQLite/Postgres filesystem space.

If the disk is already critically full, stop new executions, make checked exports to another machine, and remove only known disposable application logs after attribution. Never delete a database file, Docker volume or .n8n directory to create space.

Health checks: reachability is not readiness

n8n exposes /healthz and /healthz/readiness. The first proves HTTP reachability; readiness additionally checks database connection and migrations. Run locally first against the discovered listener:

BASE_URL='http://127.0.0.1:5678' # replace only if discovery found another local endpoint
curl --fail --silent --show-error --max-time 10 "$BASE_URL/healthz"
curl --fail --silent --show-error --max-time 10 "$BASE_URL/healthz/readiness"

Then test the canonical public HTTPS URL from another network. Do not disable TLS verification. A public response alone does not prove webhooks execute, credentials decrypt, pruning works, or backups restore.

A lightweight monitoring script can record status without printing bodies or secrets:

#!/usr/bin/env bash
set -u
BASE_URL="${1:?Usage: n8n-health.sh https://n8n.example.com}"
case "$BASE_URL" in https://*) ;; *) printf '%s\n' 'HTTPS URL required' >&2; exit 2;; esac
for path in /healthz /healthz/readiness; do
  if curl --fail --silent --show-error --max-time 10 \
      --output /dev/null "$BASE_URL$path"; then
    printf 'OK %s\n' "$path"
  else
    rc=$?
    printf 'FAIL %s (curl=%s)\n' "$path" "$rc" >&2
    exit "$rc"
  fi
done

Save as /usr/local/sbin/n8n-health with mode 0755 only after reviewing it. Test both the canonical URL and an intentional bad port/hostname; the latter must produce nonzero. Alert on repeated failure rather than restarting automatically on one transient response. Restart loops can conceal a failed migration or full disk.

Update through the discovered mechanism only

The update gate is deliberately strict:

[ ] Current version, deployment owner, image/unit and persistent paths recorded.
[ ] Current n8n upgrade notes and breaking changes reviewed for the target version.
[ ] Full state + database + encryption material exported off-server.
[ ] Restic/repository check succeeded; isolated restore test succeeded.
[ ] Enough disk for images/exports; no active production execution.
[ ] Maintenance window and rollback owner available.
[ ] I will NOT use the RackNerd one-click installer as an updater.

Complete the canonical backup/restore procedure before proceeding. An older n8n image may not understand a newer database schema. “Pull the old tag” is not a universal rollback.

For an operator-managed Docker Compose deployment only, enter the discovered project directory and use its actual compose file. First pin an intentional compatible target in the compose file or its non-secret version variable; avoid uncontrolled latest. Preserve a copy of the compose file on your workstation with secrets redacted. Then:

PROJECT='/srv/REPLACE_WITH_DISCOVERED_COMPOSE_PROJECT'
cd "$PROJECT" || exit 1
test -f compose.yml || test -f compose.yaml || {
  printf '%s\n' 'Compose file not found; stop and rediscover.' >&2; exit 1;
}
sudo docker compose config --quiet
sudo docker compose pull
sudo docker compose down
sudo docker compose up -d
sudo docker compose ps

down interrupts the application; it does not make an unsafe backup safe. If config, pull, or the pre-update gate fails, do not continue. If startup fails, inspect the discovered service logs without dumping environment, check migration/readiness errors and available disk. Recovery may require the full preserved database/state at the pre-update version on an isolated or intentionally restored system; use the lifecycle runbook rather than guessing at a downgrade.

For systemd/native, use the package/version method that actually installed n8n, after the same gate and current official upgrade instructions. Do not paste an npm/Docker command into an unknown image. For ctrl-managed components, clarify which layer RackNerd maintains; default VPS service remains unmanaged.

Acceptance requires /healthz/readiness, editor login, credential decryption on a safe test, one controlled production-path webhook, persistence across the planned restart, and normal disk/memory. Only then resume traffic.

Troubleshoot by layer, not by reinstalling

Observation Read-only checks Safe next action
Public URL unreachable, local health works DNS, TLS, nginx -t, proxy listener, ctrl/UFW Repair proxy/firewall; preserve n8n process
/healthz works, readiness fails Database service, disk, migration logs Stop writes if needed; restore DB connectivity before retries
Credentials fail after migration State-path/encryption-material inventory Restore matching encryption material and database; never regenerate to conceal loss
Webhook URL is wrong Canonical URL variables and forwarded headers Align app and proxy; test one controlled webhook
Memory pressure / OOM free, service logs, active executions Stop traffic, lower concurrency/payload, split workload
Disk full df, du, execution/binary retention Stop writes, export off-box, prune policy or expand capacity
Update fails Target notes, Compose config, migrations Keep traffic stopped; follow full-state recovery, not ctrl reinstall

For logs, use the discovered owner: sudo journalctl -u UNIT --since '-30 min' --no-pager or sudo docker compose logs --since 30m SERVICE. Logs may contain payloads, URLs and tokens; store them privately and redact before sharing.

After any incident, repeat the health checks, one non-sensitive execution, capacity review and backup verification. The long-term exit is not another reinstall: it is a tested migration with cancel-old-last.