Ten small POSIX shell wrappers around ssh that fix the most common day-to-day annoyances. Each is a one-line tweak of ssh options, packaged behind a name short enough that you'll actually use it. Plus a built-in sshh to remind you which is which.
Homepage: https://pacnpal.github.io/ssh-wrappers/
| Wrapper | Purpose | Mnemonic |
|---|---|---|
sshp |
Force password authentication (disable pubkey for one connection) | password |
sshi |
Use only explicitly configured identities (IdentitiesOnly=yes) |
identities |
ssha |
Forward your local ssh-agent to the remote host (-A) |
agent |
sshq |
Quiet/quick: skip host key prompts, don't pollute known_hosts |
quick |
sshk |
Keepalive: don't drop on idle (ServerAlive*) |
keepalive |
sshm |
Multiplex: instant subsequent connections (ControlMaster) |
multiplex |
ssht |
Force a pseudo-terminal (-t) — for sudo, htop, vim over ssh |
tty |
sshc |
Compression (-C) — wins on slow links and text-heavy streams |
compression |
sshv |
Verbose debug (-vvv) — see exactly what ssh is trying |
verbose |
sshh |
Help — list installed wrappers, what they do, how to use | help |
ssh is configurable to a fault. Most of its sharp edges have a fix that's a single -o option=value away — but it's the kind of fix you have to remember exists, type correctly, and know when to apply.
These wrappers turn each fix into a one-character mnemonic:
- Agent has too many keys, server says
Too many authentication failures→sshi. - Need to type a password but
sshkeeps offering keys instead →sshp. - Connection dropped while you got coffee →
sshk. - Spinning up the same host's connection 50 times in a deploy script →
sshm. - Run
sudoover ssh, getsudo: a terminal is required→ssht. - Cloud VM with a fresh host key, don't want to litter
known_hosts→sshq. - Need agent forwarding for
git pullon a bastion →ssha. - Streaming logs over a slow link →
sshc. - Connection failing for an unknown reason →
sshv. - Forgot which wrapper does what →
sshh.
curl -fsSL https://pacnpal.github.io/ssh-wrappers/install.sh | shPass wrapper names as positional arguments to install only those:
# Just the auth helpers
curl -fsSL https://pacnpal.github.io/ssh-wrappers/install.sh | sh -s -- sshp sshi ssha
# Just connection management
curl -fsSL https://pacnpal.github.io/ssh-wrappers/install.sh | sh -s -- sshk sshm
# Single wrapper
curl -fsSL https://pacnpal.github.io/ssh-wrappers/install.sh | sh -s -- sshtList the available names with --list:
curl -fsSL https://pacnpal.github.io/ssh-wrappers/install.sh | sh -s -- --list- auto-detects your shell from
$SHELL(zsh, bash, ksh) - writes the selected functions to the matching rc file (
~/.zshrc,~/.bash_profileon macOS bash,~/.bashrcelsewhere,~/.kshrc, …) inside a marked block - is idempotent — re-running does nothing if the managed block is already there
- refuses to silently shadow wrappers you've already defined yourself (use
--forceto install anyway) - supports
--uninstallto cleanly remove the managed block (and only the managed block)
SSH_WRAPPERS_RC=~/.zprofile sh install.shcurl -fsSL https://pacnpal.github.io/ssh-wrappers/install.sh | sh -s -- --force sshp sshi sshk sshm--force removes the existing managed block and writes the new selection.
curl -fsSL https://pacnpal.github.io/ssh-wrappers/install.sh | sh -s -- --uninstallRemoves only the marked block; anything else in your rc file is untouched.
Not covered by the installer (different function syntax). Run the installer once to see the snippet you can paste into ~/.config/fish/config.fish, or copy the bodies straight from the per-wrapper docs.
Skip the installer entirely — copy the function definitions you want from the per-wrapper docs into your rc file. Each wrapper's .md shows its function definition at the top.
Every wrapper accepts the same arguments as ssh:
sshp user@host # password instead of keys
sshi -i ~/.ssh/work_ed25519 user@host # only this key
ssha bastion # forward agent
sshq ec2-user@10.0.0.42 # ephemeral cloud VM
sshk prod-bastion 'tail -f /var/log/syslog' # long idle session
sshm work-bastion # then re-run; instant
ssht user@host sudo systemctl restart nginx # sudo over ssh
sshc remote-builder 'tail -f build.log' # compression
sshv user@host # debug auth failure
sshh # show all wrappers + install status
sshh sshm # detail for one wrapperRead the per-wrapper docs for what each option actually does, security tradeoffs, and ~/.ssh/config equivalents:
- sshp · sshi · ssha — auth
- sshq — trust
- sshk · sshm — connection lifetime
- ssht · sshc — I/O
- sshv — debugging
- sshh — help / introspection
- POSIX shell (
/bin/sh) for the installer - OpenSSH client (any version from the past decade — every option used here has been stable for years)
- Interactive shell of zsh, bash, or ksh for the wrappers (fish has its own snippet — see install)
No build step, no runtime dependencies beyond what comes with your OS.
Too many authentication failures — your agent has more keys than the server's MaxAuthTries (default 6). Use sshi to offer only one specific key, or sshp to skip pubkey entirely.
Permission denied (publickey) with sshp — the server has PasswordAuthentication no. No client-side wrapper can fix this; the server must allow password auth.
sudo: a terminal is required over ssh — pass the command through ssht instead of plain ssh.
client_loop: send disconnect: Broken pipe after idle — use sshk, or set ServerAliveInterval 30 for Host * in ~/.ssh/config.
sshp/sshi/etc. "command not found" after install — open a fresh shell, or source ~/.zshrc. Shell functions only exist in interactive shells that have sourced your rc file. Check type sshp in the new shell.
Function overridden by an alias or another script in PATH — shell functions take precedence over executables in interactive shells, but not in non-interactive scripts. type sshp reveals what's actually being invoked.
Wrapper conflicts with one I already defined — the installer refuses to silently shadow you. Either remove your existing definition, install only the wrappers that don't conflict, or pass --force to overwrite.
Pages URL returns 404 or stale content — the GitHub Pages CDN caches for ~10 min. Use the commit-pinned raw URL or https://cdn.jsdelivr.net/gh/pacnpal/ssh-wrappers/install.sh to bust the cache once.
Lint the installer locally:
shellcheck --shell=sh install.shCI runs the same on every push to master — see the badge above.
Project layout:
.
├── install.sh # the installer (source of truth for function bodies)
├── README.md # this file
├── index.html # GitHub Pages landing page
├── ssh{p,i,a,q,k,m,t,c,v,h}.md # per-wrapper docs
├── assets/
│ ├── logo.svg # the mark
│ ├── logo.png # rendered 512x512
│ ├── social-card.svg # 1280x640 OG image
│ └── social-card.png # rendered
└── .github/workflows/
└── shellcheck.yml # CI
MIT © pacnpal