I standardized remote access to managed Linux hosts using SSH key authentication, a maintained ~/.ssh/config, and persistent tmux sessions. Host aliases and ProxyJump turned multi-hop logins into a single short command that survives dropped connections.

Objective & Context

SSH is the primary control channel for every server in the homelab. This lab builds reliable, low-friction access with key auth, named host entries, jump-host routing, and tmux for session persistence, the entry point for the hardening lab.

Learning Objectives

  • Generate Ed25519 key pairs and deploy public keys with ssh-copy-id.
  • Define host aliases with per-host users, keys, and ProxyJump in ssh_config.
  • Connect through a bastion host with a single command.
  • Keep sessions alive across disconnects with tmux.
  • Harden the client side: key passphrases, agent, and known_hosts hygiene.

Environment & Prerequisites

Prerequisites

  • OpenSSH client installed (standard on Linux and macOS; Windows via OpenSSH feature).
  • Two reachable hosts for the ProxyJump step (a bastion plus a target, or simulate with one).
  • tmux installed: sudo apt install -y tmux.
  • Existing SSH keys are fine; generate fresh ones if you have none.

Step-by-Step Execution

1. Generate an Ed25519 key pair and deploy the public key

ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N "" && ssh-copy-id -i ~/.ssh/id_ed25519.pub [email protected]

Expected output:

Generating public/private ed25519 key pair.
Your public key has been saved in ~/.ssh/id_ed25519.pub
Number of key(s) added: 1

What this teaches: ssh-keygen -t ed25519 creates the modern small, fast key type; -N "" sets an empty passphrase for lab convenience (use a real passphrase on production keys and load them with ssh-agent). ssh-copy-id appends the public key to the server's ~/.ssh/authorized_keys over one password-authenticated login, after which the key does the authenticating. Skip -f if you already have a key: ssh-keygen prompts before overwriting.

2. Define a host alias with ProxyJump

Host nas
  HostName 192.168.20.10
  User taki
  ProxyJump bastion
  IdentityFile ~/.ssh/id_ed25519

Expected output (config file content):

(~/.ssh/config created with 600 permissions)
$ ssh -G nas | grep -E "^(hostname|user|proxyjump)"
hostname 192.168.20.10
user taki
proxyjump bastion

What this teaches: ssh_config turns repeated flags into a named host: ProxyJump chains through the bastion transparently, and ssh -G prints the effective config for debugging.

3. Connect with one command

ssh nas

What this teaches: The alias resolves user, key, and jump host automatically; key-based auth means no password prompt. One short word replaces a long fragile command line.

4. Start a persistent session

tmux new -s work; tmux attach -t work

Expected output:

Last login: Wed Jun 17 2026 from 192.168.10.5
[work] taki@nas:~$

What this teaches: tmux new -s work creates a named session; the server keeps it alive if your connection drops, and tmux attach -t work rejoins it. Long jobs survive network flakiness. In practice you detach with Ctrl-b d and reattach later; the second command here just demonstrates the reattach half.

How It Works

SSH multiplexes an encrypted channel over TCP: the handshake negotiates ciphers, the server proves its identity via host keys (checked against known_hosts), and the client authenticates, ideally with a public key challenge the server verifies against authorized_keys. Ed25519 keys are the modern default: small, fast, and secure.

ssh_config is a per-user policy file where Host blocks set options for matching names. ProxyJump composes connections: your client opens a channel to the bastion, then tunnels the second SSH session through it, so the target never needs a public address. IdentityFile pins which key each host uses, avoiding agent key-spray.

tmux adds persistence above SSH. The tmux server runs on the remote host independent of your connection; your terminal merely attaches to it. Detaching (Ctrl-b d) leaves everything running, which is why long transfers and builds belong inside tmux rather than a bare SSH session.

Validation & Testing

Disconnect mid-task and reconnect to confirm the tmux session resumes intact, and verify login uses the key (no password prompt). Pass criteria: one-command access through the jump host and a session that survives network drops.

Troubleshooting

SymptomLikely causeFix
Permission denied (publickey)Server rejected all offered keysConfirm the public key is in the server's ~/.ssh/authorized_keys, with 700 on ~/.ssh and 600 on authorized_keys; sshd in StrictModes ignores the file when permissions are looser
Host key verification failedThe server key changed or a different host answersInvestigate before deleting; remove the stale line with ssh-keygen -R hostname only when expected
ProxyJump hangs with no errorThe bastion is unreachable or its SSH is downTest ssh bastion alone first, then add -v to see where the chain stalls
tmux attach says no sessionsThe session name was mistyped or the server was killedList with tmux ls; sessions die if the host rebooted
Too many authentication failuresThe agent offered many keys before the right oneSet IdentitiesOnly yes and pin IdentityFile per host
Connection freezes on idleNAT or firewall drops idle TCPAdd ServerAliveInterval 60 to keep the channel active
Advanced: Troubleshooting
  • Password prompt: confirm the public key is in remote ~/.ssh/authorized_keys with 600 perms.
  • ProxyJump fails: the bastion host entry must itself be reachable and keyed.
  • Lost work on disconnect: always run long tasks inside tmux or screen.

Key Results

  • Reduced multi-hop logins to a single aliased command.
  • Eliminated password prompts via key-based authentication.
  • Preserved 100% of long-running sessions across disconnects with tmux.
  • Centralized host definitions in one maintained ssh config.

Key Takeaways

  • Ed25519 keys plus ssh_config aliases make logins one word long.
  • ProxyJump reaches private hosts through a bastion transparently.
  • ssh -G reveals the effective config when behavior surprises you.
  • tmux sessions survive disconnects; long jobs belong inside them.
  • 600 on keys and 700 on ~/.ssh are mandatory, not cosmetic.

Practice Challenges

Challenge 1: Key-only lockdown

Goal: configure a test VM so password auth is disabled and only your key works, then verify a password login fails.

Hint: PasswordAuthentication no in sshd_config plus systemctl reload sshd.

Challenge 2: Detach survival

Goal: start a 10-minute task in tmux, kill your SSH client entirely, reconnect, and reattach to find it still running.

Hint: Ctrl-b d detaches cleanly first.