spatie/ssh
spatie/ssh is a lightweight PHP package for running commands over an SSH connection. Execute commands synchronously or asynchronously, run multiple commands, choose a custom port, and inspect results via Symfony's Process.
proc_open + argument-escaping + key-handling code. spatie/ssh collapses that into one fluent call (Ssh::create($user, $host)->execute(...)) returning a Symfony Process, so the decision shifts from "do we build a transport layer?" to "what do we do with the result?"onOutput), these stop being separate roadmap items and become configuration. A PM can put "run commands across the fleet" on the roadmap without budgeting a networking workstream.isSuccessful() as the gate.upload() + execute() (scp-based artifact push plus remote command).useJumpHost().DivineOmega/php-ssh-connection (pure-PHP, no local ssh binary) — a genuine fork in the road, not a toss-up.sshpass and silently fails without it; host-key checking is on by default (deliberate security posture since 1.1.1); passwords and command strings may land on the command line. Each of these is a policy decision — SSH keys/agent only, restricted command allowlists — that a PM should make explicitly rather than let an engineer discover in production.Process, so results, timeouts, and exit codes plug into existing exception handling and queuing patterns. That keeps the integration reversible — the cheap-exit property a PM wants before committing.Adopt when:
ssh/scp, or your infrastructure expects the operator's own SSH config, known_hosts, agent, and key material to be the source of truth.useMultiplexing) for many commands against the same host.ssh installed) and the remote hosts.Look elsewhere or build differently when:
proc_open disabled, hardened hosts without ssh/scp, or a policy against spawning binaries. DivineOmega/php-ssh-connection (phpseclib) is the named alternative.sshpass is an external, easily-missed dependency that fails silently; prefer keys, or pick a library that does auth in-process.cmd.exe quirks mean isSuccessful() is only trustworthy with onWindows() on Windows targets and disciplined quoting elsewhere. If your feature's correctness hinges on exit codes, budget for tests against real hosts.Process, so price that work in.To executives (30 seconds): "We need the app to run commands on our servers — for deploys, maintenance actions, and health checks. Instead of building that plumbing ourselves, we'd adopt a small, MIT-licensed, actively maintained library that does exactly one thing: run a command over SSH and hand back the result. No new servers, no new vendor, no new credentials system — it reuses the SSH keys and access rules we already have and audit. Cost is a composer require and a day of integration; the alternative is several days of custom code plus permanent ownership of a security-sensitive component."
To engineering: "It's a thin wrapper over the system ssh/scp binaries that returns a Symfony Process — so isSuccessful(), getOutput(), timeouts, and onOutput streaming are already there, and executeAsync covers fire-and-forget. It handles the annoying parts we'd otherwise hand-roll: argument construction, ports, private keys, jump hosts, multiplexing, strict host-key checking (on by default), and Windows exit codes. Test coverage and a changelog that tracks PHP 8.4 and Symfony 8 signal it's maintained. Things to design around, not fight: it does not hold a session — one process per call — so anything needing SFTP or port forwarding goes elsewhere; password auth needs sshpass and fails silently if absent, so we standardize on keys; and any user-supplied input reaching a command string must go through an allowlist, not string interpolation."
What we're knowingly accepting: a small, single-maintainer dependency; reliance on the local SSH binary and its version; process-spawn overhead per host; and no built-in retries or connection pooling beyond multiplexing. All of these are contained by the package's size and the fact that it returns a standard Process — if it disappoints, replacing it is measured in hours, not quarters. Success looks like: ops features shipped without a bespoke transport layer, zero credential-handling code written by us, and deploy/maintenance actions whose failures surface as real exit codes in our existing error handling.
How can I help you explore Laravel packages today?