Proxmox Backup Server (PBS): HOW-TO Guide¶
Complete guide for managing Proxmox Backup Server using the SDK — from instantiating a session to managing datastores, snapshots, garbage collection, pruning, and monitoring tasks.
Overview¶
PBS is a standalone Proxmox service for backup management. It runs on a separate host from PVE and has its own API and authentication system.
PBS vs PVE Differences¶
| Aspect | PVE | PBS |
|---|---|---|
| Default port | 8006 | 8007 |
| Auth cookie | PVEAuthCookie |
PBSAuthCookie |
| Token separator | = |
: |
| Token header example | PVEAPIToken=user!name=value |
PBSAPIToken=user!name:value |
| Supported backends | https, ssh, local, mock | https, mock only |
| CLI equivalent | pvesh |
None |
No SSH or Local Backend
PBS does not support the ssh_paramiko, openssh, or local backends.
Unlike PVE, PBS has no pvesh-equivalent CLI tool. Only https and mock
backends are available.
Token Header Format
The SDK handles the PBS token separator automatically. When you pass
token_name and token_value, the SDK builds the correct
PBSAPIToken=user!name:value header — no extra configuration needed.
Session Setup¶
All examples below assume a connection object named pbs. Replace credentials with your actual values.
Password Authentication¶
Token Authentication (Recommended for Automation)¶
API tokens are preferred for automation: they do not expire and support fine-grained permissions.
SSL Configuration¶
For PBS servers with self-signed certificates (common in homelab and internal environments):
# Disable SSL verification — development only
ProxmoxSDK(
host="pbs.example.com",
user="admin@pam",
password="secret",
service="PBS",
port=8007,
verify_ssl=False,
)
# Or provide a custom CA certificate
ProxmoxSDK(
host="pbs.example.com",
user="admin@pam",
password="secret",
service="PBS",
port=8007,
cert="/etc/ssl/certs/pbs-ca.pem",
)
Listing Datastores¶
Datastores are the primary storage units in PBS. Each datastore maps to a directory on the PBS host.
async with ProxmoxSDK(..., service="PBS", port=8007) as pbs:
datastores = await pbs.admin.datastore.get()
for ds in datastores:
print(f"Datastore: {ds['store']}")
print(f" Path: {ds.get('path', 'N/A')}")
print(f" Comment: {ds.get('comment', '')}")
# Output:
# Datastore: vm-backups
# Path: /mnt/backup/vm-backups
# Comment: Production VM backups
Datastore Details¶
async with ProxmoxSDK(..., service="PBS", port=8007) as pbs:
# Get configuration of a specific datastore
ds_config = await pbs.admin.datastore("vm-backups").config.get()
print(f"Store: {ds_config['store']}")
print(f"Path: {ds_config['path']}")
print(f"GC Notify: {ds_config.get('notify', 'always')}")
print(f"Prune Jobs: {ds_config.get('prune-schedule', 'none')}")
Datastore Usage¶
async with ProxmoxSDK(..., service="PBS", port=8007) as pbs:
# Usage summary for all datastores
usage_list = await pbs.status("datastore-usage").get()
for usage in usage_list:
store = usage["store"]
total_gb = usage.get("total", 0) / 1024**3
used_gb = usage.get("used", 0) / 1024**3
avail_gb = usage.get("avail", 0) / 1024**3
pct = (usage.get("used", 0) / usage.get("total", 1)) * 100
print(f"{store}: {used_gb:.1f} GB / {total_gb:.1f} GB ({pct:.1f}% used, {avail_gb:.1f} GB free)")
# Output:
# vm-backups: 182.4 GB / 500.0 GB (36.5% used, 317.6 GB free)
Working with Snapshots¶
Each backup in PBS is stored as a snapshot (also called a backup group entry). Snapshots are organized by backup type, backup ID, and timestamp.
List Snapshots in a Datastore¶
async with ProxmoxSDK(..., service="PBS", port=8007) as pbs:
snapshots = await pbs.admin.datastore("vm-backups").snapshots.get()
for snap in snapshots:
btype = snap["backup-type"] # "vm", "ct", or "host"
bid = snap["backup-id"] # e.g. "100" for VMID 100
btime = snap["backup-time"] # Unix timestamp
size = snap.get("size", 0) / 1024**3
print(f"{btype}/{bid} @ {btime} ({size:.2f} GB)")
# Output:
# vm/100 @ 1710000000 (4.31 GB)
# vm/101 @ 1710003600 (2.17 GB)
# ct/200 @ 1710007200 (0.84 GB)
Filter Snapshots by Type¶
async with ProxmoxSDK(..., service="PBS", port=8007) as pbs:
snapshots = await pbs.admin.datastore("vm-backups").snapshots.get()
# Only VM backups
vm_snaps = [s for s in snapshots if s["backup-type"] == "vm"]
print(f"Found {len(vm_snaps)} VM snapshots:")
for snap in vm_snaps:
print(f" VM {snap['backup-id']} — {snap['backup-time']}")
Delete a Snapshot¶
Garbage Collection¶
Garbage collection (GC) reclaims disk space from deleted or pruned backup chunks that are no longer referenced.
from proxmox_sdk import ProxmoxSDK
from proxmox_sdk.sdk.tools import Tasks
async def run_garbage_collection(store: str):
async with ProxmoxSDK(
host="pbs.example.com",
user="admin@pam",
password="secret",
service="PBS",
port=8007,
) as pbs:
print(f"Starting GC on datastore '{store}'...")
result = await pbs.admin.datastore(store).gc.post()
task_id = result.get("upid") if isinstance(result, dict) else result
if task_id:
tasks = Tasks(pbs)
status = await tasks.wait_task(task_id, timeout=3600)
print(f"GC completed: {status}")
else:
print(f"GC triggered (no task ID returned): {result}")
import asyncio
asyncio.run(run_garbage_collection("vm-backups"))
Verification Jobs¶
Verification confirms that all backup chunks for a datastore are intact and readable.
from proxmox_sdk import ProxmoxSDK
from proxmox_sdk.sdk.tools import Tasks
async def verify_datastore(store: str, ignore_verified: bool = True):
"""Run a verification job on a PBS datastore."""
async with ProxmoxSDK(
host="pbs.example.com",
user="admin@pam",
password="secret",
service="PBS",
port=8007,
) as pbs:
print(f"Verifying datastore '{store}'...")
result = await pbs.admin.datastore(store).verify.post(
**{"ignore-verified": ignore_verified}, # Skip already-verified chunks
)
task_id = result.get("upid") if isinstance(result, dict) else result
if task_id:
tasks = Tasks(pbs)
status = await tasks.wait_task(task_id, timeout=7200)
print(f"Verification complete: {status}")
import asyncio
asyncio.run(verify_datastore("vm-backups"))
Pruning Old Backups¶
Pruning removes old snapshots from a datastore according to a retention policy. The policy is defined by how many backups to keep across different time intervals.
Prune a Specific Backup Group¶
from proxmox_sdk import ProxmoxSDK
from proxmox_sdk.sdk.tools import Tasks
async def prune_vm_backups(store: str, vmid: int):
"""Prune backups for a specific VM, keeping recent copies."""
async with ProxmoxSDK(
host="pbs.example.com",
user="admin@pam",
password="secret",
service="PBS",
port=8007,
) as pbs:
result = await pbs.admin.datastore(store).prune.post(
**{
"backup-type": "vm",
"backup-id": str(vmid),
"keep-last": 3, # Keep the 3 most recent backups
"keep-daily": 7, # Keep 1 backup per day for 7 days
"keep-weekly": 4, # Keep 1 per week for 4 weeks
"keep-monthly": 3, # Keep 1 per month for 3 months
"keep-yearly": 1, # Keep 1 per year
}
)
task_id = result.get("upid") if isinstance(result, dict) else result
if task_id:
tasks = Tasks(pbs)
status = await tasks.wait_task(task_id, timeout=600)
print(f"Prune complete for VM {vmid}: {status}")
else:
print(f"Prune result: {result}")
import asyncio
asyncio.run(prune_vm_backups("vm-backups", vmid=100))
from proxmox_sdk import ProxmoxSDK
with ProxmoxSDK.sync(
host="pbs.example.com",
user="admin@pam",
password="secret",
service="PBS",
port=8007,
) as pbs:
result = pbs.admin.datastore("vm-backups").prune.post(
**{
"backup-type": "vm",
"backup-id": "100",
"keep-last": 3,
"keep-daily": 7,
}
)
print(f"Prune result: {result}")
Dry Run (Preview What Would Be Deleted)¶
async with ProxmoxSDK(..., service="PBS", port=8007) as pbs:
# dry_run=1 shows what would be pruned without deleting
result = await pbs.admin.datastore("vm-backups").prune.post(
**{
"backup-type": "vm",
"backup-id": "100",
"keep-last": 3,
"keep-daily": 7,
"dry-run": 1,
}
)
print("Dry run — backups that would be removed:")
if isinstance(result, list):
for snap in result:
action = snap.get("action", "unknown")
ts = snap.get("backup-time", "?")
print(f" [{action}] backup-time={ts}")
Node Status and Monitoring¶
List PBS Nodes¶
Node Resource Status¶
async with ProxmoxSDK(..., service="PBS", port=8007) as pbs:
status = await pbs.nodes("pbs1").status.get()
uptime_h = status.get("uptime", 0) / 3600
cpu_pct = status.get("cpu", 0) * 100
mem_used = status.get("memory", {}).get("used", 0) / 1024**3
mem_total= status.get("memory", {}).get("total", 1) / 1024**3
print(f"Uptime: {uptime_h:.1f} hours")
print(f"CPU: {cpu_pct:.1f}%")
print(f"Memory: {mem_used:.1f} GB / {mem_total:.1f} GB")
Task Monitoring¶
Long-running PBS operations (GC, verify, prune) return a task UPID. You can monitor these tasks directly.
List Recent Tasks¶
Check Task Status¶
Read Task Log¶
Wait for Task with Timeout¶
Use the built-in Tasks tool to poll until a task finishes:
from proxmox_sdk.sdk.tools import Tasks
async with ProxmoxSDK(..., service="PBS", port=8007) as pbs:
# Start a GC job
result = await pbs.admin.datastore("vm-backups").gc.post()
task_id = result.get("upid") if isinstance(result, dict) else result
# Wait up to 30 minutes
tasks_tool = Tasks(pbs)
final_status = await tasks_tool.wait_task(task_id, timeout=1800)
print(f"Task finished with: {final_status}")
User and Token Management¶
List PBS Users¶
Create a PBS User¶
List API Tokens for a User¶
Create an API Token¶
async with ProxmoxSDK(..., service="PBS", port=8007) as pbs:
result = await pbs.access.users("backup-operator@pam").token("automation").post(
comment="CI/CD automation token",
)
# IMPORTANT: The token secret is only shown once on creation
token_value = result.get("value")
print(f"Token created. Secret (save now): {token_value}")
print(f"Connect using: PBSAPIToken=backup-operator@pam!automation:{token_value}")
List Auth Realms (Domains)¶
Real-World Examples¶
Example 1: Automated Backup Maintenance¶
Run garbage collection, verify, and prune in sequence for all datastores:
import asyncio
from proxmox_sdk import ProxmoxSDK
from proxmox_sdk.sdk.tools import Tasks
async def maintenance_all_datastores():
"""Run GC → verify → prune on every datastore."""
async with ProxmoxSDK(
host="pbs.example.com",
user="admin@pam",
password="secret",
service="PBS",
port=8007,
) as pbs:
datastores = await pbs.admin.datastore.get()
tasks_tool = Tasks(pbs)
for ds in datastores:
store = ds["store"]
print(f"\n=== Maintaining datastore: {store} ===")
# Step 1: Garbage collection
print(" [1/3] Running garbage collection...")
result = await pbs.admin.datastore(store).gc.post()
task_id = result.get("upid") if isinstance(result, dict) else result
if task_id:
await tasks_tool.wait_task(task_id, timeout=3600)
print(" GC done.")
# Step 2: Verify all chunks
print(" [2/3] Verifying chunks...")
result = await pbs.admin.datastore(store).verify.post(
**{"ignore-verified": True}
)
task_id = result.get("upid") if isinstance(result, dict) else result
if task_id:
await tasks_tool.wait_task(task_id, timeout=7200)
print(" Verification done.")
# Step 3: Prune — each backup group needs a separate call
print(" [3/3] Pruning old snapshots...")
snapshots = await pbs.admin.datastore(store).snapshots.get()
# Collect unique backup groups
groups = {
(s["backup-type"], s["backup-id"])
for s in snapshots
}
for btype, bid in groups:
result = await pbs.admin.datastore(store).prune.post(
**{
"backup-type": btype,
"backup-id": bid,
"keep-last": 3,
"keep-daily": 7,
"keep-weekly": 4,
}
)
task_id = result.get("upid") if isinstance(result, dict) else result
if task_id:
await tasks_tool.wait_task(task_id, timeout=300)
print(" Pruning done.")
print(f"=== {store} maintenance complete ===")
asyncio.run(maintenance_all_datastores())
Example 2: Datastore Health Report¶
Generate a summary of datastore usage and snapshot counts:
import asyncio
from proxmox_sdk import ProxmoxSDK
async def datastore_health_report():
async with ProxmoxSDK(
host="pbs.example.com",
user="admin@pam",
password="secret",
service="PBS",
port=8007,
) as pbs:
datastores = await pbs.admin.datastore.get()
usage_map = {
u["store"]: u
for u in await pbs.status("datastore-usage").get()
}
print("\n" + "=" * 65)
print("PROXMOX BACKUP SERVER — DATASTORE HEALTH REPORT")
print("=" * 65)
for ds in datastores:
store = ds["store"]
usage = usage_map.get(store, {})
total_gb = usage.get("total", 0) / 1024**3
used_gb = usage.get("used", 0) / 1024**3
avail_gb = usage.get("avail", 0) / 1024**3
pct = (used_gb / total_gb * 100) if total_gb else 0
snapshots = await pbs.admin.datastore(store).snapshots.get()
vm_count = sum(1 for s in snapshots if s["backup-type"] == "vm")
ct_count = sum(1 for s in snapshots if s["backup-type"] == "ct")
host_count= sum(1 for s in snapshots if s["backup-type"] == "host")
print(f"\nDatastore: {store}")
print(f" Path: {ds.get('path', 'N/A')}")
print(f" Usage: {used_gb:.1f} GB / {total_gb:.1f} GB ({pct:.1f}%)")
print(f" Free: {avail_gb:.1f} GB")
print(f" Snapshots: {len(snapshots)} total "
f"(VM: {vm_count}, CT: {ct_count}, Host: {host_count})")
if pct > 85:
print(" *** WARNING: datastore is more than 85% full ***")
print("\n" + "=" * 65)
asyncio.run(datastore_health_report())
Common Parameters Reference¶
Prune Retention Parameters¶
| Parameter | Type | Description |
|---|---|---|
keep-last |
int | Keep the N most recent backups regardless of time |
keep-daily |
int | Keep one backup per day for the last N days |
keep-weekly |
int | Keep one backup per week for the last N weeks |
keep-monthly |
int | Keep one backup per month for the last N months |
keep-yearly |
int | Keep one backup per year for the last N years |
keep-hourly |
int | Keep one backup per hour for the last N hours |
dry-run |
int (1) |
Preview what would be pruned without deleting |
Retention Policy Interaction
Parameters are applied in combination. A snapshot is kept if it matches any active retention rule. Set only the parameters you need — omitting a parameter means that rule is not enforced.
Backup Types¶
| Value | Description |
|---|---|
vm |
QEMU/KVM virtual machine backup |
ct |
LXC container backup |
host |
PBS host/system backup |
Troubleshooting¶
Connection Refused on Default Port¶
PBS listens on port 8007, not 8006. Always pass port=8007 explicitly:
ProxmoxSDK(
host="pbs.example.com",
user="admin@pam",
password="secret",
service="PBS",
port=8007, # Required — PBS is not on port 8006
)
SSH or Local Backend Raises ValueError¶
PBS does not support SSH or local backends:
# This raises: ValueError: Backend 'ssh_paramiko' is not supported for service 'PBS'
ProxmoxSDK(host="pbs.example.com", user="root", password="s", service="PBS", backend="ssh_paramiko")
Use backend="https" (default) or backend="mock" for testing.
Token Authentication Fails¶
PBS uses a colon (:) as the token separator, not an equals sign (=). The SDK handles this automatically — make sure you pass service="PBS" so the correct format is used:
# Wrong — PVE token format (= separator)
# Authorization: PVEAPIToken=user!name=value
# Correct — SDK builds the PBS header automatically when service="PBS"
ProxmoxSDK(
host="pbs.example.com",
user="admin@pam",
token_name="my-token",
token_value="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
service="PBS", # Critical: ensures PBSAPIToken header format
port=8007,
)
# Authorization: PBSAPIToken=admin@pam!my-token:xxxxxxxx-...
Permission Denied on Datastore Operations¶
PBS has its own ACL system separate from PVE. The user/token needs the DatastoreAdmin or DatastoreBackup role on the specific datastore path:
Configure this in the PBS web UI under Configuration → Access Control.
SSL Certificate Error¶
PBS typically uses a self-signed certificate. For production, either:
# Option 1: Provide the PBS CA certificate
ProxmoxSDK(..., service="PBS", port=8007, cert="/etc/pve/pbs-ca.pem")
# Option 2: Disable verification (development only)
ProxmoxSDK(..., service="PBS", port=8007, verify_ssl=False)
See Also¶
- SDK Guide — Overview, backends, and core concepts
- Authentication Guide — Detailed credential setup
- Virtual Machines Guide — Managing PVE VMs
- Examples & Recipes — Backup, cluster, and monitoring examples
- API Reference — Complete endpoint documentation