Monthly billing is still an automatic commitment
Record the actual invoice, service ID, renewal date and stored-payment behavior before relying on this guide. The public Terms, VPS FAQ and backup-availability guide were checked 2026-11-05; your account controls and invoice govern the specific purchase.
| Event | Public rule | Operational response |
|---|---|---|
| Linux KVM price | $3/slice/month; 2 GB/40 GB/2 TB per slice | Twelve-month arithmetic is not annual prepay or discount |
| Storage VPS | $3/slice/month; 1 TB SATA/2 GB/2 TB per slice | Separate root product, not shared Storage Hosting |
| Windows Hyper-V | $5/slice; two-slice minimum | Separate license/version/recovery questions |
| Renewal | Automatic under Terms; invoices due when generated with invoice-specific grace | Keep payment/cancellation records; nonpayment is not exit |
| Cancellation | At least three days before automatic renewal, otherwise after next renewal | Set earlier reminder and verify exact service/date |
| Older cancellation tutorial | Immediate shutdown within five minutes; “in most cases” data reactivation up to 30 days | Older vendor statement, not retention guarantee or backup |
| VPS refunds | No trial/money-back; Terms exclude VPS refunds after service starts | Do not buy expecting routine refund |
| Backup default | VPS/dedicated do not run backups by default | Create independent application-consistent exports |
Prepaid account credit may cover future invoices; it does not change the monthly rate. Additional justified IPs currently cost $3/IP/month, DirectAdmin lists an additional $8/month VPS license on its path, and cPanel/add-on totals are checkout-dependent. Tax, currency, template, stock and final total were not observable without login. The total-cost tool can normalize these items with model APIs and external backups.
A shared-hosting 30-day refund statement does not transfer to VPS. A discretionary support review is not a refund entitlement. Disabling a card, logging out or allowing suspension is not clean cancellation and does not preserve data.
Recovery layers: similar names, different guarantees
| Layer | Product scope and documented behavior | What it does not prove |
|---|---|---|
| Manual snapshot | KVM/Rapid Deploy through control panel | Automatic schedule, application consistency or survival of array failure |
| Manual portal backup | Guide says cloud storage for 45 days for eligible services up to 1 TB | Current account quota, restore target, >1 TB behavior or scheduled automation |
| Hyper-V | Guide says described portal backup is unsupported | Do not copy KVM instructions to Windows |
| Storage Hosting | Non-root SFTP/Rsync/DirectAdmin destination; finite transfer | Root daemon or backup of the storage service itself |
| Storage VPS | Root-controlled SATA capacity | Fast database storage or independent copy by itself |
| Independent Restic | Encrypted repository on infrastructure you control | Application consistency unless export/writers are handled correctly |
The 2025-12-19 backup guide is the source for the 45-day/up-to-1-TB statement. It also says snapshots may not survive array failure and a disk image can require full restore to extract one database. Account-specific backup quota, retention, restore destination and traffic accounting remain unknown. Ask support before depending on them.
Write three objectives before implementation: RPO (how many minutes/hours of workflows you can lose), RTO (how long provisioning and restore may take), and retention (daily/weekly/monthly points plus who may delete them). The public network/power SLA is not an application or data-recovery SLA.
Take a portal rollback point without stopping there
For an eligible KVM/Rapid Deploy service, follow current portal manual snapshot/backup controls. Exact clicks were not live-tested, so verify service identity and read the current warning rather than inventing a screenshot path.
- Complete the checked application export below first, or pause writers before a crash-consistent image with full awareness of that limitation.
- Confirm selected VPS, region, disk and current time.
- Create the manual control and record completion—not merely requested state—plus date, size and retention shown by your account.
- Ask/verify whether restore replaces the whole disk, which target it uses, and whether 45-day/up-to-1-TB applies.
- Keep independent Restic recovery on another failure domain. A provider snapshot cannot decrypt n8n credentials without the preserved key and cannot prove custom nodes/external data were captured.
For Hyper-V, obtain a separate supported recovery answer. For Storage Hosting, plan a second copy because the guide says that product itself has no backups.
Prepare an independent encrypted repository
The reference uses Restic SFTP to a destination you control, such as separate Storage Hosting or another provider. Keeping source and destination in the same account can simplify billing but does not remove account compromise/cancellation risk. Prefer off-provider or independently protected destination with versioning/immutability where justified.
Install Restic using current official installation documentation; verify package/binary provenance and version. Do not execute a copied remote shell pipeline. Configure SFTP using a dedicated backup user/key without revealing private-key contents.
install -m 700 -d /root/.config/interserver-recovery
read -r -s -p 'New independent Restic repository password: ' RESTIC_PASSWORD; printf '\n'
printf '%s\n' "$RESTIC_PASSWORD" > /root/.config/interserver-recovery/restic-password
unset RESTIC_PASSWORD
chmod 600 /root/.config/interserver-recovery/restic-password
cat > /root/.config/interserver-recovery/restic.env <<'EOF'
RESTIC_REPOSITORY=sftp:backupuser@storage.example.net:/restic/interserver-01
RESTIC_PASSWORD_FILE=/root/.config/interserver-recovery/restic-password
EOF
chmod 600 /root/.config/interserver-recovery/restic.env
Replace the placeholder and verify remote path semantics for your actual SFTP service. Store a second offline repository-password copy. The unattended mode-600 password file plus SFTP access can decrypt backups; protect and rotate both deliberately.
Initialize once after verifying the intended empty destination:
set -Eeuo pipefail
set -a
. /root/.config/interserver-recovery/restic.env
set +a
restic init
restic snapshots
If the repository exists, stop and verify; do not overwrite a shared repository.
Discover the complete n8n recovery set
For the custom reference Compose stack, set N8N_HOME to the discovered mountpoint of aixvps-n8n_n8n_data, not the Postgres volume. Required EXTRA_PATHS include the reference’s secrets/ directory, Caddyfile, and every actual custom-node, binary/external-data or other state path outside N8N_HOME. () is not complete.
docker volume inspect aixvps-n8n_n8n_data --format '{{.Mountpoint}}'
docker volume inspect aixvps-n8n_postgres_data --format '{{.Mountpoint}}'
find /opt/aixvps/n8n -maxdepth 2 -type f -printf '%p\n'
Do not print secret contents. Create a separate manually reviewed, redacted Compose/unit recovery definition with versions and non-secret paths. Renaming a raw .env or secrets file does not redact it. The real secret directory is captured separately as required recovery data and encrypted by Restic.
For another existing Compose or native deployment, discover actual project/units, full .n8n state, writers/workers, database and external data. Do not assume /root/.n8n, /opt/ai, service names or authentication. Stop if topology is unclear.
Fail-closed export script
This is an editorial procedure, not an InterServer/n8n official script. Test on disposable fixtures. Replace every REPLACE_ value, set mode 0700, and run as root. It pauses verified n8n writers, copies state and required external paths, dumps Postgres to restrictive .partial, verifies nonempty output, hashes the manifest and restarts only writers it stopped. Every unsuccessful export triggers guarded plaintext-stage cleanup and preserves nonzero status.
#!/usr/bin/env bash
set -Eeuo pipefail
umask 077
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# REQUIRED: replace all values for the discovered deployment.
DEPLOYMENT='compose' # compose OR systemd
N8N_HOME='/srv/REPLACE_N8N_STATE' # full .n8n-equivalent state directory
DB_TYPE='postgres' # sqlite OR postgres
PROJECT_DIR='/srv/REPLACE_COMPOSE_PROJECT'
COMPOSE_FILE='compose.yml' # actual discovered filename
WRITER_SERVICES=(n8n) # every n8n writer; add workers if present
N8N_UNITS=(n8n.service) # native only; every writer unit
POSTGRES_SERVICE='postgres' # Compose Postgres only
POSTGRES_USER='postgres'
POSTGRES_DB='n8n'
EXPORT_ROOT='/var/lib/interserver-recovery'
# Absolute source paths outside N8N_HOME that recovery requires.
# Include discovered binary/custom-node/external-storage paths; () is not sufficient.
EXTRA_PATHS=(
'/srv/REPLACE_N8N_BINARY_OR_EXTERNAL_DATA'
'/srv/REPLACE_N8N_CUSTOM_NODES'
)
# Non-secret deployment definition and manually reviewed REDACTED config template.
# Renaming a raw .env/secrets file does NOT make it safe; inspect every line first.
DEPLOYMENT_ARTIFACT='/srv/REPLACE_REDACTED_COMPOSE_OR_UNIT_DEFINITION'
[[ $EUID -eq 0 ]] || { echo 'Run as root.' >&2; exit 1; }
[[ $N8N_HOME == /* && $N8N_HOME != / && -d $N8N_HOME ]]
[[ $EXPORT_ROOT == /* && $EXPORT_ROOT != / ]]
[[ $DB_TYPE == sqlite || $DB_TYPE == postgres ]]
[[ $DEPLOYMENT == compose || $DEPLOYMENT == systemd ]]
[[ ${#WRITER_SERVICES[@]} -gt 0 && ${#N8N_UNITS[@]} -gt 0 ]]
[[ $N8N_HOME != *REPLACE_* && $PROJECT_DIR != *REPLACE_* ]] || {
echo 'Replace discovery placeholders first.' >&2; exit 1;
}
[[ ${#EXTRA_PATHS[@]} -gt 0 ]] || { echo 'Extra recovery paths are required.' >&2; exit 1; }
[[ ${ARTIFACT_REDACTION_REVIEWED:-} == 'I_REVIEWED_EVERY_LINE_NO_SECRETS' ]] || {
echo 'Set ARTIFACT_REDACTION_REVIEWED only after line-by-line secret review.' >&2; exit 1;
}
[[ $DEPLOYMENT_ARTIFACT != *REPLACE_* && -f $DEPLOYMENT_ARTIFACT ]] || {
echo 'Supply a redacted deployment/config artifact; never a raw secret .env.' >&2; exit 1;
}
[[ ${DEPLOYMENT_ARTIFACT##*/} != .env ]] || { echo 'Refusing raw .env artifact.' >&2; exit 1; }
for extra in "${EXTRA_PATHS[@]}"; do
[[ $extra == /* && $extra != / && $extra != *REPLACE_* && -e $extra ]] || {
echo "Missing/unresolved required recovery path: $extra" >&2; exit 1;
}
[[ $extra != "$N8N_HOME" && $extra != "$N8N_HOME"/* ]] || {
echo "Extra path duplicates N8N_HOME: $extra" >&2; exit 1;
}
done
for cmd in cp find sort sha256sum xargs; do command -v "$cmd" >/dev/null; done
install -d -m 700 "$EXPORT_ROOT"
exec 9>"$EXPORT_ROOT/.export.lock"
flock -n 9 || { echo 'Another export is running.' >&2; exit 1; }
STAGE=$(mktemp -d "$EXPORT_ROOT/export.XXXXXXXX")
STOPPED=0
EXPORT_SUCCESS=0
CLEANUP_NEEDS_ATTENTION=0
safely_remove_failed_stage() {
local stage_parent
stage_parent=$(dirname -- "$STAGE")
[[ $stage_parent == "$EXPORT_ROOT" && $STAGE == "$EXPORT_ROOT"/export.* && $STAGE != */../* ]] || {
echo "CRITICAL: unsafe stage path; sensitive data may remain at $STAGE" >&2
CLEANUP_NEEDS_ATTENTION=1
return 1
}
if ! chmod -R u+rwX -- "$STAGE" || ! rm -rf --one-file-system -- "$STAGE"; then
echo "CRITICAL: failed-stage cleanup incomplete; sensitive data may remain at $STAGE" >&2
CLEANUP_NEEDS_ATTENTION=1
return 1
fi
if [[ -e $STAGE ]]; then
echo "CRITICAL: failed stage still exists at $STAGE" >&2
CLEANUP_NEEDS_ATTENTION=1
return 1
fi
printf '%s\n' 'Failed plaintext staging directory removed.' >&2
}
compose() (
cd "$PROJECT_DIR"
docker compose -f "$COMPOSE_FILE" "$@"
)
writers_running() {
local unit service running
if [[ $DEPLOYMENT == compose ]]; then
running=$(compose ps --status running --services)
for service in "${WRITER_SERVICES[@]}"; do
grep -Fxq "$service" <<<"$running" || return 1
done
else
for unit in "${N8N_UNITS[@]}"; do systemctl is-active --quiet "$unit" || return 1; done
fi
}
pause_writers() {
STOPPED=1
if [[ $DEPLOYMENT == compose ]]; then
compose stop "${WRITER_SERVICES[@]}"
else
systemctl stop "${N8N_UNITS[@]}"
fi
}
resume_writers() {
if [[ $DEPLOYMENT == compose ]]; then
compose up -d "${WRITER_SERVICES[@]}"
else
systemctl start "${N8N_UNITS[@]}"
fi
}
on_exit() {
local rc=$?
trap - EXIT
find "$STAGE" -type f -name '*.partial' -delete || rc=1
if [[ $STOPPED -eq 1 ]]; then
if ! resume_writers; then
echo 'CRITICAL: writer restart failed; inspect before enabling traffic.' >&2
rc=1
fi
fi
if [[ $EXPORT_SUCCESS -ne 1 ]]; then
safely_remove_failed_stage || rc=1
elif [[ $CLEANUP_NEEDS_ATTENTION -eq 1 ]]; then
rc=1
fi
exit "$rc"
}
trap on_exit EXIT
writers_running || {
echo 'Not all configured writers were running; no stop/copy attempted.' >&2
exit 1
}
pause_writers
mkdir -p "$STAGE/n8n-home" "$STAGE/extra" "$STAGE/deployment"
cp -a "$N8N_HOME/." "$STAGE/n8n-home/"
index=0
: >"$STAGE/EXTRA-PATHS.tsv"
for extra in "${EXTRA_PATHS[@]}"; do
index=$((index + 1))
dest=$(printf '%s/extra/%03d' "$STAGE" "$index")
cp -a -- "$extra" "$dest"
printf '%03d\t%s\n' "$index" "$extra" >>"$STAGE/EXTRA-PATHS.tsv"
done
cp -a -- "$DEPLOYMENT_ARTIFACT" "$STAGE/deployment/recovery-definition"
if [[ $DB_TYPE == sqlite ]]; then
[[ -f $STAGE/n8n-home/database.sqlite ]] || {
echo 'SQLite database absent at discovered state path.' >&2; exit 1;
}
elif [[ $DB_TYPE == postgres ]]; then
mkdir -p "$STAGE/database"
DUMP="$STAGE/database/n8n.dump"
PARTIAL="$DUMP.partial"
if [[ $DEPLOYMENT == compose ]]; then
compose exec -T "$POSTGRES_SERVICE" \
pg_dump --username="$POSTGRES_USER" --dbname="$POSTGRES_DB" --format=custom \
>"$PARTIAL"
else
runuser -u postgres -- \
pg_dump --username="$POSTGRES_USER" --dbname="$POSTGRES_DB" --format=custom \
>"$PARTIAL"
fi
[[ -s $PARTIAL ]] || { echo 'pg_dump produced an empty file.' >&2; exit 1; }
mv -- "$PARTIAL" "$DUMP"
fi
# Non-secret hand-written inventory; create before running if desired.
cat >"$STAGE/RESTORE-NOTES.txt" <<EOF
Created: $(date --iso-8601=seconds)
Deployment: $DEPLOYMENT
Database: $DB_TYPE
Record exact n8n/DB image versions privately; EXTRA-PATHS.tsv maps required extra data.
EOF
(
cd "$STAGE"
find . -type f ! -name 'SHA256SUMS*' -print0 | sort -z |
xargs -0 -r sha256sum >SHA256SUMS.tmp
[[ -s SHA256SUMS.tmp ]]
mv SHA256SUMS.tmp SHA256SUMS
)
[[ -s $STAGE/SHA256SUMS ]]
resume_writers
STOPPED=0
EXPORT_SUCCESS=1
printf 'EXPORT_DIR=%s\n' "$STAGE"
printf '%s\n' 'Verify application readiness before sending this staging directory off-site.'
Set DEPLOYMENT=compose, DB_TYPE=postgres, PROJECT_DIR=/opt/aixvps/n8n, COMPOSE_FILE=compose.yml, WRITER_SERVICES=(n8n), POSTGRES_SERVICE=postgres, POSTGRES_USER=n8n, and POSTGRES_DB=n8n for the reference. Add every actual worker to WRITER_SERVICES. A SQLite/native branch is supported only when discovery proves that topology. The reference’s Postgres named volume is not copied as a live-database substitute.
EXTRA-PATHS.tsv maps numbered staged copies to original absolute paths. deployment/recovery-definition contains the reviewed redacted artifact. The script cannot know whether your inventory is semantically complete: the restore drill must test every credential, custom node and external-data integration.
If dump/copy fails, the source remains unchanged, the trap attempts writer recovery, and no successful stage is left for Restic. If writer restart fails, keep external traffic disabled until manual verification. If cleanup reports a critical unresolved stage, verify that exact path and secure/remove it manually; do not run a broad delete.
Send the exact checked stage off-site
After confirming application readiness, source repository settings without printing secrets and back up the reported stage:
set -Eeuo pipefail
set -a
. /root/.config/interserver-recovery/restic.env
set +a
EXPORT_DIR='/var/lib/interserver-recovery/export.REPLACE_WITH_REPORTED_NAME'
test -d "$EXPORT_DIR"
test -s "$EXPORT_DIR/SHA256SUMS"
(
cd "$EXPORT_DIR" || exit 1
sha256sum -c SHA256SUMS >/dev/null
)
restic backup "$EXPORT_DIR" --tag interserver-n8n --tag manual || {
rc=$?
printf 'Restic backup FAILED (exit %s); do not mark this run successful.\n' "$rc" >&2
exit "$rc"
}
restic snapshots --latest 1
Restic exit 3 (some source data unreadable) is still failure. Never schedule cancellation or retention deletion after a partial backup.
Verify the repository initially and regularly:
restic check
# More expensive full-data verification; schedule deliberately:
restic check --read-data
check validates repository structure/data, not n8n’s ability to start or decrypt credentials. Only an isolated application restore closes that gap.
Retention is a deliberate deletion operation. After multiple proven restore points, a possible editorial policy is:
restic forget --prune --keep-daily 7 --keep-weekly 4 --keep-monthly 6 || {
rc=$?
printf 'Retention/prune FAILED (exit %s); investigate before retrying.\n' "$rc" >&2
exit "$rc"
}
restic check
Confirm legal retention, capacity and immutable-copy strategy first. Do not combine first backup and first prune. Remove local plaintext staging only after application readiness, repository verification and no immediate restore-drill need; verify the exact stage is under /var/lib/interserver-recovery/ before deletion.
Isolated restore: no public webhooks, no production overwrite
Use a disposable host/VM with no production data. Install the same recorded n8n/Postgres/Caddy versions and Restic. Pull required images before removing network access. Keep production DNS, queues and triggers unchanged.
install -m 700 -d /var/tmp/interserver-restore
set -Eeuo pipefail
set -a
. /root/.config/interserver-recovery/restic.env
set +a
restic snapshots
restic restore latest --target /var/tmp/interserver-restore || {
rc=$?
printf 'Restore FAILED (exit %s).\n' "$rc" >&2
exit "$rc"
}
RESTORED_SUMS=$(find /var/tmp/interserver-restore -type f -name SHA256SUMS -print -quit)
test -n "$RESTORED_SUMS"
RESTORED_DIR=$(dirname "$RESTORED_SUMS")
(
cd "$RESTORED_DIR" || exit 1
sha256sum -c SHA256SUMS
)
printf 'Verified staging restored at %s\n' "$RESTORED_DIR"
Hash agreement proves transfer integrity only. Next:
- Inspect
EXTRA-PATHS.tsv; require a corresponding staged entry for every source, including the secret directory, Caddyfile, custom nodes and binary/external data. Restore each numbered item only to an isolated intended path. - Inspect the redacted deployment definition and exact private version inventory. Create a new empty Postgres cluster of the matching major. Restore the custom dump with
pg_restoreand check its exit status/logs. - Place restored n8n home with original ownership/modes. Supply original encryption-key and database-password files at their isolated reference paths. Never regenerate the key or print it.
- Use an isolated Compose project/name and volumes. Create its backend network
--internal; publish no public n8n/Postgres port. Disable Caddy/public listeners for the drill or bind a temporary UI only to127.0.0.1through SSH. - Start on a network with no outbound internet. Verify readiness, owner access, credential decryption with a non-sending test, workflows, custom nodes, external/binary fixtures and expected non-sensitive DB counts. No email, schedule, queue or webhook egress.
- Stop the stack before changing network access. For later promotion, deactivate schedules/webhooks first and ensure old/new writers cannot run together.
For a SQLite/native branch, check restored state’s PRAGMA integrity_check; and original ownership/key before application testing. That does not replace credential/workflow verification.
Record snapshot ID, date, versions, RTO, verifier and missing artifacts. Repeat export/restore until every required integration passes. Retain a non-secret report before removing only the disposable environment.
Grow or migrate without two active writers
Slice arithmetic makes growth easy to estimate, not risk-free. Confirm resize price difference, stock, IP behavior, disk expansion and rollback with support/current portal. Preserve a pre-change checked export and independent restore point. More RAM does not create a GPU or guarantee better model speed.
For migration, overlap old and new service:
- Lower DNS TTL if supported. Build and harden the replacement; restore into isolation.
- Pause old writers and external producers. Run final export, Restic backup and repository check; confirm no later writes.
- Restore matching versions to the new host. Verify readiness, credentials, custom nodes, external data and controlled workflow with side effects disabled.
- Cut DNS/reverse proxy/webhook producers once. Resume only the new writer; monitor both privately.
- Keep old VPS intact through observation window and successful new-host backup/restore check.
- Export invoices and private inventory. Remove public old DNS only after traffic has moved.
If failure occurs before cutover and no new writes occurred, resume old writers deliberately. If new writes occurred, do not blindly resume old database; reconcile/export first.
Cancel last, with the current option read literally
- Complete and download the independent export; perform a fresh isolated restore. A portal backup or 30-day reactivation statement is not enough.
- Verify new service, one active writer, billing overlap and repository access without the old VPS.
- Record actual service ID, paid-through date, next automatic renewal and add-ons. Plan earlier than three-day cutoff.
- In current portal, distinguish immediate from end of term. The older tutorial describes shutdown within five minutes; current Terms govern renewal/cancellation timing. Read exact data-loss warning before confirming.
- Save ticket/request and cancellation confirmation. Verify service state and later billing; contact support if separate add-on or invoice remains.
- Only after retention/observation window, rotate/revoke old SFTP, API and repository access according to policy. Cancelling a Storage Hosting/VPS destination also destroys its role in your backup schedule.
If charged after a cancellation you believe was timely, provide service ID, invoice, confirmation and timestamp privately; do not publish account/secrets. A dispute is not a data-recovery mechanism.
Windows and storage exits need separate runbooks
Hyper-V lacks the described KVM portal-backup capability. Verify Windows license/version, supported export/recovery, application consistency and cancellation behavior independently. Do not execute Linux Docker/Restic path instructions as if Windows were KVM.
Shared Storage Hosting is non-root and has finite transfer. Confirm SFTP path semantics, quota and account cancellation. The alternate official VPS wording “Cloud Remote Backup Service” and “no transfer fees on the storage service” needs checkout clarification and must not be generalized to VPS bandwidth. Storage VPS gives root/SATA but still needs a second independent copy.
Official sources
InterServer Terms, VPS FAQ, backup availability, older cancellation tutorial, Storage Hosting, Storage VPS, SLA; Restic backup, restore, check, and n8n backup commands. Public pages checked 2026-11-05. Editorial boundaries live on About.