Triple‑Hop SSH, Clean & Secure: A Blog‑Ready Tutorial (All Sensitive Info Redacted)
Author: Jiahong — This guide hides all real IPs, domains, usernames, and keys. Replace placeholders as needed.
TL;DR
- Use ProxyJump (-J) for a clean multi‑hop SSH:
local → Jump #1 (VPS) → Jump #2 (Edge/RPi) → Target. - Store everything in
~/.ssh/configwith separate keys per host. - Secure defaults: no password logins, no agent forwarding (unless required), keepalives on.
- Transfer files with
scp -Jorrsync -e 'ssh -J ...'the same way. - Works with port forwarding, too.
Network Topology (Example / Redacted)
We’ll use documentation‑safe example addresses and domains:
- Local machine: your laptop (macOS/Linux/Windows + OpenSSH)
- Jump #1 (VPS):
vps.example.net(public Internet) - Jump #2 (Edge / RPi):
198.51.100.7(e.g., VPN private range) - Target (Desktop):
192.168.50.10(home LAN)
NotesDomains likeexample.netand IP blocks like198.51.100.0/24,203.0.113.0/24, and192.0.2.0/24are reserved for documentation per RFC 5737. Safe to show in screenshots.
flowchart LR
A[Local] -- SSH --> B[Jump #1\nVPS\nvps.example.net]
B -- SSH --> C[Jump #2\nEdge/RPi\n198.51.100.7]
C -- SSH --> D[Target Desktop\n192.168.50.10]
1) Generate and Install SSH Keys (Per‑Host Keys)
Use distinct keypairs for each hop for least privilege.
# On LOCAL
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_local -C "local@host"
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_vps -C "vps@login"
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_rpi -C "rpi@login"
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_target -C "target@login"
# Copy public keys to each host (replace placeholders)
ssh-copy-id -i ~/.ssh/id_ed25519_vps.pub user_vps@vps.example.net
# From VPS to RPi (or via ProxyJump once configured)
ssh-copy-id -i ~/.ssh/id_ed25519_rpi.pub user_rpi@198.51.100.7
# From RPi to Target
ssh-copy-id -i ~/.ssh/id_ed25519_target.pub user_target@192.168.50.10
Server‑side hardening (on VPS, RPi, Target — /etc/ssh/sshd_config):
PasswordAuthentication no
PubkeyAuthentication yes
PermitRootLogin prohibit-password
AllowTcpForwarding yes
GatewayPorts no
PermitTunnel no
ClientAliveInterval 30
ClientAliveCountMax 3
Reload SSH:
sudo systemctl reload sshd
2) Modern, Minimal: ProxyJump (OpenSSH ≥ 7.3)
Create or edit LOCAL ~/.ssh/config:
# === Jump #1: VPS ===
Host vps
HostName vps.example.net # PUBLIC
User user_vps # change me
Port 22
IdentityFile ~/.ssh/id_ed25519_vps
ServerAliveInterval 30
ServerAliveCountMax 3
# === Jump #2: Edge / RPi ===
Host rpi
HostName 198.51.100.7 # PRIVATE (VPN/edge)
User user_rpi # change me
Port 22
IdentityFile ~/.ssh/id_ed25519_rpi
ProxyJump vps # hop via VPS
ServerAliveInterval 30
ServerAliveCountMax 3
# === Target Desktop ===
Host desktop
HostName 192.168.50.10 # HOME LAN
User user_target # change me
Port 22
IdentityFile ~/.ssh/id_ed25519_target
ProxyJump vps,rpi # core: vps → rpi → desktop
ServerAliveInterval 30
ServerAliveCountMax 3
# === Quality of life ===
Host *
ControlMaster auto
ControlPath ~/.ssh/cm-%r@%h:%p
ControlPersist 10m
HashKnownHosts yes
StrictHostKeyChecking accept-new
Connect with one command:
ssh desktop
3) File Copy & Sync (Multi‑Hop the Same Way)
SCP via multi‑hop:
scp -J vps,rpi ./local.file desktop:~/dest/
rsync via multi‑hop:
rsync -avP -e 'ssh -J vps,rpi' ./data/ desktop:/data/
Tip: After the first connection, ControlMaster multiplexing makes subsequent transfers faster.
4) Port Forwarding (Tunnels Across All Hops)
Example: expose Target’s Jupyter :8888 to LOCAL.
ssh -J vps,rpi -L 8888:127.0.0.1:8888 desktop
# then open http://127.0.0.1:8888 on LOCAL
Reverse forward (Target service → VPS public port), if you must:
# On TARGET (or from LOCAL with -J):
ssh -J vps,rpi -R 0.0.0.0:18080:127.0.0.1:8080 vps
# Now service on Target:8080 is reachable at VPS:18080 (firewall permitting)
Security note: Reverse forwards can expose internal services. Restrict with firewalls and only bind to 127.0.0.1 unless you truly need external access.5) Old Systems: ProxyCommand -W (OpenSSH < 7.3)
Fallback if -J is unavailable:
Host vps
HostName vps.example.net
User user_vps
IdentityFile ~/.ssh/id_ed25519_vps
Host rpi
HostName 198.51.100.7
User user_rpi
IdentityFile ~/.ssh/id_ed25519_rpi
ProxyCommand ssh -W %h:%p vps
Host desktop
HostName 192.168.50.10
User user_target
IdentityFile ~/.ssh/id_ed25519_target
ProxyCommand ssh -W %h:%p rpi
Connect:
ssh desktop
6) Security Checklist (Copy‑Paste Ready)
- Unique ed25519 key per host; keep private keys only on LOCAL.
PasswordAuthentication noon all servers.- Firewall on VPS (e.g.,
ufw+ Fail2ban); change SSH port if desired. - No
ForwardAgentby default. If you must, limit to trusted jump hosts. - Keepalives:
ServerAliveInterval/CountMax(client) andClientAliveInterval/CountMax(server). StrictHostKeyChecking accept-newand monitor for host key changes.- Regularly rotate keys; remove unused
authorized_keys.
7) Troubleshooting (From Simple to Deep)
- Test hop by hop
ssh vps
ssh -J vps rpi
ssh -J vps,rpi desktop
- RPi → Target fails: verify LAN reachability, target firewall (port 22), and actual IP.
- Idle drops: increase keepalives; check middleboxes/NAT timeouts.
- Host key mismatch: host rebuilt? Remove old key:
ssh-keygen -R <host>then reconnect. -Junknown: useProxyCommand -Wfallback.- Agent forwarding errors: avoid
ForwardAgent; copy public keys instead.
8) Redaction Policy for Blog/Screenshots
- Use
example.net,example.com,example.orgfor domains. - Use RFC‑5737 IPs:
192.0.2.0/24,198.51.100.0/24,203.0.113.0/24. - Obfuscate usernames (
user_vps,user_rpi,user_target). - Blur fingerprints and paths in screenshots if present.
9) Copy‑Ready Template (Paste, Then Replace Placeholders)
# (1) VPS
Host vps
HostName vps.example.net
User user_vps
IdentityFile ~/.ssh/id_ed25519_vps
# (2) Edge / RPi
Host rpi
HostName 198.51.100.7
User user_rpi
IdentityFile ~/.ssh/id_ed25519_rpi
ProxyJump vps
# (3) Target
Host desktop
HostName 192.168.50.10
User user_target
IdentityFile ~/.ssh/id_ed25519_target
ProxyJump vps,rpi
10) FAQ
Q: Can I add a fourth hop?
A: Yes: ProxyJump a,b,c,d scales, but complexity and latency increase. Prefer consolidating.
Q: How do I share one config across machines?
A: Store in a private dotfiles repo but keep keys out of VCS. Use a password manager or hardware tokens.
Q: Windows?
A: Use Windows 10/11 OpenSSH (built‑in) or Git Bash. ~/.ssh/config works the same.
中文版:三级跳 SSH(本地 → 跳板1 → 跳板2 → 目标)终极教程(已脱敏)
说明:本文示例中的域名与 IP 全部为文档保留地址(RFC 5737),用户名也为占位名。请在实际部署时替换为你的真实信息。
拓扑与思路
- 本地电脑(LOCAL)
- 跳板1(VPS,公网):
vps.example.net - 跳板2(边缘/树莓派,私网/VPN):
198.51.100.7 - 目标主机(家庭局域网):
192.168.50.10
flowchart LR
L[本地] -- SSH --> J1[跳板1\nVPS\nvps.example.net]
J1 -- SSH --> J2[跳板2\n树莓派\n198.51.100.7]
J2 -- SSH --> T[目标主机\n192.168.50.10]
1)密钥与最小权限
为每台机器生成独立密钥,并仅在对应机器放入公钥。
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_vps -C "vps@login"
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_rpi -C "rpi@login"
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_target -C "target@login"
ssh-copy-id -i ~/.ssh/id_ed25519_vps.pub user_vps@vps.example.net
ssh-copy-id -i ~/.ssh/id_ed25519_rpi.pub user_rpi@198.51.100.7
ssh-copy-id -i ~/.ssh/id_ed25519_target.pub user_target@192.168.50.10
服务器侧 sshd_config 关键项:
PasswordAuthentication no
PubkeyAuthentication yes
AllowTcpForwarding yes
ClientAliveInterval 30
ClientAliveCountMax 3
2)推荐写法:ProxyJump(≥ 7.3)
本地 ~/.ssh/config:
Host vps
HostName vps.example.net
User user_vps
IdentityFile ~/.ssh/id_ed25519_vps
Host rpi
HostName 198.51.100.7
User user_rpi
IdentityFile ~/.ssh/id_ed25519_rpi
ProxyJump vps
Host desktop
HostName 192.168.50.10
User user_target
IdentityFile ~/.ssh/id_ed25519_target
ProxyJump vps,rpi
Host *
ControlMaster auto
ControlPath ~/.ssh/cm-%r@%h:%p
ControlPersist 10m
StrictHostKeyChecking accept-new
连接:
ssh desktop
3)文件传输与同步
scp -J vps,rpi ./local.file desktop:~/dest/
rsync -avP -e 'ssh -J vps,rpi' ./data/ desktop:/data/
4)端口转发
ssh -J vps,rpi -L 8888:127.0.0.1:8888 desktop
# 本机浏览器访问 http://127.0.0.1:8888
5)旧版兼容:ProxyCommand -W
Host desktop
HostName 192.168.50.10
User user_target
IdentityFile ~/.ssh/id_ed25519_target
ProxyCommand ssh -W %h:%p rpi
6)安全清单
- 禁用密码登录,仅密钥。
- 不默认开启
ForwardAgent。 - 跳板只开放必要端口,结合防火墙 + Fail2ban。
- 开启 keepalive,避免闲置断线。
- 定期轮换密钥,清理
authorized_keys。
7)常见故障
- 分跳测试:
ssh vps→ssh -J vps rpi→ssh -J vps,rpi desktop。 - RPi 到目标不通:检查二层/路由、防火墙、IP 是否正确。
-J不可用:改用ProxyCommand -W。
8)脱敏规范
- 域名用
example.net/.com/.org。 - IP 用
192.0.2.0/24、198.51.100.0/24、203.0.113.0/24。 - 用户名用占位符,指纹/路径打码。
You’re done. One command to rule the route:
ssh desktop