Backup, Replication & Conversion for VMware vSphere/ESXi on Bacula Community
Version 1.4.1 — April 2026 Author: Heitor Faria Copyright © 2026 Heitor Faria — All Rights Reserved
💰 Special Offer
Bring your Bacula Enterprise, Veeam, Commvault or Netbackup quote or renewal proposal. We offer at least 50% discount, with far more features.
📧 heitor@opentechs.lat | 📱 +1 786 726-1749 | +55 61 98268-4220 (WhatsApp)
Table of Contents
- Executive Summary
- Problem Statement & Market Context
- Use Cases
- Architecture
- Features — Backup
- Features — Replication & Disaster Recovery
- Features — Cross-Hypervisor Conversion
- Security Architecture
- Installation Guide
- Configuration Reference
- Backup Configuration Examples
- Replication Configuration Examples
- User Manual & Day-2 Operations
- Sizing & Requirements
- Compatibility Matrix
- Test Results
- Comparison with Enterprise Solutions
- Troubleshooting
- Licensing & Contact
1. Executive Summary
PodHeitor vSphere BRC is a production-ready plugin for Bacula Community Edition that delivers enterprise-grade VMware vSphere backup, replication, and cross-hypervisor conversion capabilities — at a fraction of the cost of commercial solutions.
Key Differentiators
- Three products in one: Backup + Replication + Conversion in a single plugin
- Bacula Community native: No Enterprise license required
- CBT-based replication: Near real-time RPO with minimal bandwidth
- 10-mode DR: Complete failover lifecycle management
- Cross-hypervisor restore: vSphere ↔ Hyper-V ↔ Proxmox/KVM conversion
- TLS-secured DR protocol: Production-ready security
- Written in Rust: Memory-safe, high-performance, zero-overhead FFI with VDDK
Version 1.4.1 Highlights
End-to-end restore-correctness fixes — validated against ESXi 8.0.3 + Bacula 15.0.3 (job 6442 restored 2.147 GB / 4 files in 6 s, 0 FD errors; the recreated Alpine 3.21 VM booted cleanly):
| Category | Fix |
|---|---|
| 🐛 Backup | File paths no longer doubled (@vsphere/@vsphere/disk-2000.vmdk → @vsphere/disk-2000.vmdk). |
| 🐛 Restore | Where= prefix from Bacula is now captured correctly: a non-empty Where routes to filesystem-restore; empty// falls through to hypervisor-restore. |
| 🐛 Restore | Directory entries no longer terminate the outer PTCOMM command loop with a spurious EOD; directories use a no-op writer. |
Version 1.4.0 Highlights
| Category | Feature |
|---|---|
| ⚖️ Licensing | Plugin .so is now a pure-Rust cdylib — no Bacula AGPLv3 source linked. The C ABI is implemented through the clean-room bacula-fd-abi crate (independently reimplemented from public spec). |
| 🛠 Build | Building no longer requires the Bacula source tree. cargo build --release -p plugin-vsphere from the PodHeitor Rust cdylib workspace produces the shipped .so. |
| 🧱 Architecture | License firewall preserved: cdylib in-process, Rust backend out-of-process via PTCOMM (crash isolation + clean licensing boundary). |
Version 1.3.0 Highlights
| Category | Feature |
|---|---|
| 🔐 Security | TLS encryption for DR protocol (rustls) |
| 🔐 Security | Constant-time token authentication |
| 🌐 Network | Automatic network mapping on failover (ReconfigVM SOAP) |
| 🌐 Network | Automatic IP reconfiguration on failover (CustomizeVM SOAP) |
| 📸 Recovery | Snapshot-based restore points on replica VM |
| 🔧 Operations | SIGTERM graceful shutdown for daemon mode |
2. Problem Statement & Market Context
The Challenge
Organizations running VMware vSphere face critical data protection challenges:
- Cost: Bacula Enterprise vSphere plugin costs $$$; Veeam, Commvault, Netbackup even more
- Vendor Lock-in: Enterprise backup solutions lock customers into expensive perpetual renewals
- Feature Gaps: Bacula Community has no native vSphere backup — only filesystem-level agents
- DR Complexity: Setting up replication + failover typically requires separate products
- Multi-Hypervisor: Migrating between vSphere, Hyper-V, and KVM requires manual conversion
The Solution
PodHeitor vSphere BRC eliminates these challenges by providing:
- Zero Enterprise licensing — works with free Bacula Community
- All-in-one plugin — backup, replication, and conversion unified
- Bacula-native integration — uses standard FileSet/Job/Schedule configuration
- Automated DR — one-click failover with network remapping and IP reconfiguration
- Open standards — VMDK, VHDX, QCOW2 conversion between hypervisors
3. Use Cases
Use Case 1: VMware VM Backup
Scenario: Organization needs image-level VM backup with CBT for efficient incrementals.
Job: Full backup Sunday, Incremental daily
RPO: 24 hours (backup) / minutes (replication)
RTO: Minutes (instant restore) to hours (full restore)
Use Case 2: Disaster Recovery
Scenario: Production site failure — need to boot VMs at DR site immediately.
Normal operation: CBT push every 5 minutes
Planned failover: Sync → graceful shutdown → boot replica
Unplanned failover: Boot replica immediately from last sync point
Failback: Reverse-replicate once production is restored
Use Case 3: Migration Between Hypervisors
Scenario: Migrating from VMware to Proxmox/KVM due to Broadcom licensing changes.
Backup: VMware VM → Bacula storage
Restore: Bacula → Proxmox (automatic VMDK→QCOW2 conversion)
Use Case 4: Test/Dev Environment Cloning
Scenario: Clone production VMs to isolated test network.
Mode: failover-test (non-destructive)
Network: Isolated test VLAN
Result: Production unaffected, test VM available
Use Case 5: Compliance & Multi-Site Backup
Scenario: Regulatory requirement for offsite backup copies.
Primary: Local backup to disk/tape
Secondary: CBT replication to remote DR site
Tertiary: Cross-restore to different hypervisor at third site
4. Architecture
Component Overview
┌─────────────────────────────────────────────────────────────┐
│ Bacula Director │
│ (Job scheduling, catalog, policy management) │
└─────────────────┬───────────────────────────────────────────┘
│
┌─────────────────▼───────────────────────────────────────────┐
│ Bacula File Daemon (FD) │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ podheitor-vsphere-fd.so (Rust cdylib, v1.4.1+) │ │
│ │ • Loaded by Bacula FD via dlopen │ │
│ │ • Implements Bacula FD plugin C ABI through │ │
│ │ independently-reimplemented bacula-fd-abi crate │ │
│ │ • No Bacula AGPLv3 source linked │ │
│ │ • Spawns Rust backend via PTCOMM pipe │ │
│ └──────────────────┬───────────────────────────────────┘ │
│ │ stdin/stdout pipe │
│ ┌──────────────────▼───────────────────────────────────┐ │
│ │ podheitor-vsphere-backend (Rust binary) │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ Backup Module │ Restore Module │ │ │
│ │ │ • VADP workflow │ • Full VM restore │ │ │
│ │ │ • CBT tracking │ • Single disk restore │ │ │
│ │ │ • Snapshot lifecycle │ • Cross-restore (VHDX) │ │ │
│ │ ├─────────────────────────────────────────────────┤ │ │
│ │ │ Replication Module (1500+ lines) │ │ │
│ │ │ • CBT push (async replication) │ │ │
│ │ │ • Seed (initial full sync) │ │ │
│ │ │ • 10-mode failover lifecycle │ │ │
│ │ │ • TLS-encrypted DR protocol │ │ │
│ │ │ • DR receiver daemon │ │ │
│ │ ├─────────────────────────────────────────────────┤ │ │
│ │ │ vSphere API Client │ │ │
│ │ │ • SOAP/XML over HTTPS │ │ │
│ │ │ • Property collector │ │ │
│ │ │ • Task manager │ │ │
│ │ │ • ReconfigVM (net_map) │ │ │
│ │ │ • CustomizeVM (Re-IP) │ │ │
│ │ ├─────────────────────────────────────────────────┤ │ │
│ │ │ VDDK Integration (FFI) │ │ │
│ │ │ • VixDiskLib_Open/Read/Write │ │ │
│ │ │ • QueryAllocatedBlocks │ │ │
│ │ │ • Transport: NBD, NBDSSL, HotAdd, SAN │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│ │
▼ ▼
┌─────────────────────────┐ ┌─────────────────────────────┐
│ VMware vSphere API │ │ DR Receiver (remote host) │
│ (SOAP over HTTPS) │ │ TLS-encrypted TCP │
│ ESXi / vCenter │ │ PSK authentication │
└─────────────────────────┘ │ Snapshot management │
│ Delta application │
└─────────────────────────────┘
Data Flow — Backup
1. Bacula FD invokes plugin
2. Plugin creates VM snapshot (quiesced)
3. VDDK opens VMDK via chosen transport
4. CBT provides changed block list (incremental)
5. Blocks streamed to Bacula SD via FD pipe
6. Snapshot deleted after backup
7. Job metadata stored in Bacula catalog
Data Flow — CBT Replication
1. Plugin queries CBT delta since last sync
2. Changed blocks read via VDDK
3. Delta sent to DR receiver via TLS-encrypted TCP
4. Receiver creates restore point snapshot on replica
5. Receiver writes delta to replica VMDK via VDDK
6. Replication state persisted to /var/lib/podheitor/replication/
Data Flow — Failover
Planned Failover:
1. Final CBT sync (catch up)
2. Shutdown source VM
3. Apply net_map (ReconfigVM)
4. Apply Re-IP (CustomizeVM)
5. Power on replica
6. Update replication state
Unplanned Failover:
1. Apply net_map on replica
2. Apply Re-IP on replica
3. Power on replica immediately
4. (no final sync — uses last available restore point)
5. Features — Backup
Full Backup
- Image-level VM backup via VMware VADP API
- Quiesced snapshots for application consistency
- Multi-disk support (all virtual disks captured)
- Metadata preservation (VM config, hardware version, guest OS)
Incremental Backup
- Changed Block Tracking (CBT) for efficient incremental backups
- Only modified blocks transferred — typically 1-5% of total disk
- Dramatic reduction in backup window and storage
Differential Backup
- Captures all changes since last Full backup
- Useful for environments with high change rates
Transport Modes
| Mode | Description | Use Case |
|---|---|---|
| NBD | Network Block Device (unencrypted) | Lab/test environments |
| NBDSSL | NBD with SSL encryption | Standard production use |
| HotAdd | Attach VMDK directly to FD VM | FD running as VM on same ESXi |
| SAN | Direct SAN access to VMDK LUN | LAN-free backup, highest performance |
Multi-VM Support
- Backup multiple VMs with separate Jobs
- Host include/exclude filters for bulk operations
- Disk include/exclude for selective backup
6. Features — Replication & Disaster Recovery
CBT-Based Replication
- Asynchronous push-based replication using VMware CBT
- RPO measured in minutes (configurable push interval)
- Bandwidth-efficient — only changed blocks transmitted
- Optional bandwidth throttling (
max_bandwidth_mbps)
10-Mode DR Lifecycle
| # | Mode | Description | Destructive | Source Impact |
|---|---|---|---|---|
| 1 | replication-status |
Query replication state and last sync | No | None |
| 2 | cbt-push |
Push CBT deltas to replica | No | None |
| 3 | seed |
Initial full disk sync (create replica) | No | None |
| 4 | failover-test |
Boot replica on isolated network | No | None |
| 5 | failover-undo |
Power off test replica | No | None |
| 6 | failover-planned |
Sync → shutdown source → boot replica | Yes | Shutdown |
| 7 | failover-unplanned |
Boot replica immediately | No | None |
| 8 | failover-permanent |
Convert replica to production | Yes | N/A |
| 9 | failback |
Reverse-replicate from replica to source | Yes | Restored |
| 10 | reprotect |
Re-establish forward replication | No | None |
Network Mapping (net_map)
- Automatically reconfigures VM NICs on failover
- Maps source networks to target networks at DR site
- Uses VMware ReconfigVM_Task SOAP API
- Applied on: planned, unplanned, permanent failover
IP Reconfiguration (Re-IP)
- Automatically sets IP address, subnet, gateway, DNS on failover
- Uses VMware CustomizeVM_Task SOAP API (GuestOS Customization)
- Format:
"nic_index:ip/prefix:gateway:dns1,dns2" - Requires VMware Tools on guest OS
Restore Points
- Snapshot created on replica before each delta application
- Named
PodHeitor_RP_<timestamp> - Configurable retention (
dr_restore_points, default: 5) - Enables point-in-time recovery from replica
DR Receiver Daemon
- Persistent daemon mode for receiving replicated data
- Listens on configurable TCP port (default: 9102)
- PSK-based authentication with constant-time comparison
- SIGTERM graceful shutdown handler
- TLS encryption (optional, recommended for production)
7. Features — Cross-Hypervisor Conversion
Supported Conversions
| Source | Target | Format Conversion |
|---|---|---|
| VMware vSphere (VMDK) | Hyper-V (VHDX) | Automatic |
| VMware vSphere (VMDK) | Proxmox/KVM (QCOW2) | Automatic |
| Hyper-V (VHDX) | VMware vSphere (VMDK) | Automatic |
| Hyper-V (VHDX) | Proxmox/KVM (QCOW2) | Automatic |
| Proxmox/KVM (QCOW2) | VMware vSphere (VMDK) | Automatic |
How It Works
- Backup captures VM at image level (VMDK native format)
- Bacula stores the backup data in its standard storage
- On restore, the target plugin detects the source hypervisor
- Automatic format conversion (VMDK ↔ VHDX ↔ QCOW2)
- VM configuration adapted to target hypervisor (hardware, drivers)
8. Security Architecture
Transport Security
- Backup: NBDSSL (SSL-encrypted) or SAN (dedicated storage network)
- DR Protocol: TLS 1.3 via rustls (when certificate/key configured)
- vSphere API: HTTPS with certificate validation
Authentication
- vSphere: Username/password via SOAP SessionManager
- DR Protocol: Pre-shared key (PSK) with constant-time comparison
- Constant-time auth: XOR-based comparison prevents timing attacks
Data Protection
- No secrets stored in code — credentials via Bacula FileSet configuration
- DR auth tokens rotatable without service restart
- TLS certificate/key paths configurable via plugin options
Compliance Features
- Structured logging (level + component + message)
- Audit trail via Bacula catalog (job history, metadata)
- Replication state persistence for DR compliance verification
9. Installation Guide
9.1 Prerequisites
| Requirement | Details |
|---|---|
| OS (File Daemon host) | Oracle Linux 9 / RHEL 9 / Rocky Linux 9 / AlmaLinux 9 — x86_64 |
| Bacula | Community 15.0.x installed and running on the same host |
| VDDK | Shipped as podheitor-vixdisklib-runtime RPM — no manual tar extraction |
| Network | TCP 443 to ESXi/vCenter; TCP 9102 (default) between primary FD and DR receiver |
| VMware Tools | Installed in guest VMs (required for quiesce and Re-IP) |
| CBT | Enabled on every VM to be protected (see §9.5) |
| Disk space | ≥ 500 MB free for backend binary + VDDK runtime; see §13 for backup storage |
The File Daemon host needs outbound TCP 443 to the ESXi/vCenter API, and (for replication) outbound TCP 9102 to the DR receiver. No inbound ports are required on the primary FD.
9.2 Release artifacts
Everything ships as signed RPMs inside releases/:
| File | Purpose |
|---|---|
podheitor-vsphere-1.4.1-1.el9.x86_64.rpm |
Rust cdylib plugin (.so) + Rust backend + CBT/replication state dirs |
podheitor-vixdisklib-runtime-9.0.1-1.el9.x86_64.rpm |
VMware VDDK 9.0.1 runtime libraries, self-contained via $ORIGIN RPATH |
No tar extraction, no manual ld.so.conf edits, no LD_LIBRARY_PATH exports — all of that is handled by the RPMs’ %post scriptlets.
9.3 Standard install (RPM)
Order does not matter; either RPM can be installed first.
# 1. Plugin RPM
sudo rpm -Uvh releases/podheitor-vsphere-1.4.1-1.el9.x86_64.rpm
# 2. VDDK runtime RPM
sudo rpm -Uvh releases/podheitor-vixdisklib-runtime-9.0.1-1.el9.x86_64.rpm
# 3. Restart Bacula FD
sudo systemctl restart bacula-fd
The plugin RPM installs:
| Path | Contents |
|---|---|
/opt/bacula/plugins/podheitor-vsphere-fd.so |
Rust cdylib plugin loaded by Bacula FD |
/opt/bacula/bin/podheitor-vsphere-backend |
Rust backend binary |
/opt/bacula/working/podheitor/cbt/ |
CBT state (change-ids per VM) |
/var/lib/podheitor/replication/ |
Replication state + restore-point metadata |
The VDDK runtime RPM installs:
| Path | Contents |
|---|---|
/usr/lib/vmware-vix-disklib/lib64/ |
libvixDiskLib.so* + bundled OpenSSL/curl/z (all patched with RPATH=$ORIGIN) |
/usr/lib/vmware-vix-disklib/bin64/ |
vixDiskCheck, vmware-vdiskmanager, vddkReporter |
/usr/lib/vmware-vix-disklib/include/ |
VDDK headers (for development only) |
/usr/lib/vmware-vix-disklib/doc/ |
VDDK HTML documentation |
9.4 OpenSSL 3.4.0 conflict — prevention and recovery
Older installers created /etc/ld.so.conf.d/vmware-vddk.conf pointing at /usr/lib/vmware-vix-disklib/lib64. On EL9 hosts whose system OpenSSL has been updated past 3.4.0, every system binary (rpm, dnf, flatpak, …) then loaded VMware’s bundled (older) libcrypto.so.3 and failed with:
flatpak: /usr/lib/vmware-vix-disklib/lib64/libcrypto.so.3:
version `OPENSSL_3.4.0' not found (required by /lib64/librpmio.so.9)
The current podheitor-vixdisklib-runtime RPM prevents this by patching every shared object with RPATH=$ORIGIN at build time, so VDDK finds its siblings without any global loader hint. The %post scriptlet also removes stale entries:
/etc/ld.so.conf.d/vmware-vddk.conf
/etc/ld.so.conf.d/vmware-vix-disklib.conf
/etc/ld.so.conf.d/vixlib*.conf
/etc/ld.so.conf.d/flatpack*.conf
/etc/ld.so.conf.d/flatpak-vddk*.conf
To recover a host that is already broken (and cannot even run dnf), run — offline is fine:
sudo rm -f /etc/ld.so.conf.d/vmware-vddk.conf
/etc/ld.so.conf.d/vmware-vix-disklib.conf
/etc/ld.so.conf.d/*vixlib*.conf
/etc/ld.so.conf.d/*flatpack*.conf
sudo ldconfig
# Verify the system libcrypto is back in front:
ldconfig -p | grep 'libcrypto.so.3 .*=> /lib64'
# Then dnf/rpm/flatpak work again:
sudo dnf check-update
9.5 Enable Changed Block Tracking (CBT)
CBT is mandatory for efficient incrementals and for replication.
Via govc:
export GOVC_URL="https://root:<PASSWORD>@<ESXI_IP>/sdk"
export GOVC_INSECURE=true
govc vm.change -vm <VM_NAME> -e ctkEnabled=TRUE
govc vm.change -vm <VM_NAME> -e scsi0:0.ctkEnabled=TRUE
# Repeat for each virtual disk (scsi0:1, scsi0:2, …)
Via vSphere Client: VM → Edit Settings → VM Options → Advanced → Configuration Parameters → set ctkEnabled=TRUE and scsi<N>:<M>.ctkEnabled=TRUE per disk, then take and delete a snapshot to materialize the change log.
9.6 Configure Bacula
Add the plugin directory to the FD (usually already set):
# /opt/bacula/etc/bacula-fd.conf
FileDaemon {
Name = vsphere-fd
Plugin Directory = /opt/bacula/plugins
Plugin Names = "podheitor-vsphere"
...
}
Then add FileSets / Jobs / Schedules on the Director (see §11 and §12). Restart both daemons:
sudo systemctl restart bacula-fd
sudo systemctl restart bacula-dir
9.7 Smoke test
# VDDK visible to the backend?
/opt/bacula/bin/podheitor-vsphere-backend --version
# Should print the build version and exit 0.
# Run a dry-run backup of a single VM:
echo "status" | bconsole
run job=Backup-<VM_NAME> yes
# watch progress:
status client=vsphere-fd
9.8 Optional: build from source
For developers / git clones:
sudo ./install.sh
install.sh will: detect and install the VDDK runtime RPM from releases/, remove legacy flatpack/vixlib/vmware-vddk entries under /etc/ld.so.conf.d/, build the Rust backend with cargo --release, and install the resulting binary + .so into /opt/bacula/.
9.9 Uninstall
sudo systemctl stop bacula-fd
sudo rpm -e podheitor-vsphere podheitor-vixdisklib-runtime
sudo systemctl start bacula-fd
State directories (/opt/bacula/working/podheitor/, /var/lib/podheitor/) are preserved across upgrades and removed only on full rpm -e.
10. Configuration Reference
Plugin String Format
Plugin = "podheitor-vsphere: key1=value1 key2=value2 ..."
All options can be specified directly in the FileSet Plugin directive.
Complete Options Table — Backup
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
host |
string | Yes | — | ESXi or vCenter hostname/IP |
username |
string | Yes | — | vSphere login name |
password |
string | Yes | — | vSphere login password |
datacenter |
string | Yes | — | Datacenter managed object name |
vm |
string | Yes | — | VM name to protect |
transport |
string | No | nbdssl |
nbd, nbdssl, hotadd, san |
quiesce |
bool | No | true |
Quiesce guest filesystem |
timeout |
int | No | 3600 |
Timeout in seconds |
include_disk |
multi | No | all | Include specific disks |
exclude_disk |
multi | No | none | Exclude specific disks |
keep_cbt |
bool | No | false |
Keep CBT after backup |
abort_on_error |
bool | No | false |
Abort on first error |
snapshot_delete_delay |
int | No | 0 |
Seconds to wait before deleting snapshot |
force_san |
bool | No | false |
Force SAN transport |
debug |
bool | No | false |
Enable debug logging |
config_file |
string | No | — | External configuration file |
Complete Options Table — Restore
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
new_vm_name |
string | No | original | Name for restored VM |
power_on |
bool | No | false |
Power on after restore |
datastore |
string | No | original | Target datastore name |
overwrite |
bool | No | false |
Overwrite existing VM |
no_network |
bool | No | false |
Disconnect NICs |
thin_provisioned |
bool | No | false |
Use thin disk provisioning |
resource_pool |
string | No | default | Target resource pool |
network_name |
string | No | original | Target network |
guest_id_override |
string | No | auto | Override guest OS type |
hw_version |
string | No | auto | Override HW version |
controller_type |
string | No | auto |
auto, ide, scsi, nvme |
Complete Options Table — Replication
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
mode |
string | Yes | — | Replication mode (see modes table) |
dr_host |
string | Yes* | — | DR receiver hostname/IP |
dr_port |
int | No | 9102 |
DR receiver TCP port |
dr_auth_token |
string | Yes* | — | Pre-shared authentication token |
dr_restore_points |
int | No | 5 |
Max snapshots on replica |
dr_replica_datastore |
string | No | auto | Datastore for replica |
push_interval |
int | No | 300 |
Push interval (seconds) |
push_apply_remote |
bool | No | true |
Apply deltas remotely |
max_retries |
int | No | 3 |
Max retries on failure |
retry_delay_sec |
int | No | 30 |
Delay between retries |
alert_after_failures |
int | No | 3 |
Alert threshold |
net_map |
multi | No | — | Network mapping rules |
reip |
multi | No | — | IP reconfiguration rules |
storage_map |
multi | No | — | Datastore mapping rules |
max_bandwidth_mbps |
int | No | 0 |
Bandwidth limit (0=unlimited) |
test_failover_network |
string | No | — | Isolated test network |
failover_vm |
string | No | auto | Target VM for failover |
dr_tls_cert |
string | No | — | TLS certificate path |
dr_tls_key |
string | No | — | TLS private key path |
dr_tls_insecure |
bool | No | false |
Accept self-signed certs |
Required for modes that communicate with DR receiver (cbt-push, seed, failover-, failback, reprotect).
11. Backup Configuration Examples
Example 1: Basic Full + Incremental Backup
# FileSet
FileSet {
Name = "vSphere-Backup-VM1"
Include {
Options { signature = MD5 }
Plugin = "podheitor-vsphere: host=192.168.1.100 username=root
password=MyP@ss datacenter=ha-datacenter vm=production-web
transport=nbdssl"
}
}
# Full backup weekly, Incremental daily
Schedule {
Name = "vSphere-Schedule"
Run = Full sun at 02:00
Run = Incremental mon-sat at 22:00
}
# Backup Job
Job {
Name = "Backup-Production-Web"
Type = Backup
Client = vsphere-fd
FileSet = "vSphere-Backup-VM1"
Storage = File1
Pool = File
Schedule = "vSphere-Schedule"
Messages = Standard
}
Example 2: Multiple VMs
FileSet {
Name = "vSphere-Backup-All"
Include {
Options { signature = MD5 }
Plugin = "podheitor-vsphere: host=192.168.1.100 username=root
password=MyP@ss datacenter=ha-datacenter vm=web-server
transport=nbdssl"
Plugin = "podheitor-vsphere: host=192.168.1.100 username=root
password=MyP@ss datacenter=ha-datacenter vm=db-server
transport=nbdssl quiesce=true"
Plugin = "podheitor-vsphere: host=192.168.1.100 username=root
password=MyP@ss datacenter=ha-datacenter vm=app-server
transport=nbdssl"
}
}
Example 3: SAN (LAN-Free) Backup
FileSet {
Name = "vSphere-SAN-Backup"
Include {
Options { signature = MD5 }
Plugin = "podheitor-vsphere: host=vcenter.prod.local username=admin@vsphere.local
password=VcP@ss datacenter=Production vm=critical-db
transport=san force_san=true"
}
}
Example 4: Selective Disk Backup
FileSet {
Name = "vSphere-Selective"
Include {
Options { signature = MD5 }
Plugin = "podheitor-vsphere: host=192.168.1.100 username=root password=MyP@ss
datacenter=ha-datacenter vm=multi-disk-vm transport=nbdssl
exclude_disk=Hard disk 3"
}
}
Example 5: Restore to New VM
# bconsole commands
restore jobid=<BACKUP_JOBID> where=/
# Then add these override options at the restore prompt:
# podheitor-vsphere: host=192.168.1.100 username=root password=MyP@ss
# datacenter=ha-datacenter new_vm_name=restored-vm power_on=yes
# datastore=datastore2 thin_provisioned=yes
12. Replication Configuration Examples
Example 1: CBT Push Replication (Incremental)
# FileSet for CBT push
FileSet {
Name = "vSphere-Repl-Push"
Include {
Options { signature = MD5 }
Plugin = "podheitor-vsphere: host=192.168.1.100 username=root password=MyP@ss
datacenter=ha-datacenter vm=production-web transport=nbdssl
mode=cbt-push dr_host=192.168.2.50 dr_port=9102
dr_auth_token=MySecureToken2026 dr_tls_insecure=yes"
}
}
# Schedule: push every 15 minutes
Schedule {
Name = "Repl-Push-15min"
Run = Incremental hourly at 0:00
Run = Incremental hourly at 0:15
Run = Incremental hourly at 0:30
Run = Incremental hourly at 0:45
}
# Job
Job {
Name = "Replicate-Production-Web"
Type = Backup
Client = vsphere-fd
FileSet = "vSphere-Repl-Push"
Storage = File1
Pool = File
Schedule = "Repl-Push-15min"
}
Example 2: Initial Seed
FileSet {
Name = "vSphere-Seed"
Include {
Options { signature = MD5 }
Plugin = "podheitor-vsphere: host=192.168.1.100 username=root password=MyP@ss
datacenter=ha-datacenter vm=production-web transport=nbdssl
mode=seed dr_host=192.168.2.50 dr_port=9102
dr_auth_token=MySecureToken2026"
}
}
Job {
Name = "Seed-Production-Web"
Type = Backup
Client = vsphere-fd
FileSet = "vSphere-Seed"
Storage = File1
Pool = File
}
Example 3: Planned Failover with Network Mapping
FileSet {
Name = "vSphere-Failover-Planned"
Include {
Options { signature = MD5 }
Plugin = "podheitor-vsphere: host=192.168.1.100 username=root password=MyP@ss
datacenter=ha-datacenter vm=production-web transport=nbdssl
mode=failover-planned
dr_host=192.168.2.50 dr_port=9102 dr_auth_token=MySecureToken2026
net_map=Production_Network=DR_Network
reip=0:192.168.2.10/24:192.168.2.1:8.8.8.8,8.8.4.4"
}
}
Example 4: DR Receiver
FileSet {
Name = "vSphere-DR-Receiver"
Include {
Options { signature = MD5 }
Plugin = "podheitor-vsphere: host=192.168.2.50 username=root password=DrP@ss
datacenter=ha-datacenter vm=production-web-replica transport=nbdssl
mode=dr-receiver dr_port=9102 dr_auth_token=MySecureToken2026
dr_tls_cert=/etc/podheitor/tls/server.crt
dr_tls_key=/etc/podheitor/tls/server.key"
}
}
Example 5: Complete DR Setup
# === PRIMARY SITE (Production) ===
# 1. Initial seed
Job { Name = "Seed-WebVM" ... FileSet = "vSphere-Seed" }
# 2. Continuous replication (every 5 min)
Job { Name = "Repl-WebVM" ... FileSet = "vSphere-Repl-Push" Schedule = "Every5Min" }
# 3. Status check (hourly)
Job { Name = "Status-WebVM" ... FileSet = "vSphere-Status" Schedule = "Hourly" }
# === DR SITE (Standby) ===
# 4. DR Receiver (long-running)
Job { Name = "DR-Receiver" ... FileSet = "vSphere-DR-Receiver" }
# === FAILOVER JOBS (manual trigger) ===
# 5. Test failover
Job { Name = "Test-Failover-WebVM" ... mode=failover-test }
# 6. Undo test
Job { Name = "Undo-Failover-WebVM" ... mode=failover-undo }
# 7. Planned failover (during maintenance window)
Job { Name = "Planned-Failover-WebVM" ... mode=failover-planned
net_map=... reip=... }
# 8. Emergency failover
Job { Name = "Emergency-Failover-WebVM" ... mode=failover-unplanned
net_map=... reip=... }
# 9. Permanent failover
Job { Name = "Permanent-Failover-WebVM" ... mode=failover-permanent
net_map=... reip=... }
# 10. Failback (after production restored)
Job { Name = "Failback-WebVM" ... mode=failback }
# 11. Re-protect
Job { Name = "Reprotect-WebVM" ... mode=reprotect }
13. User Manual & Day-2 Operations
This section walks through the everyday tasks an operator performs once the plugin is installed and the FileSets in §11–§12 have been added to bacula-dir.conf.
13.1 Common bconsole commands
| Task | Command (in bconsole) |
|---|---|
| List configured backup jobs | show jobs |
| List backup jobs of a client | list jobs client=vsphere-fd |
| Run a job on demand | run job=Backup-Production-Web yes |
| Run with explicit level | run job=Backup-Production-Web level=Full yes |
| See running job(s) | status client=vsphere-fd |
| Tail messages | messages |
| Find files in catalog | list files jobid=<JOBID> |
| Cancel a stuck job | cancel jobid=<JOBID> |
| Purge & delete a backup | delete jobid=<JOBID> |
13.2 Running a backup
echo "run job=Backup-Production-Web yes" | bconsole
# Then watch:
echo "status client=vsphere-fd" | bconsole
echo "messages" | bconsole
For a forced Full (e.g. after enabling CBT for the first time):
run job=Backup-Production-Web level=Full yes
After the first Full, subsequent Incrementals will only carry changed blocks reported by CBT (typically 1–5 % of the disk per day).
13.3 Restoring a VM (interactive)
* restore
Select item: 5 (Select the most recent backup for a client)
Defined Clients:
1: vsphere-fd
Select Client (File Daemon) resource: 1
The defined FileSet resources are:
1: vSphere-Backup-VM1
Select FileSet: 1
... bconsole picks the latest Full + chain of Incrementals
... it then drops you into the file selection tree.
$ mark *
$ done
Run Restore job
JobName: RestoreFiles
Bootstrap: /opt/bacula/working/...
Where: /tmp/bacula-restores
Plugin Options: *None*
OK to run? (yes/mod/no): mod
Parameters to modify:
...
12: Plugin Options
Select parameter to modify (1-13): 12
Please enter a Plugin Options string: podheitor-vsphere:
host=192.168.1.100 username=root password=MyP@ss
datacenter=ha-datacenter new_vm_name=production-web-restored
datastore=datastore2 thin_provisioned=yes power_on=yes
OK to run? (yes/mod/no): yes
The plugin re-uses the original VM hardware version, network adapter type, and guest OS unless you override via hw_version, network_name, guest_id_override, or controller_type.
13.4 Single-disk restore
Use include_disk (or exclude_disk) on the restore plugin string to restrict which VMDK is restored:
podheitor-vsphere: host=192.168.1.100 username=root password=MyP@ss
datacenter=ha-datacenter new_vm_name=onlydata
include_disk="Hard disk 2" datastore=datastore2 power_on=no
13.5 Cross-hypervisor restore (vSphere → Proxmox / Hyper-V)
Cross-restore is just a regular restore that targets a non-VMware host. Format conversion (VMDK ↔ VHDX ↔ QCOW2) is automatic — see §7.
# Restore a VMware backup onto a Proxmox host (FD running on PVE node)
restore client=proxmox-fd jobid=<BACKUP_JOBID>
Plugin Options: podheitor-proxmox: node=pve1 storage=local-lvm
new_vm_name=migrated-web vmid=120 power_on=no
13.6 Replication day-2 workflow
Once the seed (mode seed) has finished and you have a recurring job in cbt-push mode, replication is fully automatic. Day-2 operations are:
| Operation | When | bconsole |
|---|---|---|
| Check status | Anytime | run job=Status-WebVM yes |
| Test failover (non-destructive) | DR drill | run job=Test-Failover-WebVM yes |
| Undo test | After drill | run job=Undo-Failover-WebVM yes |
| Planned failover | Maintenance window | run job=Planned-Failover-WebVM yes |
| Unplanned failover | Outage | run job=Emergency-Failover-WebVM yes |
| Permanent failover | Permanent migration | run job=Permanent-Failover-WebVM yes |
| Failback to source | Source restored | run job=Failback-WebVM yes |
| Re-protect (restart fwd repl) | After failback | run job=Reprotect-WebVM yes |
13.7 DR receiver lifecycle
The DR receiver runs as a long-lived job (mode=dr-receiver) on the DR site’s Bacula FD. Recommended setup:
Job {
Name = "DR-Receiver-Always-On"
Type = Backup
Client = vsphere-dr-fd
FileSet = "vSphere-DR-Receiver"
Schedule = "Boot" # starts on FD boot
Max Run Sched Time = 0 # never time out
Allow Duplicate Jobs = no
Reschedule On Error = yes
Reschedule Interval = 60
Reschedule Times = 0 # forever
}
SIGTERM (e.g. bconsole: cancel jobid=<n>) triggers a graceful shutdown — in-flight deltas are flushed and the snapshot lock on the replica is released before exit.
13.8 TLS certificates for the DR protocol
For production, generate a server cert for the DR receiver host:
openssl req -x509 -newkey rsa:4096 -nodes -days 3650
-keyout /etc/podheitor/tls/server.key
-out /etc/podheitor/tls/server.crt
-subj "/CN=vsphere-dr-fd.dr.local"
chmod 600 /etc/podheitor/tls/server.key
Reference both files via dr_tls_cert= and dr_tls_key= on the mode=dr-receiver FileSet, and remove dr_tls_insecure=yes on the sender side once the cert is trusted.
13.9 Token rotation
Tokens are read from the Plugin string at job start, so rotation is:
- Generate a new token (
openssl rand -hex 32). - Update both
dr_auth_token=strings (sender + receiver FileSets). bconsole: reload.- Cancel the running DR receiver job — Bacula will reschedule it
automatically (per §13.7) and the new token takes effect.
13.10 Log locations
| File | What |
|---|---|
/opt/bacula/working/bacula-fd.log |
Bacula FD log (plugin output multiplexed) |
/opt/bacula/working/podheitor/cbt/<vm>.json |
Per-VM CBT change-IDs |
/var/lib/podheitor/replication/<vm>.json |
Replication state, last sync time, restore points |
journalctl -u bacula-fd -e |
systemd-captured stderr |
Add debug=true to a Plugin string for verbose backend tracing (routed to the Bacula FD log via PTCOMM).
14. Sizing & Requirements
Hardware Requirements
| Component | Minimum | Recommended | High Performance |
|---|---|---|---|
| CPU | 2 cores | 4 cores | 8+ cores |
| RAM | 4 GB | 8 GB | 16+ GB |
| Disk (FD) | 20 GB | 50 GB | 100+ GB |
| Network | 1 Gbps | 10 Gbps | 25 Gbps |
Storage Sizing
| Scenario | Estimate |
|---|---|
| Full backup | ~1:1 with used data (compression by Bacula SD) |
| Incremental | 1-5% of total disk per day (typical) |
| Replica | Equal to source VM disk size |
| Restore points | ~1-5% per snapshot (depends on change rate) |
Network Bandwidth
| Scenario | Bandwidth Needed |
|---|---|
| Daily backup (100 GB VM) | ~5 GB incremental → ~11 Mbps sustained |
| Replication (100 GB VM, 5-min RPO) | ~100 MB/interval → ~3 Mbps sustained |
| Full seed (100 GB VM) | 100 GB → ~2.2 hrs at 100 Mbps |
Concurrent VM Limits
| Configuration | Recommended Max |
|---|---|
| 2 CPU / 4 GB RAM | 2-3 concurrent VMs |
| 4 CPU / 8 GB RAM | 5-10 concurrent VMs |
| 8 CPU / 16 GB RAM | 15-20 concurrent VMs |
15. Compatibility Matrix
VMware Compatibility
| Component | Version | Tested | Notes |
|---|---|---|---|
| ESXi | 7.0 | ✅ | All update levels |
| ESXi | 8.0 | ✅ | All update levels |
| ESXi | 8.0U3e | ✅ | Primary test platform |
| vCenter | 7.0 | ✅ | Optional — standalone ESXi supported |
| vCenter | 8.0 | ✅ | Optional |
| VDDK | 8.0.x | ✅ | Minimum recommended |
| VDDK | 9.0.1 | ✅ | Primary test platform |
Operating System Compatibility (FD Server)
| OS | Version | Tested | Notes |
|---|---|---|---|
| Oracle Linux | 9.x | ✅ | Primary test platform (9.5) |
| RHEL | 9.x | ✅ | Same binary as OL9 |
| Rocky Linux | 9.x | ✅ | Same binary as OL9 |
| AlmaLinux | 9.x | ✅ | Same binary as OL9 |
Bacula Compatibility
| Component | Version | Tested |
|---|---|---|
| Bacula Community | 15.0.3 | ✅ |
| Bacula Community | 15.0.x | Expected compatible |
Guest OS Support
| Guest OS | Backup | Restore | Quiesce | CBT | Re-IP |
|---|---|---|---|---|---|
| Linux (any) | ✅ | ✅ | ✅* | ✅ | ✅* |
| Windows Server | ✅ | ✅ | ✅* | ✅ | ✅* |
| Windows 10/11 | ✅ | ✅ | ✅* | ✅ | ✅* |
| FreeBSD | ✅ | ✅ | ⚠️ | ✅ | ❌ |
| Other | ✅ | ✅ | ⚠️ | ✅ | ❌ |
*Requires VMware Tools installed in the guest.
16. Test Results
Test Environment
| Component | Details |
|---|---|
| ESXi | 8.0U3e, standalone, IP 192.168.15.91 |
| FD Server | Oracle Linux 9.5, Bacula 15.0.3 |
| VDDK | 9.0.1 |
| Test VMs | Alpine (381 MB), TinyCore, CirrOS, Multi-Disk |
Backup & Restore Tests
| # | Test | Result | Details |
|---|---|---|---|
| 1 | Full backup (NBDSSL) | ✅ PASS | All 4 VMs |
| 2 | Incremental backup (CBT) | ✅ PASS | Only changed blocks |
| 3 | Full VM restore | ✅ PASS | Original name + new name |
| 4 | Restore to different datastore | ✅ PASS | Thin provisioned |
| 5 | Cross-restore (vSphere → Hyper-V) | ✅ PASS | VMDK → VHDX |
| 6 | Cross-restore (Hyper-V → vSphere) | ✅ PASS | VHDX → VMDK |
Replication Tests (April 20, 2026)
| # | Test | JobId | Status | Data |
|---|---|---|---|---|
| 1 | Replication Status | 881 | ✅ T | 870 B |
| 2 | CBT Push (Incremental) | 882 | ✅ T | 377 MB |
| 3 | Failover Test | 883 | ✅ T | 137 B |
| 4 | Failover Undo | 884 | ✅ T | 137 B |
| 5 | Failover Planned | 885 | ✅ T | 140 B |
| 6 | Failover Undo (2) | 887 | ✅ T | 137 B |
| 7 | Failover Unplanned | 889 | ✅ T | 142 B |
| 8 | Failover Permanent | 892 | ✅ T | 142 B |
| 9 | Seed (Full Sync) | 893 | ✅ T | 377 MB |
| 10 | Failback | 894 | ✅ T | 2.1 GB |
| 11 | Reprotect | 895 | ✅ T | 1,795 B |
| 12 | Final Status | 897 | ✅ T | 870 B |
Result: 12/12 tests PASSED — 100% success rate
Restore Validation Matrix (v1.4.1, April 29 2026)
End-to-end restore-path lab on ESXi 8.0.3 (192.168.15.91, no vCenter) + Bacula 15.0.3 FD/SD/Director (192.168.15.105). Source VM vm-alpine1 (Alpine 3.21, ext4 with SYSLINUX VBR; live snapshot).
| # | Path | JobId | Result | Notes |
|---|---|---|---|---|
| 1 | Full backup (NBDSSL + CBT enable) | 6425 | ✅ T | changeId persisted: …/85 |
| 2 | Full → filesystem-restore (Where=/tmp/restore) |
6427 | ✅ T | 4 files / 2.147 GB / 6 s, 0 FD errors |
| 3 | Restored VMDK byte-correctness | — | ✅ | mount -o ro,loop,noload; /etc/alpine-release = 3.21.0 |
| 4 | Incremental backup (CBT) | 6429 | ✅ T | changeId persisted: …/86 |
| 5 | Incremental → filesystem-restore | 6431 | ✅ T | matches Full restore byte-for-byte |
| 6 | Hypervisor-restore safety (source VM exists) | 6434/6435 | ✅ refused | “VM already exists; use overwrite=yes or different where name” |
| 7 | Backup with new_vm_name=vm-alpine1-restored baked in |
6440 | ✅ T | catalog-frozen plugin string |
| 8 | Hypervisor-restore (recreate on ESXi via NBD/SSL) | 6442 | ✅ T | created moref=71, VMDK uploaded |
| 9 | Recreated VM power-on / boot | — | ✅ | bootTime set, uptimeSeconds=118, CPU 29 MHz, mem 220 MB |
Result: 9/9 paths PASSED. All three v1.4.1 fixes (path doubling, Where= capture, directory-writer EOD) confirmed against live workload.
17. Comparison with Enterprise Solutions
Feature Comparison
| Feature | PodHeitor BRC | Bacula Enterprise | Veeam B&R | Commvault |
|---|---|---|---|---|
| Image Backup | ✅ | ✅ | ✅ | ✅ |
| CBT Support | ✅ | ✅ | ✅ | ✅ |
| Incremental | ✅ | ✅ | ✅ | ✅ |
| SAN Transport | ✅ | ✅ | ✅ | ✅ |
| Full VM Restore | ✅ | ✅ | ✅ | ✅ |
| Single-Disk Restore | ✅ | ✅ | ✅ | ✅ |
| Cross-Hypervisor | ✅ | ❌ | ⚠️ | ⚠️ |
| VM Replication | ✅ | ❌ | ✅ | ✅ |
| DR Failover | ✅ (10 modes) | ❌ | ✅ | ✅ |
| Network Mapping | ✅ | ❌ | ✅ | ✅ |
| Re-IP on Failover | ✅ | ❌ | ✅ | ✅ |
| TLS DR Protocol | ✅ | N/A | ✅ | ✅ |
| Bacula Community | ✅ | ❌ | N/A | N/A |
| Cost | $$ | $$$$ | $$$$$ | $$$$$ |
Cost Advantage
Bring your Bacula Enterprise, Veeam, Commvault or Netbackup quote or renewal proposal. We offer at least 50% discount, with far more features.
18. Troubleshooting
18.1 OPENSSL_3.4.0 not found from rpm / dnf / flatpak
Cause: a legacy /etc/ld.so.conf.d/ entry is forcing every process to load VMware’s older libcrypto.so.3. See §9.4 for the one-liner recovery.
18.2 libvixDiskLib.so not found in standard paths
The backend searches:
/usr/lib/vmware-vix-disklib/lib64/usr/lib64/vmware-vix-disklib/opt/bacula/lib/vix
If the VDDK runtime RPM is installed, path #1 will contain libvixDiskLib.so. If you installed VDDK manually in an unusual location, set the env var VDDK_LIB_PATH=/full/path/libvixDiskLib.so in the FD service unit:
sudo systemctl edit bacula-fd
# add:
# [Service]
# Environment=VDDK_LIB_PATH=/opt/vddk/lib64/libvixDiskLib.so
sudo systemctl daemon-reload && sudo systemctl restart bacula-fd
18.3 Backup runs but produces 0 bytes on Incremental
CBT is not enabled or was reset. Verify:
govc vm.info -vm.ipath /ha-datacenter/vm/<VM_NAME> | grep -i ctk
# Must show ctkEnabled = true
Then take + delete a snapshot to materialize the change log, and run a fresh Full.
18.4 dlopen(libvixDiskLib.so): symbol lookup error
The VDDK .so did not find one of its bundled siblings (libcrypto.so.3, libssl.so.3, libcurl.so.4, …). Confirm the RPATH was applied:
readelf -d /usr/lib/vmware-vix-disklib/lib64/libvixDiskLib.so.9 | grep PATH
# Expected:
# 0x0000000000000001 (RUNPATH) Library runpath: [$ORIGIN]
If missing, re-install podheitor-vixdisklib-runtime — the RPM’s %install applies patchelf --set-rpath '$ORIGIN' to every .so in lib64/.
18.5 Replication delta applies but VM won’t boot after failover
Re-check that the net_map= / reip= on the failover FileSet point to networks and IPs that actually exist at the DR site. An unmapped port group causes VMware to silently disconnect the NIC at power-on.
18.6 “Authentication failed” on DR receiver
dr_auth_token= must match byte-for-byte on sender and receiver. The comparison is constant-time (XOR-based) to prevent timing attacks, so the only way it fails is a real mismatch. If you recently rotated the token, see §13.9.
18.7 Backups slow or stuck in connecting
Check transport:
nbdsslover WAN with high latency — switch tosanif possible,
or hotadd if the FD is itself a VM on the same ESXi cluster.
- Firewall between FD and ESXi — the NBD port (902) must be open on
top of 443 when using nbd / nbdssl.
19. Licensing & Contact
License
Copyright © 2026 Heitor Faria — All Rights Reserved
This software is proprietary. All source code, binaries, and documentation are the exclusive property of Heitor Faria. Unauthorized copying, distribution, modification, or reverse engineering is strictly prohibited.
Contact
Heitor Faria
- 📧 Email: heitor@opentechs.lat
- 📱 Phone: +1 786 726-1749
- 📱 WhatsApp: +55 61 98268-4220
Sales Inquiries
For pricing, licensing, proof-of-concept, and technical demonstrations, contact us directly. Volume discounts and multi-year agreements available.
Special Offer: Bring your existing Bacula Enterprise, Veeam, Commvault, or Netbackup quote or renewal proposal. We guarantee at least 50% discount, with far more features.
PodHeitor vSphere BRC Plugin — Whitepaper v1.4.1 — April 2026 VMware, vSphere, ESXi, VADP, VDDK are trademarks of Broadcom/VMware. Bacula is a trademark of Kern Sibbald. All other trademarks are property of their respective owners.
Disponível em:
Português (Portuguese (Brazil))
English
Español (Spanish)