Zelta relies on SSH for communication between replication partners. This document covers SSH configuration from basic key setup through advanced features like agent forwarding, jump hosts, and connection multiplexing.
As of version 1.1, Zelta does not need to be installed on replication partners. You can run Zelta from a dedicated bastion host that orchestrates replication between remote systems. This architecture provides exceptional security and operational flexibility.
This guide will help you set up:
Before configuring SSH, ensure you have:
backupuser throughout this guide)For modern ZFS implementations (OpenZFS 2.2+, FreeBSD 14+):
On the source system:
zfs allow -u backupuser hold,send,bookmark,snapshot sink
On the target system:
zfs allow -u backupuser receive:append,create,mount,readonly,clone,rename,volmode tank/Backups
Note: Older ZFS implementations, including most Linux distributions as of 2025, do not support the receive:append delegation. For these systems, you may need to grant broader receive permissions or use root access. Consult the ZFS Allow Configuration documentation for platform-specific guidance.
If you're new to SSH key authentication, here's a minimal configuration to get started.
On the system where you'll run Zelta (your bastion or management host):
ssh-keygen -t ed25519 -C "zelta-backup-key"
Follow the prompts. For automated replication, you may choose to use a passphrase-protected key with ssh-agent, or an unprotected key in a secure environment.
Use ssh-copy-id to install your public key on each replication partner:
# Copy to source system
ssh-copy-id backupuser@source
# Copy to target system
ssh-copy-id backupuser@target
Verify you can connect without a password:
ssh backupuser@source
ssh backupuser@target
If you can authenticate successfully, you're ready to proceed.
For more detailed SSH setup guidance, see the OpenSSH documentation or your operating system's handbook.
A Zelta Bastion is a dedicated, locked-down system that orchestrates replication between remote hosts without storing SSH keys on the replication partners themselves. This architecture provides several advantages:
Zelta's default behavior is pull replication: the target system pulls data from the source. This means:
send permissionsreceive permissionsWhen running from a bastion where both source and target are remote, you have three options:
--push): Bastion SSHs to source, source SSHs to target--sync-direction=0): Bastion SSHs to both, data flows through bastionFor most scenarios, pull replication with agent forwarding (described below) provides the best balance of security and performance.
Agent forwarding allows the bastion's SSH credentials to be used by remote systems without copying keys to those systems. This is how you achieve a fully locked-down bastion with no SSH keys stored on replication partners.
Here's what makes this remarkable: your source and target systems can authenticate to each other using your bastion's credentials, but they never have access to the private key itself. The bastion can be an OpenBSD system with minimal attack surface, and your replication partners only need non-destructive ZFS permissions.
This is a security posture your cloud provider cannot offer.
Add to ~/.ssh/config:
Host source target
ForwardAgent yes
Or for all hosts:
Host *
ForwardAgent yes
Start your SSH agent and add your key:
eval $(ssh-agent)
ssh-add ~/.ssh/id_ed25519
Edit /etc/ssh/sshd_config on both source and target systems:
Match User backupuser
AllowAgentForwarding yes
Reload the SSH daemon:
# FreeBSD
service sshd reload
# Linux (systemd)
systemctl reload sshd
# Linux (init)
/etc/init.d/ssh reload
The key test is verifying that the target can reach the source using your bastion's forwarded credentials.
For pull replication (target pulls from source):
From your bastion, SSH to the target and then to the source:
ssh -J target source
Or test the full chain:
ssh backupuser@target
# Now from the target:
ssh backupuser@source
If this succeeds, your agent forwarding is working correctly.
Test with Zelta:
zelta match backupuser@source:sink/dataset backupuser@target:tank/Backups/dataset
If zelta match can compare the dataset trees, your SSH configuration is correct and you're ready to replicate.
Replication partners need network connectivity to each other. This can be direct connectivity, a VPN, or SSH jump hosts (ProxyJump).
SSH jump hosts provide an alternative to VPNs with excellent performance characteristics. In our testing, ProxyJump outperformed all tested VPN solutions including WireGuard for ZFS replication workloads.
To configure a jump host, add to your ~/.ssh/config:
Host *.internal
ProxyJump jumphost.example.com
Or for a specific host:
Host target
Hostname 10.2.19.77
User backupuser
ProxyJump jumphost.example.com
With this configuration, ssh target automatically routes through the jump host.
Jump hosts can be chained:
Host target
ProxyJump bastion.example.com,internal-jump.local
If you prefer VPNs, ensure:
SSH cipher selection can significantly impact replication performance. Modern CPUs often include hardware acceleration for AES encryption (AES-NI on x86_64).
Add to ~/.ssh/config:
Host *
Ciphers aes128-ctr,aes256-ctr
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256
MACs umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com
Compression no
Note on compression: ZFS datasets are typically already compressed. SSH compression adds CPU overhead with minimal benefit for ZFS replication. We recommend leaving it disabled.
ControlMaster allows SSH to reuse existing connections, dramatically reducing the overhead of Zelta's "look before you leap" approach. Since Zelta makes multiple SSH calls per operation, ControlMaster provides substantial performance improvements.
Add to ~/.ssh/config:
Host *
ControlMaster auto
ControlPath ~/.ssh/cm-%r@%h:%p
ControlPersist 5m
This configuration:
ControlMaster auto)~/.ssh/ with a unique name per connectionControlPersist 5m)Adjust ControlPersist based on your replication frequency. For zelta policy loops running every 5 minutes, a 5-10 minute persist time works well.
Here's a complete ~/.ssh/config for a Zelta Bastion:
# Global defaults
Host *
ControlMaster auto
ControlPath ~/.ssh/cm-%r@%h:%p
ControlPersist 10m
Ciphers aes128-ctr,aes256-ctr
KexAlgorithms curve25519-sha256,ecdh-sha2-nistp256
MACs umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com
Compression no
ForwardAgent yes
# Internal network via jump host
Host *.internal
ProxyJump jumphost.example.com
User backupuser
# Specific replication partners
Host source
Hostname source.internal
User backupuser
Host target
Hostname target.internal
User backupuser
If you encounter issues with SSH configuration:
ssh -v backupuser@host (verbose output helps diagnose issues)zfs allow dataset shows delegated permissionsssh-add -l should show your key on the bastionzelta match to verify end-to-end connectivityFor Zelta-specific questions, see:
For SSH-specific questions, consult:
man ssh_config - SSH client configurationman sshd_config - SSH server configuration