Skip to content

SSH, remote shells, and remote tmux

Run:

Terminal window
ssh user@example.com

A remote prompt appears in the same Ghostty surface. The pixels are local. The shell producing the prompt is remote. Between them are two operating systems, two PTY relationships, and an encrypted byte channel.

Owns:

  • keyboard and display hardware;
  • desktop compositor;
  • Ghostty process and native window;
  • Ghostty terminal state, fonts, and GPU rendering;
  • local shell and local ssh client process;
  • local PTY connecting Ghostty to the foreground ssh process.

Owns:

  • sshd server process;
  • remote account and filesystem;
  • remote PTY allocated for the SSH session;
  • remote shell and commands;
  • optional remote tmux server and pane processes.

The network transports encrypted data. It does not transport a window or GPU frame.

Your local shell starts ssh as its foreground child. From Ghostty’s immediate perspective, ssh is simply the terminal application attached to the local PTY.

</>
The local process attached to Ghosttysrc/termio/Exec.zig:88–150Read threadEnter through process startup. For an SSH session, this local subprocess is ssh—not the remote shell.
Read excerptGitHub

SSH authenticates, negotiates encryption, and normally requests a remote PTY for an interactive session. The remote SSH server starts the remote account’s shell with the slave side of that remote PTY.

ANIMATED DATA FLOW

From local command to remote prompt

A local studio opens a secure contribution line to a reporter at another stadium.

STEP 01 OF 06

Local shell

Starts the local ssh client on Ghostty’s local PTY.

DATA INssh user@example.com
DATA OUTforeground local ssh process
1 / 6

Suppose you type:

Terminal window
uname -a

Input path:

keyboard
→ local OS/window event
→ Ghostty input encoding
→ local PTY master
→ local ssh process
→ SSH encrypted channel
→ remote sshd
→ remote PTY master
→ remote shell

The remote shell parses and starts remote uname. It runs under the remote kernel and reads remote system information.

Output path:

remote uname stdout
→ remote PTY slave/master
→ remote sshd
→ encrypted network
→ local ssh process stdout
→ local PTY slave/master
→ Ghostty parser/state/fonts/GPU
→ local display

remote process output bytes eventually become local GPU pixels.

The remote application needs to know what kind of terminal behavior exists at the far end. SSH forwards selected environment information, commonly including TERM. The remote program uses that value and terminfo data to choose control sequences.

The value does not mean “the remote machine has Ghostty’s GPU.” It describes the terminal protocol capabilities expected through the connection. Rendering still happens locally.

Incorrect or missing terminal capability information can cause broken colors, keys, or screen updates even when network transport works perfectly.

When the Ghostty window changes size:

  1. Ghostty computes new rows and columns.
  2. The local PTY size changes.
  3. Local ssh detects the terminal-size change.
  4. SSH sends a window-change request across the encrypted channel.
  5. sshd updates the remote PTY size.
  6. The remote foreground application receives resize information.
  7. It redraws.
  8. Output returns and Ghostty renders the new layout.

No remote pixel image is resized. Terminal dimensions and fresh terminal output propagate.

Networks split and delay data. SSH preserves ordered byte streams, but Ghostty may receive output in different chunk sizes from those written remotely. A correct terminal parser cannot assume one read call contains one complete escape sequence or one complete UTF-8 character.

This is why parser state persists across chunks.

Now run tmux after connecting:

Terminal window
ssh user@example.com
tmux attach

The nesting becomes:

local Ghostty
↕ local PTY
local ssh client
↕ encrypted network
remote sshd
↕ remote session PTY
remote tmux client/server
↕ pane PTY A → remote shell/program
↕ pane PTY B → remote shell/program

A pane application’s output may be:

  1. parsed by remote tmux;
  2. composed into tmux’s outer remote terminal stream;
  3. transported by SSH;
  4. parsed by local Ghostty;
  5. rendered by the local GPU.

Detach from remote tmux and close SSH: remote pane processes can continue because the remote tmux server owns them. Close Ghostty without tmux: whether the remote command survives depends on SSH/session behavior, not Ghostty rendering.

Another arrangement is:

local Ghostty → local tmux pane → local ssh → remote PTY → remote shell

Now local tmux parses remote output before Ghostty does. Add remote tmux and there are three terminal-state owners in the path. When debugging, draw the actual process/PTY nesting rather than saying “the terminal.”

Ghostty is connected through a local PTY to a local ssh client. SSH creates an encrypted byte channel to remote sshd, which allocates a remote PTY and launches a remote shell or program. Keys travel from local hardware through Ghostty, the local PTY, SSH, and the remote PTY. Output travels back through the reverse route. Ghostty parses and renders locally. tmux can add another parser/state/composition layer on either side.

A remote sports feed often transports compressed audio/video. SSH terminal sessions transport terminal bytes, not rendered frames. The remote side normally does not decide the final font, glyph rasterization, or GPU output you see in Ghostty.