Fifth post in a series where I read through SSH man pages and write about things worth knowing. Previous posts: ObscureKeystrokeTiming, ChannelTimeout, Match version, Match sessiontype. Today, I'm stepping out of ssh_config and into man ssh itself: escape sequences. Every interactive SSH session has a hidden control interface. Press Enter, then ~, then ?, and you'll see it:

Supported escape sequences:
  ~.   - terminate connection (and any multiplexed sessions)
  ~B   - send a BREAK to the remote system
  ~C   - open a command line
  ~R   - request rekey
  ~V/v - decrease/increase verbosity (LogLevel)
  ~^Z  - suspend ssh
  ~#   - list forwarded connections
  ~&   - background ssh (when waiting for connections to terminate)
  ~?   - this message
  ~~   - send the escape character by typing it twice
(Note that escapes are only recognized immediately after newline.)

That last line is the critical detail. Escape sequences only work at the beginning of a line, right after you press Enter (or at the very start of the session). If you type hello~., nothing happens. You need Enter, then ~.. This trips me0 up every time.

~. is the one everyone should know. When your SSH session freezes, the network dropped, the remote host crashed, a firewall somewhere timed out the connection, Ctrl+C does nothing because the client is waiting on a dead TCP socket. ~. kills the client side immediately. No stuck terminal, no hunting for PIDs. I use this multiple times a week. If you take nothing else from this post, take ~..

~^Z (tilde then Ctrl+Z) suspends the SSH session and drops you back to your local shell, same as suspending any process. fg brings it back. Useful when you need to quickly check something locally without opening a new terminal.

~# lists all active forwarded connections. If you set up port forwards with -L or -R or -D and want to see what's actually connected, this shows you.

~V and ~v adjust the SSH client's log verbosity on the fly. Each ~v bumps it up one level (INFO → VERBOSE → DEBUG → DEBUG2 → DEBUG3), and ~V takes it back down. This is great for debugging a connection problem mid-session without having to disconnect and reconnect with -vvv.

Now the interesting one: ~C. This opens a ssh> command prompt where you can add or remove port forwards on a live session:

ssh> -L 8080:localhost:80
Forwarding port.
ssh> -D 1080
Dynamic forwarding port.
ssh> -KL 8080
Canceled forwarding.

You can add local forwards (-L), remote forwards (-R), dynamic SOCKS proxies (-D), and cancel any of them with the -K prefix. No need to drop your session and reconnect with different flags. This is genuinely powerful for the kind of ad-hoc tunneling you do when you're deep into debugging something on a remote machine and realize you need access to another port.

Here's the catch. Since OpenSSH 9.2 (February 2023), the ~C command line is disabled by default. If you try it, you'll see commandline disabled. The OpenSSH team added a new EnableEscapeCommandline option that defaults to no. The rationale is that disabling the command line allows tighter sandboxing of the SSH client process on platforms that support it. And to get it back:

Host *
    EnableEscapeCommandline yes

Or per-connection: ssh -o EnableEscapeCommandline=yes host.

All the other escape sequences (~., ~^Z, ~#, ~V/v, etc.) still work regardless of this setting. Only ~C is gated behind EnableEscapeCommandline.

One more thing about nested sessions. If you're SSH'd into host A, and from there SSH'd into host B, ~. will kill the connection to host A (taking host B down with it). To send the escape to the inner session instead, you double the tilde: ~~. kills only the connection to host B. Triple for three levels deep, and so on. The same applies to all escape sequences, each additional ~ pushes the escape one hop further in.

The escape character itself is configurable via the EscapeChar directive in ssh_config, or -e on the command line. Setting it to none disables escape sequences entirely, which you'd want for binary-transparent connections where tilde characters in the data stream could be misinterpreted.