Skip to content

Backup / Restore Errors

ProxPanel backups are AES-256-GCM encrypted with a key derived from the license server’s per-customer DB password. The file format went through one revision (V1 → V2) in v1.0.208–209 to add an embedded license key for automatic cross-server restore. This page covers the five most common reasons a restore won’t run.

  • “Invalid backup file format” toast when restoring
  • “Decryption failed — wrong password or corrupted file”
  • pg_restore fails with server version: 16.x; pg_dump version: 14.x
  • Backup created but the scheduler never fires at the configured time
  • Restored database loads but with timezone-shifted timestamps
V1 (pre-2026-02-12):
PROXPANEL_ENCRYPTED_BACKUP_V1\n
[AES-256-GCM ciphertext]
V2 (current):
PROXPANEL_ENCRYPTED_BACKUP_V2\n
LICENSE_KEY=PROXP-XXXXX-XXXXX-XXXXX-XXXXX\n
[AES-256-GCM ciphertext]

The V2 header lets a different server fetch the source’s DB password from license.proxrad.com automatically. V1 backups can still be restored — but only if the operator manually enters the source license key into the Source License Key field on the restore modal.

  1. Confirm the file isn’t garbage. A valid backup is a binary file starting with one of the two ASCII headers.

    Terminal window
    head -c 35 /opt/proxpanel/backups/proisp_full_20260512_010000.proisp.bak

    You want to see PROXPANEL_ENCRYPTED_BACKUP_V2 or _V1. If you see something else (e.g. a SQL dump or random binary), the file is not a ProxPanel backup or is truncated.

  2. Look at the backup log.

    Terminal window
    docker logs proxpanel-api 2>&1 | grep -iE "backup|restore|decrypt" | tail -40
  3. Check the backup_logs table.

    Terminal window
    docker exec proxpanel-db psql -U proxpanel -d proxpanel -c \
    "SELECT id, schedule_name, filename, last_status, last_error, created_at \
    FROM backup_logs ORDER BY created_at DESC LIMIT 10;"
  4. Verify Postgres versions agree between dump and restore (see Cause 4).

The file doesn’t start with either V1 or V2 magic bytes. Either it’s not a ProxPanel backup, it’s been corrupted in transit, or it’s a partial write from an interrupted backup job.

Log line:

Restore: invalid encrypted backup format (first 30 bytes: '...')

Diagnostic:

Terminal window
# What's actually in the header?
xxd /opt/proxpanel/backups/SUSPECT.proisp.bak | head -2
# File size sanity check
ls -la /opt/proxpanel/backups/SUSPECT.proisp.bak

A valid backup for an install with 1000 subscribers is typically 5–50 MB. Anything under 1 KB is almost certainly truncated.

Fix:

  • Re-download the file (FTP / SCP / Cloud) — corruption in transit is common over FTP without binary mode.
  • Re-take the backup if the original is bad. The local /opt/proxpanel/backups/ directory has the source files; check backup_logs for the most recent successful job.

The header is right but the AES-GCM verification fails. The key is derived from the DB password; if the restoring server has a different password than the source, decryption can’t work.

Log line:

Restore: decryption failed - this backup may be from a different installation

Diagnostic:

Same-server restore

The current server’s DB password should decrypt its own backups. If it doesn’t, either the password changed (e.g. you regenerated secrets on the license server) or this backup is from a different server.

Terminal window
# Try the validate endpoint — it attempts decryption without restoring
curl -s -X POST http://localhost:8080/api/backups/SUSPECT.proisp.bak/validate \
-H "Authorization: Bearer $TOKEN" | jq

Cross-server restore

The backup is from a different installation. If V2, the license key is embedded — the handler will auto-fetch the source’s DB password from license.proxrad.com. If V1, you must enter the source license key manually.

Terminal window
# What format is it?
head -c 35 /opt/proxpanel/backups/SUSPECT.proisp.bak
# What license key is embedded? (V2 only)
head -c 200 /opt/proxpanel/backups/SUSPECT.proisp.bak | grep -ao "LICENSE_KEY=[A-Z0-9-]*"

Fix:

  1. V2 backup, restore failed: confirm the embedded license key matches an active license on license.proxrad.com:

    Terminal window
    curl -s -X POST "https://license.proxrad.com/api/v1/license/validate" \
    -H "Content-Type: application/json" \
    -d '{"license_key":"PROXP-XXXXX-XXXXX-XXXXX-XXXXX","server_ip":"YOUR_IP"}' | jq '.status'

    If the license is revoked or unknown, the password fetch will 404 and the restore can’t proceed.

  2. V1 backup: open the restore modal in the UI → Source License Key field → paste the source server’s license key (not the current server’s). The handler fetches the DB password matching that key and decrypts with it.

  3. Secrets regenerated: if the license-server operator regenerated the customer’s DB password (admin → license → Regenerate Secrets), every backup taken before that point is unrecoverable on this server without the old password. Either restore the old password, or take a fresh backup post-regenerate as the new baseline.

Cause 3 — Wrong source license key for V1

Section titled “Cause 3 — Wrong source license key for V1”

You’re restoring a V1 backup from Server A onto Server B and you typed Server B’s license key. Decryption fails because Server B’s key derives a different DB password than Server A’s.

Symptom: “decryption failed” on a V1 backup that you know is good.

Fix:

In the restore modal’s Source License Key field, enter Server A’s license key — the one that created the backup, not the one currently running. The handler calls:

GET https://license.proxrad.com/api/v1/license/backup-password
Header: X-License-Key: PROXP-SERVER-A-KEY

and uses the returned password for decryption only. Nothing is permanently changed on Server B.

The backup was taken with pg_dump from one Postgres major version (e.g. 16) and you’re restoring with pg_restore from another (e.g. 14). This happens when an upgrade replaced the container’s postgresql-client package but the running database is still on the old version, or vice versa.

Log line:

pg_restore: error: unsupported version (1.16) in file header

or

pg_restore: error: server version: 16.11; pg_dump version: 14.20

Diagnostic:

Terminal window
# What server version is running?
docker exec proxpanel-db psql -U proxpanel -d proxpanel -c "SELECT version();"
# What client version is in the API container?
docker exec proxpanel-api pg_restore --version

Both should be the same major version (16 today).

Fix:

  1. Install matching client in the API container. ProxPanel’s docker-compose since v1.0.95 installs postgresql-client-16 from the PostgreSQL APT repo. For older installs:

    Terminal window
    docker exec proxpanel-api bash -c '
    apt-get update &&
    apt-get install -y wget gnupg lsb-release &&
    install -d /usr/share/postgresql-common/pgdg &&
    wget -qO /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc https://www.postgresql.org/media/keys/ACCC4CF8.asc &&
    echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list &&
    apt-get update &&
    apt-get install -y postgresql-client-16
    '
    docker restart proxpanel-api
  2. Verify:

    Terminal window
    docker exec proxpanel-api pg_restore --version
  3. Retry the restore.

Cause 5 — Backup scheduler never fires (tzdata missing)

Section titled “Cause 5 — Backup scheduler never fires (tzdata missing)”

A scheduled backup is configured for 02:00 Asia/Beirut but no backup files appear and backup_logs has no row. The scheduler couldn’t load the timezone and silently fell back to UTC, where 02:00 already passed without firing.

Same root cause as the FUP reset issue — the API container is missing tzdata.

Diagnostic:

Terminal window
docker exec proxpanel-api dpkg -l | grep tzdata
docker exec proxpanel-db psql -U proxpanel -d proxpanel -c \
"SELECT id, name, frequency, time_of_day, last_run_at, next_run_at, last_status FROM backup_schedules;"

If tzdata is missing or next_run_at is NULL, the scheduler can’t compute the next firing time.

Fix:

Terminal window
docker exec proxpanel-api apt-get update
docker exec proxpanel-api apt-get install -y tzdata
docker restart proxpanel-api

Then in the UI: open the schedule, save it again (the save handler recomputes next_run_at correctly). The next firing window is then valid.

Full details: see FUP Not Resetting — the same tzdata fix applies.

Cause 6 — Backup file header was mangled by FTP ASCII mode

Section titled “Cause 6 — Backup file header was mangled by FTP ASCII mode”

The backup uploads to FTP with binary content but the FTP client did the upload in ASCII mode, replacing \n with \r\n (or vice versa). The first 30 bytes — the magic header — are now off-by-one, decryption fails.

Diagnostic:

Terminal window
# CR-LF in a backup means corruption
xxd /opt/proxpanel/backups/SUSPECT.proisp.bak | head -3 | grep -c '0d 0a'

If you see a 0d 0a (CR-LF) near the start of a backup that should have plain 0a (LF), FTP mangled it.

Fix:

Re-upload with FTP in binary mode:

Terminal window
# Inside an ftp session:
binary
put /opt/proxpanel/backups/proisp_full_20260512_010000.proisp.bak

Or skip FTP and use SCP / rsync, which never touch byte content.

How to take a clean backup before troubleshooting

Section titled “How to take a clean backup before troubleshooting”

Before doing anything destructive, take a manual backup. In the UI: Settings → Backups → Create Backup Now. Or from the host:

Terminal window
# Dump and encrypt via the running API
curl -s -X POST http://localhost:8080/api/backups/create \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"backup_type":"full"}'
# Files land in:
ls -lh /opt/proxpanel/backups/

Copy the resulting .proisp.bak to a safe location before continuing.

Terminal window
# Header + size of the suspect file
head -c 35 /opt/proxpanel/backups/SUSPECT.proisp.bak | xxd > /tmp/header.txt
ls -la /opt/proxpanel/backups/SUSPECT.proisp.bak >> /tmp/header.txt
# Backup log entries
docker exec proxpanel-db psql -U proxpanel -d proxpanel -c \
"SELECT * FROM backup_logs ORDER BY created_at DESC LIMIT 10;" > /tmp/backup-log.txt
# Schedules
docker exec proxpanel-db psql -U proxpanel -d proxpanel -c \
"SELECT * FROM backup_schedules;" > /tmp/schedules.txt
# Postgres versions
docker exec proxpanel-db psql -U proxpanel -d proxpanel -c "SELECT version();" > /tmp/pgsrv.txt
docker exec proxpanel-api pg_restore --version > /tmp/pgcli.txt
# tzdata status
docker exec proxpanel-api dpkg -l | grep tzdata > /tmp/tzdata.txt
# Recent API errors
docker logs --since=1h proxpanel-api 2>&1 | grep -iE "backup|restore|decrypt" > /tmp/backup.log

Email info@proxrad.com with these files and the source/destination license keys (if cross-server).