VCF 9.1 SFTP Backup Target — Build on VMware Workstation, Migrate to VCF
Summary runbook covering: build in Workstation → export/import to vCenter → harden sshd for FIPS → verify → register in VCF Operations. Based on ITQ’s VCF 9.1 Backup Target guide, with fixes for the errors hit during this build.
1. Provision the VM in Workstation
- Ubuntu Server 24.04 LTS, minimal install
- Set virtual hardware version at creation time to one your target vSphere/VCF version supports (don’t accept Workstation’s newest default — it can exceed what vSphere understands). Check vSphere 8/9 compatibility; hardware version 19 or 20 is typically safe.
- Sizing: CPU/RAM are trivial for SFTP; disk sized for your retention window (daily SDDC Manager/vCenter backups, hourly NSX backups, 7-day retention each) — start with a few hundred GB and adjust to your environment.
2. Base OS setup
sudo apt update && sudo apt upgrade -y
sudo timedatectl set-timezone <your-tz>
sudo hostnamectl set-hostname sftp01.yourdomain.com
Confirm/install OpenSSH server:
sudo apt install openssh-server -y
sudo systemctl enable --now ssh
Install open-vm-tools instead of relying on Workstation’s bundled tools (needed for a clean run under vSphere later):
sudo apt install open-vm-tools -y
Set the NIC to VMXNET3 in the VM’s virtual hardware settings (not the Workstation default e1000) for proper vSphere performance.
3. Create the service account and chroot jail
sudo groupadd sftpbackup
sudo useradd -g sftpbackup -s /sbin/nologin -m svc-vcf-bck
sudo passwd svc-vcf-bck # strong password — store in password manager
sudo mkdir -p /sftp/svc-vcf-bck/backups
sudo chown root:root /sftp/svc-vcf-bck
sudo chmod 0755 /sftp/svc-vcf-bck
sudo chown svc-vcf-bck:sftpbackup /sftp/svc-vcf-bck/backups
sudo chmod 0750 /sftp/svc-vcf-bck/backups
The chroot directory itself must stay root-owned and not group/world writable — sshd refuses the chroot at login otherwise. Only the backups subfolder belongs to the service account.
4. Configure sshd (FIPS-ready, hardened)
Back up first:
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
Edit /etc/ssh/sshd_config. Crypto directives must sit above the first Match block — they’re global-only and sshd won’t start if they land inside a Match block. Directive names are plural — KexAlgorithms and HostKeyAlgorithms, not the singular form (a common typo that throws Bad configuration option):
# --- VCF backup target crypto (must stay above any Match block) ---
KexAlgorithms ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,curve25519-sha256
MACs hmac-sha2-256,hmac-sha2-256-etm@openssh.com,hmac-sha1
Ciphers aes256-ctr,aes256-gcm@openssh.com,chacha20-poly1305@openssh.com
HostKeyAlgorithms rsa-sha2-512,rsa-sha2-256,ecdsa-sha2-nistp256,ssh-ed25519
Subsystem sftp internal-sftp
Match Group sftpbackup
ChrootDirectory /sftp/%u
ForceCommand internal-sftp
AllowTcpForwarding no
X11Forwarding no
PasswordAuthentication yes
No spaces after commas in the algorithm lists — a stray space (or a smart-quote/non-breaking space from a copy-paste) causes
Bad SSH2 KexAlgorithms. Verify withcat -Aorod -con the line if you hit that error, and cross-check supported algorithms withssh -Q kex.
Validate before restarting, and keep a root session open in case of a mistake:
sudo sshd -t
sudo systemctl restart ssh
5. Firewall (ufw)
Scope to the actual VCF management network CIDR(s) where the services-runtime pods, SDDC Manager, NSX Manager, and VCF Operations live — find these under VCF Operations → Build → Lifecycle → Components → VCF Services Runtime → Nodes. Don’t scope to a few named hosts — connections come from arbitrary pod IPs across several services, so a narrow rule breaks the moment a service restarts onto a new address.
sudo ufw allow from <mgmt-cidr> to any port 22 proto tcp
ICMP isn’t a supported proto in ufw’s simple syntax (unsupported protocol icmp) — it has to go directly into /etc/ufw/before.rules:
sudo nano /etc/ufw/before.rules
Add above the existing default ICMP lines (order matters — allow rule first):
-A ufw-before-input -p icmp -s <mgmt-cidr> --icmp-type echo-request -j ACCEPT
-A ufw-before-input -p icmp --icmp-type echo-request -j DROP
sudo ufw reload
No outbound rule is needed — ufw’s default outgoing policy is allow, so echo-replies go out automatically (sudo ufw status verbose to confirm).
If you opened SSH/ICMP to “any” for quick testing: tag the rule so it doesn’t get forgotten (
ufw allow 22/tcp comment 'TEMP - testing only'), keep the exposure window short, and swap it for the scoped CIDR rule immediately after verification — password auth + backup data on this box makes an open port 22 worth avoiding beyond a brief test window.
6. Shut down and export from Workstation
sudo shutdown -h now
In Workstation: File → Export to OVF. This packages the VMDK and config into a format vCenter’s OVF deploy wizard reads directly.
7. Import into vCenter (NIC disconnected first)
vCenter → right-click target cluster/folder → Deploy OVF Template → select the exported OVF/OVA → choose datastore, map the network to the correct management-adjacent segment.
Before powering on, disconnect the NIC (uncheck “Connect at power on” on the network adapter in the VM’s Edit Settings, or untick “Connected” if it’s already powered off). This keeps the VM off the network while it still carries whatever IP/netplan config it had from Workstation, so it can’t collide with another host or leak onto the production segment with stale settings.
Power on, console in, and set the real IP inside the OS while still disconnected:
sudo nano /etc/netplan/*.yaml
network:
version: 2
ethernets:
ens160: # confirm the interface name with `ip a`
addresses:
- 10.0.10.50/24
routes:
- to: default
via: 10.0.10.1
nameservers:
addresses: [10.0.10.10]
sudo netplan apply
Confirm the config took before going any further:
ip a
ip route
resolvectl status # or cat /etc/resolv.conf
Only once the IP, gateway, and DNS are correct do you reconnect the NIC — in vCenter, edit settings and tick Connect (or Connect at power on for future reboots). At that point the VM lands on the network already carrying its final, correct address, instead of briefly appearing with Workstation-era settings.
Create the DNS A + PTR records for the new IP/hostname now if you haven’t already, since the verification steps later depend on them resolving correctly.
8. Host-key note
Treat this as a one-time build-and-move, not a template. If you later clone this VM to stamp out more SFTP servers, the host keys (and
/etc/machine-id) need to be regenerated per clone, or every clone shares the same SSH identity. Do the SSH/SFTP verification below only after the VM has its final IP/hostname — don’t rely on a fingerprint fetched while it was still in Workstation.
9. Confirm host keys and sizes
ssh-keygen -lf /etc/ssh/ssh_host_ecdsa_key.pub # expect 256
ssh-keygen -lf /etc/ssh/ssh_host_rsa_key.pub # expect >= 2048
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub
10. Verify before registering in VCF
Force the FIPS negotiation from a Linux client on the management network (not native Windows ssh-keyscan — it’s broken against modern servers due to an unsupported post-quantum KEX it advertises but can’t perform):
ssh -o KexAlgorithms=ecdh-sha2-nistp256 \
-o MACs=hmac-sha2-256 \
-o HostKeyAlgorithms=ecdsa-sha2-nistp256 \
-o Ciphers=aes256-ctr \
svc-vcf-bck@sftp01.yourdomain.com
Getting “This service allows sftp connections only” here is success, not an error — it confirms the crypto negotiated and the account authenticated;
ForceCommand internal-sftpis just refusing the interactive shellsshtried to open, exactly as configured.
Now do the real functional test with sftp (create the local test file before connecting, or the local put fails with stat testfile.txt: No such file or directory — that error is about the file missing on your local machine, not the server):
echo "vcf backup target test" > testfile.txt
sftp -o KexAlgorithms=ecdh-sha2-nistp256 \
-o MACs=hmac-sha2-256 \
-o HostKeyAlgorithms=ecdsa-sha2-nistp256 \
-o Ciphers=aes256-ctr \
svc-vcf-bck@sftp01.yourdomain.com
sftp> cd backups
sftp> put testfile.txt
sftp> ls
sftp> pwd
pwd here (e.g. /backups) is the chroot-relative path to enter in the VCF wizard — not the server’s real filesystem path.
11. Register in VCF Operations
- Fleet/management services config: Build → Lifecycle → Backup & Restore
- SDDC Manager + NSX config: Operate → Administration → SDDC Manager (separate screen — both need setting)
- Host: use the server’s IP, not FQDN, if the name could ever resolve to more than one box (load balancer, round-robin DNS) — an FQDN fronting multiple hosts breaks host-key pinning even when the target itself is fine
- Directory: the chroot-relative path from step 10 (e.g.
/backups) - Click Fetch Fingerprint, compare against the
ssh-keygen -lfoutput from step 9 - vCenter backup is separate again — configure manually per instance in each vCenter’s VAMI (
https://<vcenter-fqdn>:5480→ Backup)
12. Firewall cleanup
If step 5 or the verification stage used a temporary “any” rule, close it now and confirm only the scoped CIDR rule remains:
sudo ufw status numbered
sudo ufw delete <temp-rule-number>
sudo ufw status verbose
Quick error reference (from this build)
| Error | Cause | Fix |
|---|---|---|
Bad configuration option: KexAlgorithm / HostKeyAlgorithm | Directive name missing trailing s | Use KexAlgorithms / HostKeyAlgorithms |
Bad SSH2 KexAlgorithms | Stray space after a comma, or an unsupported algorithm name | Remove spaces around commas; cross-check with ssh -Q kex |
unsupported protocol icmp (ufw) | ufw’s simple allow syntax doesn’t support ICMP | Edit /etc/ufw/before.rules directly |
This service allows sftp connections only | Expected — ForceCommand internal-sftp blocking a shell session | Not an error; re-test with sftp instead of ssh |
stat testfile.txt: No such file or directory on put | Local file doesn’t exist on the client machine | Create it locally first (echo ... > testfile.txt) |