Provider clusterDeployment & day-two operationsUpdated 2026-11-05

n8n on Vultr: build one stack you can restore and update

An AiXVPS custom reference: private Postgres, internal n8n, HTTPS-only Caddy, protected secrets, bounded work, and tested recovery before real workflows.

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

Choose a path without erasing an existing n8n

A new accepted Ubuntu Cloud Compute host can use this AiXVPS custom reference if you operate its OS, database, TLS, secrets and recovery. Reviewed Marketplace results found no dedicated Vultr-labs n8n/Ollama listing, including direct candidate checks; that is not a universal-absence claim. An inspected Docker image supplies runtime—not this database/proxy/backup stack.

Reader has Decision Unsafe shortcut
Accepted blank host Build the application below after current release/support checks; consider Regular 2 GB for a deliberately constrained experiment if OS/n8n/Postgres fit Treating 2 or 4 GB as a production minimum without retention/concurrency observation
Existing custom n8n Inventory app/DB/domain/configuration/key then export and migrate Second Compose at the same domain, volume, key or database
Chosen N99panel Vendor onboarding, support, billing and backup path Applying this Compose recipe to its different deployment
Marketplace other app Separate workflow: inspect credentials and files first Assuming Docker means n8n is installed
Wants managed application Consider n8n Cloud or another managed service Mistaking host infrastructure or automatic backups for workflow management

One host holds regular-mode n8n and Postgres for this bounded starting architecture. Queue mode is not enabled: it adds Redis, synchronized workers and binary-data decisions. See official queue documentation. External model endpoints stay optional through AI application placement. Neither mode is automatically a RAM remedy.

Discovery and prerequisites before directories

Complete host acceptance. Know the administrator, firewall layers, domain A/AAAA/DNS route, recovery target, RPO, legal/data requirements, free disk/memory and external integration budget. Existing valuable data belongs in migration/restore before any writes here.

printf '%s\n' '== resources =='
free -h; df -hT; lsblk -o NAME,SIZE,FSTYPE,TYPE,MOUNTPOINTS
printf '%s\n' '== services and actual listeners =='
systemctl list-units --type=service --state=running --no-pager
ss -lntup
printf '%s\n' '== container projects and mounts =='
docker compose ls
ls -la /opt
if test -d /opt/aixvps/n8n; then
  sudo find /opt/aixvps/n8n -maxdepth 2 \( -name compose.yml -o -name Caddyfile -o -name .env \)
fi
docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Mounts}}\t{{.Ports}}'

An unexpected proxy, port 80/443 service, n8n/Postgres service or /opt/aixvps/n8n means stop. Inventory owner/schema/version, workers, files/custom nodes, DB host, n8n canonical URLs, key origin and current backup/update mechanism. No umount, chown, rm, parallel project or re-key guesses.

Review n8n Docker/Compose installation, PostgreSQL support and security audit. At 2026-11-05 evidence check, the legacy installation page marked outdated showed n8n stable 2.46.6 in favor of the n8n-hosting repository. In the documented July 2026 support snapshot maintained Postgres 17/18 plus compatibility 16, with latest minor in selected major. The dated pins here require fresh release/support/digest acceptance each production run, not permanent currency.

Prepare RPO/independent encrypted destination before owner/credential workflow data. If an existing installation looks empty or cannot decrypt, stop: investigate key/database/mounted volume rather than generate a new key or filesystem over it.

Build the protected project and stable key

This small AiXVPS custom scaffold checks a known new target and probes image user/health capability before writes. It leaves protected secrets under exclusive root paths; operator must securely transfer escrow into password manager and off-host independent repository. No real keys are generated or displayed on this page.

Run deliberately as root on the accepted host after changing public-host placeholder settings:

sudo -i
install -d -m 700 /opt/aixvps
printf '%s\n' 'Created for one new n8n reference; 2026-11-05 source review' > /opt/aixvps/README
chmod 600 /opt/aixvps/README
export N8N_DOMAIN='n8n.your-owned-domain.example'
export TLS_EMAIL='replace-cert-admin@your-owned-domain.example'
export GENERIC_TIMEZONE='America/Los_Angeles' # or actual IANA zone

Review/run the creator only for this new project. Exporting an email is different from exposing key material; do not substitute literal domain/email or command-line secrets.

#!/usr/bin/env bash
set -Eeuo pipefail
umask 077
PROJECT='/opt/aixvps/n8n'
[[ ${EUID} -eq 0 ]] || { printf '%s\n' 'Run as root on the discovered new host.' >&2; exit 1; }
[[ -n ${N8N_DOMAIN:-} && $N8N_DOMAIN != *example.com ]] || { printf '%s\n' 'Export real N8N_DOMAIN first.' >&2; exit 1; }
[[ -n ${TLS_EMAIL:-} && ${GENERIC_TIMEZONE:-} ]] || { printf '%s\n' 'Set TLS_EMAIL and GENERIC_TIMEZONE.' >&2; exit 1; }
[[ ! -e $PROJECT ]] || { printf '%s\n' 'Target exists: discover/export; do not overwrite occupied state.' >&2; exit 1; }
[[ -f /opt/aixvps/README ]] || { printf '%s\n' 'Stop: /opt/aixvps exists without your creation record.' >&2; exit 1; }
mkdir -p "$PROJECT/secrets" /root/aixvps-secret-escrow
chmod 700 "$PROJECT" "$PROJECT/secrets" /root/aixvps-secret-escrow
# Synthetic calls to dd are not application validation. Read actual current image ownership:
# docker run --rm --entrypoint id n8nio/n8n:CHOSEN_PIN
docker run --rm --entrypoint sh n8nio/n8n:2.46.6 -c 'id; test -r /etc/os-release; command -v wget'
dd if=/dev/urandom of="$PROJECT/secrets/postgres_password" bs=32 count=1 status=none
dd if=/dev/urandom of="$PROJECT/secrets/n8n_encryption_key" bs=32 count=1 status=none
cp -a "$PROJECT/secrets/postgres_password" "$PROJECT/secrets/n8n_encryption_key" /root/aixvps-secret-escrow/
chmod 600 "$PROJECT"/secrets/* /root/aixvps-secret-escrow/*
for name in POSTGRES_DB POSTGRES_USER; do
  value=$(openssl rand -hex 12)
  printf '%s=%s\n' "$name" "$value" >>"$PROJECT/.env"
done
printf 'N8N_DOMAIN=%s\nTLS_EMAIL=%s\nGENERIC_TIMEZONE=%s\n' "$N8N_DOMAIN" "$TLS_EMAIL" "$GENERIC_TIMEZONE" >>"$PROJECT/.env"
chmod 600 "$PROJECT/.env"
printf '%s\n' 'Now install the reviewed compose.yml and Caddyfile in that exact directory.'
printf '%s\n' 'Move escrow secrets to the password manager and off-host protected repository before valuable workflows.'

The selected image’s real UID/GID and readiness client (wget) get checked. Revalidate image user before mounting bind directories or Docker secrets; named volumes need no guessed global UID chown. If wget is absent or readiness fails, use selected image’s supported actual DB/readiness request, not an always-true check.

Secrets stay in dedicated root 600 host files via [config-name]_FILE, with a separate secret escrow rather than random per-restart key. .env contains only nonsecret identifiers. These variables shown are not vendor defaults for concurrency or timeouts:

# Non-secret identifiers only. Choose actual values; do not reuse .env on recovery.
POSTGRES_DB=n8n
POSTGRES_USER=n8n_operator
N8N_DOMAIN=n8n.example.com
TLS_EMAIL=operator@example.com
GENERIC_TIMEZONE=America/Los_Angeles
N8N_DIAGNOSTICS_ENABLED=false
# AiXVPS conservative examples, NOT n8n defaults or workload guarantees.
N8N_CONCURRENCY_PRODUCTION_LIMIT=5
EXECUTIONS_DATA_MAX_AGE=336
EXECUTIONS_DATA_PRUNE_MAX_COUNT=10000
EXECUTIONS_TIMEOUT=900
EXECUTIONS_TIMEOUT_MAX=900

Save env.example as nonsecret template, keep .env protected and do not print either real secret file. Generated DB/user are identifiers, not replacements for unique Postgres password and stable n8n key. Preserve the key over updates, ordinary restarts and recovery. Explicit supported rotation is an intentional migration, not generated automatically.

Review then install compose/Caddyfile below into /opt/aixvps/n8n and check source provenance/digests:

cd /opt/aixvps/n8n || exit 1
stat -c '%a %U:%G %n' .env secrets/* compose.yml Caddyfile
set -a; . ./.env; set +a
docker compose config --quiet
for selected_image in 'postgres:17.11@sha256:748a18a54d3842d43bc156f0840d78029b8d349e0feca98c12b9c4ce3404a6b3' 'n8nio/n8n:2.46.6@sha256:8602f5dfacbe2c931eeafd010de0e68182310e9f309921e780ab69182e92b3ae' 'caddy:2.11.9@sha256:c2aeffaeb80d5403e9c8d1d908b50e191aad204e16129f991ae357ada642094d'; do
  docker image inspect "$selected_image" --format '{{.RepoDigests}}' || docker pull "$selected_image"
done

Official tag/digest presence was verified against known registries at draft; you verify same selected image at execution: “pull worked” does not prove app compatibility.

Compose: private database, internal n8n, public HTTPS only

Postgres/n8n use isolated backend; n8n/Caddy use web. Caddy publishes the only host ports 80/443. The other two use expose, not ports; no public database, workflow backend, privileged socket or default-local model. Named volumes hold DB, n8n settings, proxy certificates. New image versions need no blind random .n8n folder.

Pin examples below are registry-checked on 2026-11-05, with no claim that tag-associated manifest always remains immutable. Apply fresh release/digest verification before use.

name: aixvps-n8n

services:
  postgres:
    image: postgres:17.11@sha256:748a18a54d3842d43bc156f0840d78029b8d349e0feca98c12b9c4ce3404a6b3
    restart: unless-stopped
    environment:
      POSTGRES_DB: ${POSTGRES_DB:?set non-secret database name}
      POSTGRES_USER: ${POSTGRES_USER:?set non-secret database user}
      POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password
      PGDATA: /var/lib/postgresql/data/pgdata
    secrets: [postgres_password]
    volumes:
      - postgres_data:/var/lib/postgresql/data
    expose: ["5432"]
    networks: [backend]
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U \"$$POSTGRES_USER\" -d \"$$POSTGRES_DB\" -h 127.0.0.1"]
      interval: 15s
      timeout: 10s
      retries: 8
      start_period: 30s
  n8n:
    image: n8nio/n8n:2.46.6@sha256:8602f5dfacbe2c931eeafd010de0e68182310e9f309921e780ab69182e92b3ae
    restart: unless-stopped
    init: true
    depends_on:
      postgres:
        condition: service_healthy
    environment:
      DB_TYPE: postgresdb
      DB_POSTGRESDB_HOST: postgres
      DB_POSTGRESDB_PORT: "5432"
      DB_POSTGRESDB_DATABASE: ${POSTGRES_DB:?set same database name}
      DB_POSTGRESDB_USER: ${POSTGRES_USER:?set same database user}
      DB_POSTGRESDB_PASSWORD_FILE: /run/secrets/postgres_password
      DB_POSTGRESDB_SCHEMA: public
      N8N_ENCRYPTION_KEY_FILE: /run/secrets/n8n_encryption_key
      N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: "true"
      N8N_HOST: ${N8N_DOMAIN:?set actual public hostname not localhost}
      N8N_EDITOR_BASE_URL: https://${N8N_DOMAIN:?set actual public hostname}
      N8N_WEBHOOK_URL: https://${N8N_DOMAIN:?set actual public callback}/
      N8N_PROTOCOL: https
      N8N_LISTEN_ADDRESS: 0.0.0.0
      N8N_PORT: "5678"
      N8N_PROXY_HOPS: "1"
      N8N_PATH: /
      N8N_PUSH_BACKEND: websocket
      EXECUTIONS_MODE: regular
      EXECUTIONS_TIMEOUT: ${EXECUTIONS_TIMEOUT:-900}
      EXECUTIONS_TIMEOUT_MAX: ${EXECUTIONS_TIMEOUT_MAX:-900}
      EXECUTIONS_DATA_PRUNE: "true"
      EXECUTIONS_DATA_MAX_AGE: ${EXECUTIONS_DATA_MAX_AGE:-336}
      EXECUTIONS_DATA_PRUNE_MAX_COUNT: ${EXECUTIONS_DATA_PRUNE_MAX_COUNT:-10000}
      N8N_CONCURRENCY_PRODUCTION_LIMIT: ${N8N_CONCURRENCY_PRODUCTION_LIMIT:-5}
      GENERIC_TIMEZONE: ${GENERIC_TIMEZONE:?set IANA zone, e.g. America/Los_Angeles}
      TZ: ${GENERIC_TIMEZONE:?same timezone}
      N8N_DIAGNOSTICS_ENABLED: ${N8N_DIAGNOSTICS_ENABLED:-false}
    secrets: [n8n_encryption_key, postgres_password]
    volumes:
      - n8n_data:/home/node/.n8n
    expose: ["5678"]
    networks: [backend, web]
    security_opt: [no-new-privileges:true]
    healthcheck:
      test: ["CMD-SHELL", "wget -q -O /dev/null --timeout=5 http://127.0.0.1:5678/healthz/readiness"]
      interval: 15s
      timeout: 10s
      retries: 8
      start_period: 60s
  caddy:
    image: caddy:2.11.9@sha256:c2aeffaeb80d5403e9c8d1d908b50e191aad204e16129f991ae357ada642094d
    restart: unless-stopped
    depends_on:
      n8n:
        condition: service_healthy
    environment:
      N8N_DOMAIN: ${N8N_DOMAIN:?set exact DNS name}
      TLS_EMAIL: ${TLS_EMAIL:?set monitored certificate contact}
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    networks: [web]

volumes:
  postgres_data:
  n8n_data:
  caddy_data:
  caddy_config:

secrets:
  postgres_password:
    file: ./secrets/postgres_password
  n8n_encryption_key:
    file: ./secrets/n8n_encryption_key

networks:
  backend:
    internal: true
  web:

Both n8n and Postgres use current supported file-backed secrets. Host mode 600 does not guarantee a container UID can read them; inspect actual effective file UID/read behavior at launch and use Docker Compose long-syntax secret UID/GID/mode only as required. Never make it world-readable to fix permissions. Current N8N_BLOCK_ENV_ACCESS_IN_NODE=true can restrict workflows’ environment access; enable deliberately if needed and test unaffected approved runtime.

One real domain and one HTTPS route

Create /opt/aixvps/n8n/Caddyfile. Caddy redirects HTTP to managed TLS and forwards one real domain to n8n, preserving host and supporting supported WebSockets. One proxy is the matching N8N_PROXY_HOPS=1; upstream host/protocol get reconciled if a different load-balancer-to-origin flow is chosen.

{
    email {$TLS_EMAIL}
}

{$N8N_DOMAIN} {
    encode zstd gzip
    reverse_proxy n8n:5678 {
        header_up X-Forwarded-Proto {scheme}
    }
    log {
        output stdout
        format filter {
            wrap console
            fields {
                request>uri query {
                    actions redact
                }
            }
        }
    }
}

The domain must have a deliberate correct A and optional valid AAAA to this intended server. Confirm new host public 80/443 allowed by correct Vultr Firewall/host layer. This does not set DNS or a root-domain redirection for an existing website. Caddy reverse proxy env expansions/forwarded protocol and the explicit query-redaction are intentional; change only after understanding auth/query implications.

Validate actual Caddy config without competing proxy listener:

cd /opt/aixvps/n8n || exit 1
set -a; . ./.env; set +a
docker compose run --rm --no-deps -T \
  -e N8N_DOMAIN="$N8N_DOMAIN" -e TLS_EMAIL="$TLS_EMAIL" \
  caddy caddy validate --config /etc/caddy/Caddyfile

This loads container, not a running web endpoint; verify actual HTTPS certificate and backend response after creation.

Canonical URL, stable secrets, resource bounds and retention

Configuration Current official fact at source review Reference decision
Editor N8N_HOST, N8N_PROTOCOL=https, N8N_EDITOR_BASE_URL, root N8N_PATH=/ Real domain and one subdomain/root path; not localhost or stripped prefix
Webhook N8N_WEBHOOK_URL sets base; WEBHOOK_URL is deprecated 2.35 Current N8N_WEBHOOK_URL=https://domain/, covers test/prod callbacks
Forwarding N8N_PROXY_HOPS default 0 Exactly one chained proxy 1 in this concrete architecture
Scheduling GENERIC_TIMEZONE for schedules, TZ for scripts/display Same actual IANA zone, not a copied timezone promise
Encryption N8N_ENCRYPTION_KEY or file input, default random on first launch Same secret every restart + independent protected source; no accidental rotation
Concurrency N8N_CONCURRENCY_PRODUCTION_LIMIT, default -1 regular AiXVPS conservative example 5, not measured throughput or vendor default
Pruning Current defaults true, 336 hours, 10000 count Defaults mirrored explicitly; choose your own based on RPO/regulatory/disk need and preserve required data
Timeout EXECUTIONS_TIMEOUT=-1, max 3600 default Editor examples 900 each; still set outbound retry/timeouts separately
Diagnostics Deployment doc defaults true false privacy choice; review official telemetry notes and feature tradeoffs

Stack uses regular one-host mode intentionally. Worker queue-level concurrency and this main production limit are separate. Concurrency limits can bound simultaneous tasks, not total external API spend; scope credentials/API budgets and retry behavior accordingly in each workflow.

After first owner setup use a unique protected password, supported 2FA, narrow n8n API keys and authenticated callback routes. Read hardening guide, review Code-node untrusted execution/environment and credential scopes; no unrestricted default owner accounts. file-based config is official behavior, but each pin still needs runtime verification.

Start and prove restart persistence before real integrations

Run only after completed backup destination, DNS and host/exposure checks:

cd /opt/aixvps/n8n || exit 1
set -a; . ./.env; set +a
docker compose config --quiet
docker compose up -d
timeout 180 bash -c 'until \
  docker compose exec -T n8n wget -q -O /dev/null http://127.0.0.1:5678/healthz/readiness; do
    sleep 3
  done'
docker compose ps
docker compose logs --no-color --tail=80 postgres n8n caddy
curl --fail --silent --show-error --max-time 15 \
  "https://${N8N_DOMAIN}/healthz/readiness" -o /dev/null
printf '%s\n' 'Readiness URL responded. Continue owner/workflow/decrypt tests before relying on it.'

Health includes DB-ready/readiness criterion through n8n; running, HTTP-open and Postgres pg_isready individually do not prove it. Read concise redacted logs for deprecation/permission/database failures. Do not dump whole environment back into browser/shell artifacts. If wget fails because selected image has no tool, stop and edit health to actual supported endpoint/client verified in runtime manifest. The example requires its image probe earlier, not assumed present.

Before meaningful integrations:

  1. Visit HTTPS editor; create correct owner unique credential and supported 2FA; sign out/back in.
  2. Create one synthetic, no-external-action Webhook+Set workflow; verify test callback path works through expected public TLS route.
  3. Confirm proper production callback /webhook/… carries authentication when activated; its URL is not a guess or localhost callback.
  4. Create a synthetic n8n credential value using protected UI/password manager, with no real integration send/action. Store a workflow that uses it.
  5. docker compose restart n8n and revalidate health; open the actual stored workflow/credential/use it only harmlessly. Wrong/missing key or unexpected newly generated config means failed acceptance, even if Postgres queries return rows.

Then perform exact application export/restore. Prevent restored jobs/webhooks/external messages in rehearsal.

cd /opt/aixvps/n8n || exit 1
docker compose restart n8n
docker compose ps n8n postgres
ss -lntup

Acceptance receipt: selected/digested images and release URLs, supported Postgres rank, actual UID/config mounts, backend/db health and certificate/redirect check, of all expected listeners only intended published, successful synthetic workflow plus credential decrypt through restart, protected key/escrow locations not values, RPO/application restore evidence, resource baseline and relevant provider/API costs. Any one missing gates valuable workflow traffic.

Monitor health, execution history and actual costs

Owner’s recurring review checks safe redacted logs, failing executions/retry ladder, external quota/rate results, DB/host resources/disk/inodes/n8n volume, key available/no unrecognized secret changes, next restore/backup/evidence age, certificate and account security, egress/tax/provider invoice versus ledger. Billing monitoring does not substitute for UI/application check nor guarantee alerts.

Prune actual EXECUTIONS_DATA by legal/RPO/audit/storage policy; compact row-count alone does not prove disk reclaimed or key export. Do not delete evidence needlessly. Queue/db/data shift may be required before larger host if measured cause is execution history unbounded rather than lack of RAM.

cd /opt/aixvps/n8n || exit 1
set -a; . ./.env; set +a
docker compose ps
docker compose logs --no-color --since=24h n8n postgres caddy > /root/aixvps-n8n-review.log
chmod 600 /root/aixvps-n8n-review.log
free -h; df -hT; df -ih
docker compose exec -T postgres psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" \
  -c 'SELECT current_database(), current_timestamp;'

Logs/query could contain workflow data and are root-only. SELECT tests connectivity not historical data or future recovery. Cloud Compute transfer behavior is described only in scoped egress worksheet, not universal to VX1/mixed backup traffic.

Upgrade through supported pins and exact-cutoff recovery

Capture actual old image digests, DB Major, compatible selected new app/DB pin, current config/key location, custom nodes and workflow integration dependencies. Read release notes, upgrade guidance and Postgres current support; choose maintenance consent. A Marketplace OS/app template redeploy is not a valid n8n updater.

Routine in-place update still requires a current application-consistent capture and precise latest included file/transaction. For any destructive rollback/source retirement:

  1. CONTROL WRITES / DEFINE CUTOFF: choose known maintenance window and stop executor/webhook writers or controlled delta approach through the final cutoff. Freeze old writer remains until final exact acceptance.
  2. FINAL EXPORT: use Billing export protocol of DB/files/stable key/config/version and required final deltas. Meaningful-content checks and independent encrypted copy happened.
  3. VERIFY FINAL-CUTOFF RESTORE: restore exact cutoff on disposable application-compatible target, externally disabled; owner/login/read/decrypt/test workflow/restart/latest marker passes. A pre-maintenance rehearsal does not count as final-cutoff proof. Old writer resumes → collect delta and repeat final export/restored proof.
  4. Change only chosen supported image/tag/digest in same discovered config; docker compose pull and up -d, preserving exact identities/named volumes. Observe database migrations; owner login, callback/API behavior, known credential decrypt, workflow/history/restart in timeline. Source stays reliable until success.
  5. On failure, preserve logs/DATA/new recovered final cutoff; use current supported migration rollback recovery, not container-downgrade assumption and not down -v. Do not restore a stale backup over valid later writes.

An in-place upgrade not destroying source may accept the new instance through normal supported verification after current backup, but initial backup must account for ongoing writers. If its rollback overwrites data or you retire old hardware, this same freeze-finish-restore gate applies. Use migration/storage decisions for the product-specific recovery context.

N99panel: separate third-party operations and licensing

The official Marketplace N99panel listing is third-party: Debian 13, displayed 0.238 and explicit N8N support. Advertised 14-day trial then $4.99/month plus compute differs from other published regional terms and adds software support risks. It is neither Vultr-managed n8n nor unlimited app included in hourly host price.

Before choosing it read N99panel documentation. Wait marker /var/rt-n99p/installation_completed; securely retrieve admin credential from /root/n99p-admin.txt only authorized owner root and move to password manager, no public log. Current listing separates DNS/mail server assumption and licensing support, not inferred free resources.

Panel path requires actual panel/n8n versions, app/database/custom-node configuration/storage, selected domain/TLS/admin restrictions, vendor update/backup/restore paths and current contract/trial/renew/cancel evidence. Never run that stack alongside this competing Compose. Retain its external export and apply separate license audit; destroy of compute is not enough.

Failure triage, scale and safe handoff

Symptom/evidence Inspect first Bounded safe response
DB unhealthy Storage/resource, image service logs, actual secret location/UID, schema/version/mount Keep fail-safe and fix actual cause. No public 5432 or wipe
Empty UI / decryption fails Preserved key, config or named volume path, current logs showing mismatch Freeze writers; acquire exact protected key/state and test read; never rekey/reset or ‘down -v’
wget missing/compose health fail Actual image task prerequisites/client and path current environment Supported replace matching capability, validate config not fake boolean healthy
502/readiness Cloud-init/config; n8n migration/DB and Caddy network logs, listeners Correct explicit n8n-proxy connection; do not publish backend/alter N8N_PATH randomly
TLS fail Own A/AAAA, 80/443 allowed, other proxy, Caddy certificate log Fix actual route/certificate contact; no disable-firewall/skip-TLS workaround
Callback wrong Current N8N_WEBHOOK_URL, endpoint, authentication, proxies/deploy, client allowed sources Change precise URL/path; test harmless authorization with no real action
OOM/disk exhaustion Execution/binary/DB growth, concurrency, custom code/runtime/max retention Export needed history, constrain retention/batches/timeouts/concurrency or resize/split cause
Update fails Exact tag/digest/release migration, DB health, host storage, credential preservation Preserve current complete state + current off-host; supported current backup recovery, no snapshot-over-production gamble
Admin/model/public port appears Actual docker/host/Vultr layers, effective all-address listener, auth Restrict route/source intentionally; retain admin access and fix workflow backend instead

Diagnose before scaling. External model may be right if n8n stable but model memory grows; DB split may address persistent storage/independent DB support; queue mode requires its fuller architecture. All leave restore exact last writes, shift canonical API/workflows, keep old read-only and destroy only accepted target then audit all leftover resources.

Owner handoff should include application/version source, canonical HTTPS plus workflow scope, expected external endpoints, API/budget/supply boundaries, fragmentation/payload/logging choices, current encryption/key/control-plane owner, app backup/schema/final state and reproducible restore evidence. “Port responds” alone never completes n8n operation.