Signal9Signal9 — home
online
all lessons

SSH Power Use: Config, Multiplexing, Jump Hosts & Tunnels

Intermediate35 min6 steps
What you'll learn
  • Structure ~/.ssh/config with per-host aliases and a Host * defaults block
  • Speed up repeated connections with connection multiplexing (ControlMaster/ControlPath/ControlPersist)
  • Reach hosts behind a bastion in one command with ProxyJump
  • Forward ports with -L and -R to tunnel services over SSH
  • Use ssh-agent for keys and ssh -G to debug how config resolves
  1. ~/.ssh/config: aliases and defaults

    Organise connection settings so a host is one word and shared options live in one place.

    Typing ssh -p 2222 -i ~/.ssh/deploy_ed25519 deploy@10.20.30.40 a dozen times a day is a waste. ~/.ssh/config turns it into ssh web. Each `Host` block names an alias and sets its options; SSH reads the file top to bottom and, for each option, the first matching value wins.

    Two constructs make it powerful:

    • Per-host blocks — Host web with its HostName, Port, User, IdentityFile.
    • *A `Host block** — defaults that apply to *every* host. Because first-match-wins, put specific hosts **first** and the Host ` catch-all last*.

    Example — not runnable on your lab box

    cat ~/.ssh/config
    Host web
        HostName 10.20.30.40
        Port 2222
        User deploy
        IdentityFile ~/.ssh/deploy_ed25519
    
    Host *
        ServerAliveInterval 30
        ControlMaster auto
        ControlPath ~/.ssh/cm-%r@%h:%p
        ControlPersist 10m
    Host web
    Define an alias; ssh web uses this block's settings.
    Host *
    Wildcard block — options here are defaults for every host. Place it last.
    ssh web now expands to the full command. The specific Host web block comes first; the Host * defaults (keepalive + multiplexing, covered next) come last so a per-host block can still override them. The %r@%h:%p tokens in ControlPath expand to remote-user@host:port.
  2. Connection multiplexing: make the 2nd connection instant

    Reuse one master connection so repeated SSH/scp/git is fast.

    Every fresh SSH connection pays for a TCP handshake, key exchange, and authentication — noticeable when a deploy script opens twenty of them. Multiplexing opens the expensive connection once (the master) and runs every later session to that host through the same socket.

    Three directives:

    • `ControlMaster auto` — reuse an existing master if one is up, else become the master.
    • `ControlPath` — the control socket's path; use %r@%h:%p so each user/host/port gets its own.
    • `ControlPersist 10m` — keep the master alive 10 minutes after the last session closes, so the next command is instant too.
    ssh web true    # first connection: opens the master
    time ssh web true    # second: rides the existing socket
    real    0m0.03s
    ControlPersist
    How long the master lingers after the last client detaches — the window in which later connections stay instant.
    The first ssh web authenticates and (with ControlMaster auto) leaves a master socket behind. The second connection skips the whole handshake and attaches to that socket — milliseconds instead of a second. scp web:... and git push to the same host get the same speedup for free.
    ssh -O check web
    Master running (pid=48213)
    -O check
    Query the master connection's status (also -O exit to close it, -O stop to stop accepting).
    ssh -O check asks whether a master is currently up for that host. -O exit tears it down on demand (handy if a hung master is wedging connections). These control commands only work when multiplexing is configured.
  3. ProxyJump: hosts behind a bastion

    Reach an internal host through a jump box in one command.

    Production hosts usually aren't reachable directly — you land on a bastion (jump host) first, then hop inward. ProxyJump (or ssh -J) does both hops in one command, tunnelling the connection to the internal host through the bastion. Your key authenticates end-to-end; the traffic just passes through the jump.

    ssh -J bastion.example.com db-internal
    root@db-internal:~#
    -J host
    Jump: connect through the given host to reach the target (equivalent to the ProxyJump config directive).
    SSH connects to bastion.example.com, then from there opens a connection to db-internal and forwards your session through it — a single command instead of ssh bastion then ssh db-internal. Your private key never touches the bastion (unlike agent forwarding); the bastion only relays bytes.
    printf 'Host db-internal\n    HostName 10.0.5.12\n    ProxyJump bastion.example.com\n' >> ~/.ssh/config
    ProxyJump
    The config-file form of -J; can chain multiple jump hosts comma-separated.
    Bake it into config and ssh db-internal handles the jump automatically. Now scp, rsync, and git through the same alias all traverse the bastion with no extra flags — the config is where power-user settings become invisible defaults.
  4. Port forwarding: tunnel a service over SSH

    Use -L and -R to move a port across the SSH connection.

    SSH can carry other services inside its encrypted channel. Two directions:

    • `-L` local forward — expose a remote service on a local port. -L 5432:localhost:5432 db makes the remote database reachable at localhost:5432 on your machine.
    • `-R` remote forward — expose a local service on a remote port (the reverse: let a remote host reach something on your side).
    • `-D` dynamic forward — turn SSH into a local SOCKS proxy, so a browser pointed at it routes all its traffic through the remote host. One flag, a whole tunnel.

    Read -L localport:targethost:targetport: the target is resolved from the remote end, so localhost:5432 means 'localhost as seen by the server.' That 'resolved from the server side' rule is the part people trip over — the target host/port are looked up by the remote SSH daemon, not by your machine.

    ssh -L 8080:localhost:80 web
    (session opens; now browse http://localhost:8080)
    -L
    Local forward: localPort:targetHost:targetPort, target resolved from the server's side.
    -N
    Do not execute a remote command — just hold the tunnel open.
    A web server bound to localhost:80 on web — not exposed to the network — is now reachable at http://localhost:8080 on your laptop, tunnelled through SSH. This is the standard safe way to reach an internal admin UI without opening a firewall port. Add -N to forward without running a remote shell.

    Check yourself

    You want a database that only listens on the remote server's localhost to be reachable at 127.0.0.1:5432 on your own machine. Which SSH forward flag does that — -L or -R?

  5. Agent, and debugging with ssh -G

    Load keys once with ssh-agent and inspect how config resolves.

    `ssh-agent` holds your decrypted private keys in memory so you type the passphrase once per session, not per connection. ssh-add loads a key; ssh-add -l lists what's loaded; ssh-add -t 8h loads one with an expiry so it doesn't live forever. Without an agent, a passphrase-protected key prompts you on every connection — which quietly pushes people toward passphrase-less keys, the worse trade. The agent is what makes strong, encrypted keys practical.

    Agent forwarding (-A, or ForwardAgent yes) lets a remote host reach back to your local agent to authenticate a further hop — convenient for chained SSH, but only enable it per-host to machines you trust, because root on the forwarded host can use your loaded keys while the connection is open. Prefer ProxyJump over agent forwarding when you only need to pass through a bastion: it keeps your keys entirely on your side.

    When a connection behaves unexpectedly, `ssh -G host` prints the effective configuration SSH computed for that host — every option after all the Host/Match blocks are merged. It's the fastest way to see which value actually won.

    ssh -G web | grep -iE 'hostname|port|user|controlmaster|serveraliveinterval'
    hostname 10.20.30.40
    port 2222
    user deploy
    controlmaster auto
    serveraliveinterval 30
    -G
    Print the effective (resolved) configuration for a host and exit — the debugging power tool.
    ssh -G resolves the config WITHOUT connecting, so it's safe and instant. Here you can confirm the alias web really maps to the right host/port/user and that your Host * defaults (controlmaster, keepalive) are in effect. When a setting 'isn't working,' ssh -G shows you the value SSH actually chose — usually revealing a wildcard block that won the first-match race.
  6. Try it in your live terminal

    Write power-user defaults and confirm they resolve.

    On the box to your right, add multiplexing + keepalive defaults to ~/.ssh/config and confirm they resolve with ssh -G. Nothing connects anywhere — you're just editing config and asking SSH to resolve it.

    printf 'Host *\n    ControlMaster auto\n    ControlPath ~/.ssh/cm-%%r@%%h:%%p\n    ControlPersist 10m\n    ServerAliveInterval 30\n' >> ~/.ssh/config
    Appends a Host * defaults block enabling connection multiplexing and keepalives. (The %% escapes are just so printf emits literal % tokens for ControlPath.)
    ssh -G labbox | grep -iE '^(controlmaster|controlpath|serveraliveinterval)'
    Resolves the effective config for the alias 'labbox' and shows controlmaster auto, a real controlpath socket, and serveraliveinterval 30 — proof your defaults took effect. This is exactly what the lab grades.
    ssh -G labbox | grep -i '^controlmaster'
    Focus on one directive: it should read 'controlmaster auto'. If it still says 'false', your block didn't apply — check that no earlier Host * set it first.
Recap
  • ~/.ssh/config turns a host into one word; per-host blocks first, a Host * defaults block last (first match wins).
  • Connection multiplexing (ControlMaster auto + ControlPath + ControlPersist) reuses one master connection — the biggest single SSH speedup for repeated sessions.
  • ProxyJump / ssh -J reaches a host behind a bastion in one command, relaying through the jump without exposing your key to it.
  • Port forwarding tunnels services: -L brings a remote port to your local machine, -R does the reverse.
  • ssh-agent holds keys for the session; ssh -G host prints the effective, resolved config — the fastest way to debug precedence.
Directive / flag → what it does
SettingDoesNote
Host alias / Host *Per-host block / defaultsSpecific first, wildcard last
ControlMaster auto + ControlPathReuse one connectionAdd ControlPersist to keep it warm
ProxyJump / -JHop through a bastionKey not exposed to the jump
-L / -RLocal / remote port forward-L brings a remote port to you
-AAgent forwardingOnly to hosts you trust
ssh -G hostPrint effective configGround truth for precedence