PHPackages                             detain/sshpool - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Queues &amp; Workers](/categories/queues)
4. /
5. detain/sshpool

ActiveLibrary[Queues &amp; Workers](/categories/queues)

detain/sshpool
==============

SSH command queue and connection pool runs tons of commands on a ssh host concurrently with completion callback.

v0.0.1(3y ago)11.1kGPL-3.0PHP

Since Jan 27Pushed 1mo ago1 watchersCompare

[ Source](https://github.com/detain/sshpool)[ Packagist](https://packagist.org/packages/detain/sshpool)[ RSS](/packages/detain-sshpool/feed)WikiDiscussions master Synced 3w ago

READMEChangelog (1)DependenciesVersions (2)Used By (0)

sshpool
=======

[](#sshpool)

[![License: GPL v3](https://camo.githubusercontent.com/48bf9b56d44f38db53ce21294cf0b9487d0a3734ab3ba1fe4c69858ae20db2c1/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d47504c76332d626c75652e737667)](https://www.gnu.org/licenses/gpl-3.0)

`detain/sshpool` is a small PHP library for running many shell commands on a single SSH host concurrently. It maintains one SSH session, opens multiple `ssh2_exec` channels in parallel up to a configurable cap, collects each command's stdout / stderr / exit status, and fires a per-command callback on completion. Commands can be queued while the pool is already running, automatic retries with a back-off delay are supported, and per-command timeouts kill long-running work.

Requirements
------------

[](#requirements)

- PHP **8.0+**
- The [`ext-ssh2`](https://www.php.net/manual/en/book.ssh2.php) PHP extension
- An SSH host you can authenticate to with a key pair

> Password authentication is not currently wired through; the supported path is public/private key authentication via `ssh2_auth_pubkey_file`.

Installation
------------

[](#installation)

```
composer require detain/sshpool
```

If you do not already have `ext-ssh2` installed:

```
# Debian / Ubuntu
sudo apt-get install php-ssh2

# RHEL / Alma / Rocky
sudo dnf install php-pecl-ssh2
```

Quick Start
-----------

[](#quick-start)

```
require 'vendor/autoload.php';

use Detain\SshPool\SshPool;

$pool = new SshPool(
    'example.com',          // host
    22,                     // port
    'deploy',               // ssh user
    '',                     // password (unused — pubkey auth)
    '/home/me/.ssh/id_rsa.pub',
    '/home/me/.ssh/id_rsa'
);

$pool->setMaxThreads(10);
$pool->setMaxRetries(2);

foreach (['uptime', 'df -h', 'free -m'] as $cmd) {
    $pool->addCommand(
        $cmd,
        null,                               // auto id
        ['command' => $cmd],                // user data passed to callback
        function ($cmd, $id, $data, $exit, $stdout, $stderr) {
            echo "[{$id}] {$cmd} (exit {$exit}):\n{$stdout}\n";
            if ($stderr !== '') {
                fwrite(STDERR, "[{$id}] stderr: {$stderr}\n");
            }
        },
        30                                  // 30s timeout
    );
}

$pool->run();
```

Usage Patterns
--------------

[](#usage-patterns)

### One-shot synchronous command

[](#one-shot-synchronous-command)

For a single command where you want a synchronous result rather than a callback, use `runCommand()`:

```
$result = $pool->runCommand('hostname');
// $result === ['cmd' => 'hostname', 'exitStatus' => 0, 'out' => '...', 'err' => '']
```

`runCommand()` returns `false` if the channel cannot be opened.

### Adding commands while the pool is running

[](#adding-commands-while-the-pool-is-running)

`addCommand()` is safe to call from inside a completion callback. New commands join `$cmdQueue` and the run loop picks them up on its next iteration.

```
$pool->addCommand('first', null, null, function ($cmd, $id, $data, $exit) use ($pool) {
    if ($exit === 0) {
        // Chain a follow-up command.
        $pool->addCommand('second');
    }
});
$pool->run();
```

### Non-blocking single iteration

[](#non-blocking-single-iteration)

`run(true)` performs one polling iteration (start any newly-available commands, drain finished output, promote any due retries) and returns. This is useful when you want to integrate the pool into your own event loop.

```
while (!$done) {
    $done = $pool->run(true);
    // ... do other work ...
}
```

### Custom SSH connection methods

[](#custom-ssh-connection-methods)

`ssh2_connect()` accepts an algorithm/method override array. Pass it through the constructor's seventh argument when, for example, you need to whitelist a deprecated host key algorithm against a legacy server:

```
$pool = new SshPool(
    $host, 22, $user, '', $pub, $priv,
    ['hostkey' => 'ssh-rsa']
);
```

Configuration
-------------

[](#configuration)

SetterDefaultEffect`setMaxThreads(int)``50`Maximum number of channels open concurrently. Clamped to `>= 1`.`setMaxRetries(int)``0`Retry attempts on a non-zero exit status (or undersized output). `0` disables retry.`setWaitRetry(int)``15`Seconds to wait before promoting a retry back into the run queue.`setMinConfigSize(int)``0`Minimum stdout byte count required to consider a command successful. `0` disables.`$pollInterval` (public int)`25000`Microseconds between run-loop polls in blocking mode.Public API
----------

[](#public-api)

```
new SshPool(
    string $host,
    int $port,
    string $user,
    string $pass,
    string $pubKey,
    string $privKey,
    array $methods = []
);

string addCommand(
    string $cmd,
    ?string $id = null,
    mixed $data = null,
    ?callable $callback = null,
    int $timeout = 0
);

bool run(bool $once = false);
array|false runCommand(string $cmd);

void connect();
void disconnect();
resource|null getConnection();
```

### Callback signature

[](#callback-signature)

```
function (
    string $cmd,
    string $id,
    mixed  $data,        // user data passed to addCommand()
    int    $exitStatus,  // -1 if unknown / timed out
    string $stdout,
    string $stderr
): void;
```

Retry Semantics
---------------

[](#retry-semantics)

A command is considered failed (and eligible for retry) when either:

- its exit status is non-zero, or
- `minConfigSize > 0` **and** `strlen(stdout) < minConfigSize`.

Failed commands are deferred for `waitRetry` seconds, then promoted back into the run queue. The original timeout is preserved across retries. After `maxRetries` attempts the completion callback is invoked with the most recent exit status.

Timeouts
--------

[](#timeouts)

When `addCommand($cmd, ..., $timeout)` is given a non-zero `$timeout`, the run loop closes the command's streams once `time() - start_time > $timeout`. The callback is invoked just like any other completion. If a real exit status was not yet observed, the command is reported with `exitStatus = -1`.

The `metadata` property captures the actual duration and a boolean `timed_out` flag per id:

```
$pool->metadata['my-id'] === [
    'exit_status' => -1,
    'duration'    => 30,
    'timed_out'   => true,
];
```

Development
-----------

[](#development)

```
composer install
./vendor/bin/phpunit              # run the test suite
./vendor/bin/phpunit --filter Foo # run one test
./vendor/bin/phpunit --coverage-text
```

The tests use a `FakeSshPool` fixture (`tests/Fixtures/FakeSshPool.php`) that bypasses `ext-ssh2` so the queue / callback / retry logic is verified without needing a live SSH server.

License
-------

[](#license)

GPL-3.0. See [LICENSE](LICENSE).

Author
------

[](#author)

Joe Huss ·

###  Health Score

34

—

LowBetter than 75% of packages

Maintenance60

Regular maintenance activity

Popularity22

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity36

Early-stage or recently created project

 Bus Factor1

Top contributor holds 96.9% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

1244d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/b1036e0717211b8030b83cbe729e8ba6ba442fdbd5285fb97a39d7dcfe339342?d=identicon)[detain](/maintainers/detain)

---

Top Contributors

[![detain](https://avatars.githubusercontent.com/u/1364504?v=4)](https://github.com/detain "detain (31 commits)")[![mend-bolt-for-github[bot]](https://avatars.githubusercontent.com/in/16809?v=4)](https://github.com/mend-bolt-for-github[bot] "mend-bolt-for-github[bot] (1 commits)")

---

Tags

asyncasync-phpasync-sshchannelcomposer-packageconcurrencyconnection-poolevent-loophigh-concurrencymultiplexnon-blockingparallelphpphp-libraryreactphpsshssh-connection-poolstream-selectworker-pool

### Embed Badge

![Health badge](/badges/detain-sshpool/health.svg)

```
[![Health](https://phpackages.com/badges/detain-sshpool/health.svg)](https://phpackages.com/packages/detain-sshpool)
```

###  Alternatives

[league/geotools

Geo-related tools PHP 7.3+ library

1.4k5.5M29](/packages/league-geotools)[illuminate/bus

The Illuminate Bus package.

6045.5M504](/packages/illuminate-bus)[uecode/qpush-bundle

Asynchronous processing for Symfony using Push Queues

1672.5M2](/packages/uecode-qpush-bundle)[jayazhao/think-queue-rabbitmq

为 ThinkPHP5.1 队列增加 RabbitMQ 驱动

141.5k](/packages/jayazhao-think-queue-rabbitmq)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
