hqterm docs
hqterm is the app you use. hqsh is the engine: it keeps a session alive on the host and reconnects to it. Both are free and MIT-licensed. Source: hqterm, hqsh.
Install
| Where | Command | Installs |
|---|---|---|
| Your Linux or macOS machine | curl -fsSL https://hqterm.sh/install | sh | hqterm and hqsh in ~/.local/bin, plus the desktop app on a Linux desktop |
| A Linux or macOS server | curl -fsSL https://hqterm.sh/install | sh -s -- --server | hqsh only. It also runs hqsh server setup, which turns on systemd lingering. |
| Windows (PC or server) | irm https://hqterm.sh/install.ps1 | iex | hqsh.exe in ~\.local\bin, added to your user PATH |
| Desktop app only | curl -fsSL https://hqterm.sh/install | sh -s -- --desktop | The Linux AppImage in ~/.local/opt/hqterm-desktop, plus a launcher entry |
Nothing needs sudo or admin rights, and the installers are safe to run again. Every download is checked against the release's SHA256SUMS. hqsh must be installed on both ends: the plain install covers your machine, and --server covers each host.
| install.sh option | |
|---|---|
| --server | Install hqsh only, for hosts you connect to |
| --desktop / --no-desktop | Always or never install the desktop app. By default it is installed on Linux when a display is present. |
| --bin=DIR | Install into DIR instead of ~/.local/bin (or $HQTERM_BIN) |
| HQTERM_VERSION, HQSH_VERSION | Pin a release, for example HQSH_VERSION=v0.3.0 |
hqterm
hqterm # the session manager (below)
hqterm connect <host> [name] # attach to (or create) session name (default main) on host
hqterm desktop [--host H] [--session S] # the desktop app, optionally attached to H/S
hqterm doctor [--try kitty|iterm] # what this terminal supports, with a test image
hqterm fonts install|status|remove # the OpenEmoji colour font
hqterm update [--check] [--force] # update hqterm, hqsh and the desktop app in place
hqterm --version | --help
Hosts come from ~/.ssh/config (concrete Host entries), from ~/.config/hqterm/hosts.json (hosts you add with a), and, when Tailscale is running, from your online tailnet machines, marked ts.
The session manager
| Key | |
|---|---|
| Enter / click | Attach to the selected session (one click, no double-click) |
| n | New session on the host (default name main) |
| a | Add a host (saved to hosts.json) |
| i | Install hqsh on the selected host over ssh |
| r | Refresh the session list |
| Tab / arrows | Move between hosts and sessions |
| Ctrl-^ then . | Inside a session: detach and return to hqterm. The session keeps running. |
| q | Quit |
hqsh
hqsh [user@]host [--session NAME] [--steal | --read-only] [--tailscale auto|on|off] [-- ssh flags...]
hqsh dev2 # connect (or resume) session "main" on dev2
hqsh dev2 -s work # another session
hqsh dev2 -- -p 2202 -i ~/.ssh/key # extra ssh flags after --
hqsh version
Close the laptop, change networks, lose Wi-Fi: when the connection comes back, hqsh reconnects with backoff (0.5 s, doubling up to 10 s) and replays exactly the output you missed, from a numbered buffer on the host. It never repeats output and never leaves a gap. If you were away so long that the buffer moved on, full-screen programs are asked to repaint.
| Key / result | |
|---|---|
| Ctrl-^ then . | Detach. The shell keeps running, and hqsh host picks it up again. |
| Ctrl-^ Ctrl-^ | Send a literal Ctrl-^ |
| exit status | When the shell exits, hqsh exits with the shell's status (0 when you detach) |
hqsh uses your ssh as it is: ~/.ssh/config, keys, agent, ProxyJump, known_hosts. It opens no port, needs no UDP, and runs no service you have to expose.
Shared sessions, like tmux
Any number of clients can attach to one session at once. They all see the output, any of them can type, and the window is the smallest of their sizes.
hqsh dev2 --read-only # -r: watch; your keys are not sent
hqsh dev2 --steal # -d: detach everyone else first (tmux attach -d)
A slow client never stalls the others: it falls behind on its own, and after a gap it resumes like any reconnect. --steal applies to the first connection only, so a reconnect never kicks anyone. In the desktop app, splitting a remote pane opens a new session (main-2, ...). Opening the same host and session in two panes or two windows gives you a shared view.
hqterm connect always attaches normally. For --steal or --read-only, run hqsh directly.
Tailscale
If Tailscale is running on your machine and the host is an online peer on your tailnet, hqsh connects over the peer's tailnet address. That path survives network changes better than a public IP and needs no open port.
- Matching: a host counts as a peer if its alias is the peer's machine name, MagicDNS name or tailnet IP, or if your ssh alias resolves to one of those.
- What changes: only the address (
-o HostName=). The user, port and keys still come from your ssh config, andHostKeyAliaskeeps your known_hosts entry. - Fallback: if the tailnet path fails on the first connect, hqsh uses the normal route. While reconnecting, it alternates between the two, so whichever network is up wins.
hqsh dev2 # auto (default): the tailnet when dev2 is a peer
hqsh dev2 --tailscale on # require it; fail if dev2 is not a peer
hqsh dev2 --tailscale off # never (or HQSH_TAILSCALE=off)
hqterm also lists your online tailnet machines as hosts. Set HQTERM_TAILSCALE=off to hide them.
Servers: what runs on the host
There is nothing to open or enable. When you connect, ssh runs hqsh server attach SESSION. If the session's daemon is not running, attach starts it under the OS service manager:
| Host | The session runs as | Survives logout |
|---|---|---|
| Linux with systemd | A user unit, hqsh-<session>.service | Yes, with lingering on |
| macOS | A launchd job, sh.hqterm.hqsh.<session> | Yes |
| Windows 10 1809+ / Server 2019+ | A process outside the ssh connection's job, on a ConPTY | Yes |
| Anything else | A detached process | Unless the OS kills it |
hqsh server setup # how sessions start here; turns lingering on where systemd needs it
hqsh server setup --check # report only
hqsh server list [--json] # live sessions and how many clients each has
systemctl --user status 'hqsh-*' # Linux: the units
journalctl --user -u 'hqsh-*' # and their logs
systemctl --user stop hqsh-main.service # end a session (its shell gets SIGTERM)
Lingering (loginctl enable-linger) keeps your systemd user manager running after your last logout. Without it, logind stops your units when you log out, so until it is on hqsh falls back to a detached process. --server installs and hqsh server setup turn it on. If your distribution does not allow that for yourself, root can run loginctl enable-linger USER.
On the host, hqsh can live anywhere on the PATH, or in ~/.local/bin. The client tries ~/.local/bin/hqsh too, because ssh commands often lack that directory on their PATH. Sockets go in $XDG_RUNTIME_DIR/hqsh (else ~/.local/state/hqsh), mode 0700. Each session runs your login shell ($SHELL -l) in your home directory, with HQSH_SESSION set to the session name.
Windows hosts
- Enable the OpenSSH server, as Administrator:
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0, thenStart-Service sshd; Set-Service sshd -StartupType Automatic. - As the user you will log in as:
irm https://hqterm.sh/install.ps1 | iex. - From your machine:
hqsh winboxorhqterm connect winbox.
Sessions run PowerShell 7 (pwsh) when it is installed, else Windows PowerShell, else cmd.exe. HQSH_SHELL picks another. Win32-OpenSSH ends everything in a connection's job when it disconnects, so hqsh starts the session daemon outside that job: by breakaway when the job allows it, else through WMI (Win32_Process.Create). hqsh server setup reports which applies.
A whole fleet
Run curl -fsSL https://hqterm.sh/install | sh -s -- --server on each host, as each user that will connect. To do it for every account on a box at once, as root, run cli-tools' root-ubuntu.sh. It installs hqsh for root and every login, does nothing when hqsh is already current, and enables lingering.
for h in web1 web2 db1; do ssh "$h" 'curl -fsSL https://hqterm.sh/install | sh -s -- --server'; done
The desktop app
hqterm's own terminal window (Electron and xterm.js with WebGL): tabs, split panes that come back where you left them, and hqtui/qc images and HD emoji drawn in full. Linux x86_64 and arm64.
| Key | |
|---|---|
| Ctrl+Shift+T / W | New tab / close tab. Ctrl+Tab and Ctrl+PgUp/PgDn switch tabs. |
| Ctrl+Shift+D / E | Split right / split down. Splitting a remote pane opens a new session on that host. |
| Ctrl+Shift+Arrows | Move between panes (or click). Drag a divider to resize. |
| Ctrl+Shift+X | Close the pane |
| Ctrl+Shift+C / V | Copy / paste |
| Ctrl+= / Ctrl+- / Ctrl+0 | Zoom in / out / reset |
The + button opens a shell, the session manager, or any host (as hqsh host --session main). The layout (tabs, splits, each remote pane's host and session) is saved to ~/.config/hqterm/desktop-layout.json. The next launch reattaches every remote pane; local panes come back as fresh shells.
Settings live in ~/.config/hqterm/desktop.json:
{ "fontFamily": "JetBrains Mono", "fontSize": 15, "theme": { "background": "#000" }, "restore": true, "gpu": true }
The default font size is 15. "gpu": false turns hardware acceleration off. Each start writes GPU status to ~/.config/hqterm/desktop.log. Every pane gets TERM_PROGRAM=hqterm, HQTUI_IMAGES=iterm and QC_HD=iterm.
Images and HD emoji
hqsh carries the raw terminal stream, so Kitty graphics, iTerm2 inline images and sixel reach your terminal. mosh cannot do this: it syncs only text and drops every image. Supported terminals: the hqterm desktop app, Kitty, Ghostty, WezTerm, iTerm2, Rio and Windows Terminal. hqterm doctor shows what yours supports, and hqterm fonts install adds the OpenEmoji colour font.
Environment variables
| Variable | |
|---|---|
| HQSH_TAILSCALE | auto (default), on or off |
| HQSH_SUPERVISOR | On the host: auto (default), systemd, launchd or fork |
| HQSH_SHELL | On the host: the session's shell, instead of $SHELL (or PowerShell/cmd on Windows) |
| HQSH_SESSION | Set inside every session to its name |
| HQTERM_TAILSCALE | off hides tailnet machines from hqterm's host list |
| HQTERM_BIN | The install directory (default ~/.local/bin) |
| HQTERM_VERSION / HQSH_VERSION | Pin the installers to a release |
Troubleshooting
hqsh: could not reach HOST: the connection closed (status 1)
ssh connected, but the host did not run hqsh. Usually the port you reached is not a shell: an app on port 22, or a box whose real sshd is on another port. Check with ssh HOST echo ok. If that does not print ok, point an ssh alias at the right port and user (for example Port 2202, User root) and connect to the alias.
HOST has no hqsh
Install it on the host: ssh HOST 'curl -fsSL https://hqterm.sh/install | sh -s -- --server', or press i in the session manager. On Windows, run install.ps1 there.
Sessions disappear when I log out of the server
Run hqsh server setup on the host. If it cannot enable lingering itself, ask root for loginctl enable-linger USER.
Images or emoji art do not show
Run hqterm doctor. Under mosh, images can never work; use hqsh instead. Under tmux, keep allow-passthrough on.
The desktop app redraws slowly
Check ~/.config/hqterm/desktop.log. If webgl is not enabled, the GPU is blocklisted or missing and panes use the slower renderer. Update your graphics drivers, or file an issue with the log attached.
How it works
your terminal ── hqsh ══ ssh ══ hqsh server attach ── unix socket ── session daemon ── PTY/ConPTY ── your shell
(systemd unit / launchd job / outside the ssh job)
The daemon owns the shell and outlives connections. Output goes into a numbered ring buffer, and clients acknowledge what they have printed. On reconnect, the client says where it stopped and gets exactly the rest. Full detail: protocol.md.
Update and uninstall
hqterm update # everything, in place (--check: just compare versions)
curl -fsSL https://hqterm.sh/install | sh -s -- --server # a host: run it again
# uninstall (Linux/macOS)
rm -f ~/.local/bin/hqterm ~/.local/bin/hqsh
rm -rf ~/.local/opt/hqterm-desktop ~/.config/hqterm
rm -f ~/.local/share/applications/hqterm.desktop ~/.local/share/icons/hqterm.png
# Windows: Remove-Item ~\.local\bin\hqsh.exe, then remove that folder from your user PATH