We're dealing with a classic constrained environment where:
- Application servers cannot initiate outbound connections to jump hosts
- All access must originate from jump boxes
- Direct server-to-server transfer is blocked by firewall rules
- Storage limitations prevent using jump boxes as temporary staging areas
The key is establishing a reverse tunnel chain that leverages the jump boxes' interconnectivity. Here's the step-by-step approach:
# On QA Jump Box (initiating the tunnel chain):
ssh -R 2222:localhost:22 dev_jump_user@dev_jump_box
# On Development Jump Box (creating the final hop):
ssh -L 3333:dev_app_server:22 -p 2222 qa_jump_user@localhost
With tunnels established, we can now perform direct data transfer:
# From QA Application Server (after tunnels are up):
rsync -avz -e 'ssh -p 3333' dev_app_user@localhost:/source/path /destination/path
For 670GB of data, consider these enhancements:
- Compression: Add
-z
flag to rsync - Bandwidth limiting:
--bwlimit=50000
(50MB/s) - Checksum verification:
-c
for critical data - Resume capability:
--partial --progress
Here's a robust Bash implementation:
#!/bin/bash
# Establish tunnel chain
ssh -fN -R 2222:localhost:22 dev_jump_user@dev_jump_box
ssh -fN -L 3333:dev_app_server:22 -p 2222 qa_jump_user@localhost
# Verify tunnel connectivity
nc -z localhost 3333 || { echo "Tunnel failed"; exit 1; }
# Execute transfer with progress monitoring
rsync -avz --stats --human-readable --progress \
-e 'ssh -p 3333 -o StrictHostKeyChecking=no' \
dev_app_user@localhost:/source/path /destination/path
# Cleanup
pkill -f "ssh -fN -R 2222"
pkill -f "ssh -fN -L 3333"
When tunnels aren't feasible:
- Netcat over SSH:
tar cvzf - /source | ssh jump_box "nc -l 1234" | ssh qa_box "nc dev_box 1234 | tar xvzf -"
- SOCKS proxy chaining
- SSH ControlMaster for persistent connections
Always:
- Use SSH key authentication
- Set appropriate timeout values
- Restrict port forwarding ranges
- Monitor tunnel processes
In complex enterprise environments, we often encounter strict firewall rules that create data transfer challenges. Here's the exact topology we're dealing with:
Client → DEV_Jump_Box (10.0.1.10) → DEV_App_Server (10.0.2.20) ↓ Client → QA_Jump_Box (10.0.1.11) → QA_App_Server (10.0.3.30)
The critical constraints:
- App servers can't initiate connections to jump boxes
- Direct app-to-app communication is blocked
- Jump boxes can communicate with each other
Since we can't establish forward tunnels from app servers, we'll use reverse SSH tunnels through the jump boxes:
# On DEV Application Server (run as persistent background process): ssh -fNTR 2222:localhost:22 user@DEV_Jump_Box # On QA Application Server: ssh -fNTR 3333:localhost:22 user@QA_Jump_Box
This creates endpoints on each jump box that we can chain together.
From your local machine (or CI/CD server):
# First hop: Local to DEV jump box ssh -L 4000:localhost:2222 user@DEV_Jump_Box # Second hop: DEV jump box to QA jump box ssh -L 5000:QA_Jump_Box:3333 user@DEV_Jump_Box # Final connection point ssh -p 5000 user@localhost
With tunnels established, we can use rsync for efficient transfer:
rsync -avz --progress -e "ssh -p 5000" /source/path/ user@localhost:/destination/path/
For better performance with large files:
rsync -avz --progress --bwlimit=50000 --partial \ -e "ssh -p 5000 -c aes128-gcm@openssh.com" \ /source/path/ user@localhost:/destination/path/
Simplify recurrent connections with ~/.ssh/config:
Host dev-jump HostName DEV_Jump_Box User service-account IdentityFile ~/.ssh/tunnel_key Host qa-jump HostName QA_Jump_Box User service-account IdentityFile ~/.ssh/tunnel_key Host dev-app-via-jump HostName localhost Port 2222 ProxyJump dev-jump Host qa-app-via-jump HostName localhost Port 3333 ProxyJump qa-jump
For long-running transfers, use autossh to maintain tunnels:
# On both app servers: autossh -M 0 -o "ServerAliveInterval 30" -o "ServerAliveCountMax 3" \ -NTR 2222:localhost:22 user@DEV_Jump_Box
Combine with tmux/screen for session persistence:
tmux new-session -d -s tunnel 'autossh [...]'
If tunnel stability is problematic, use jump boxes as temporary relays:
# On DEV jump box: ssh user@DEV_App_Server "tar cf - /source/path" | \ ssh user@QA_Jump_Box "ssh user@QA_App_Server 'tar xf - -C /destination'"
This streams data through the jump boxes without requiring local storage.