Download the PHP package idct/sftp-client without Composer

On this page you can find all versions of the php package idct/sftp-client. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package sftp-client

idct/sftp-client

Typed PHP 8.2+ wrapper around ext-ssh2 that simplifies file upload / download over SSH / SCP / SFTP. Built for predictable error handling, fingerprint verification, and a clean unit-testable seam over the procedural ssh2_* API.

The 1.0 release adds atomic uploads, resume, recursive directory operations, streaming sources/sinks, progress callbacks, retry policies, known-hosts verification, and opt-in checksum verification — see the feature index below.

codecov CI

Sponsorship ❤️

This project is maintained on the side and looking for sponsors to keep the modernization moving forward. If your team relies on it, please consider chipping in — every contribution helps keep this library alive:

Sponsor on GitHub Buy Me a Coffee

Thank you to everyone who already supports the project! 🙏

Contents

Requirements

Component Version
PHP >= 8.2
ext-ssh2 >= 1.4
libssh2 >= 1.10

Install ext-ssh2 (Debian/Ubuntu):

Alpine:

Or manually using PECL

(depending on your OS it may be required to install the ssh2-beta version)

Installation

Quick start

SftpClient implements __destruct() that closes the SSH session, so leaked instances still send SSH_MSG_DISCONNECT to the peer.

Verified by Behat: readme.feature → Quick start round-trips a small file

Features

Topic Where to read more
Authentication (password / pubkey / both / loader) § Authentication
Host-key fingerprint pinning + known_hosts file § Host-key fingerprint verification
File ops (upload / download / SCP / stat / mkdir / rmdir / …) § Operations
Atomic uploads + resume § Atomic uploads & resume
Streaming sources / sinks (e.g. S3) § Streaming uploads / downloads
Progress callbacks § Progress tracking
Recursive directory operations § Recursive directory operations
Retry policy & lazy reconnect § Retry policy
Checksum verification (opt-in) § Checksum verification
Logging (PSR-3) § Logging
Runnable examples examples/
SemVer policy COMPATIBILITY.md
Disclosure policy SECURITY.md

Authentication

Credentials is a final readonly value object. Password and passphrase parameters are marked #[\SensitiveParameter] so they are redacted from PHP stack traces, and __debugInfo() replaces them with ***REDACTED*** in var_dump / print_r / error-log dumps.

For dynamic per-host secret resolution (Vault, AWS Secrets Manager, GCP Secret Manager, …), implement IDCT\Networking\Ssh\Auth\CredentialsLoaderInterface and wire it in:

For after-the-fact rate-limiting on auth failures:

Verified by Behat: connect.feature (password / wrong-password / pubkey), readme.feature → setCredentialsLoader resolves credentials at connect-time. Unit tests: CredentialsLoaderTest.

Host-key fingerprint verification

A mismatch immediately disconnects and throws ConnectionException — the auth handshake never starts.

For persistent host pinning across runs, use an OpenSSH-format known_hosts file:

TrustOnFirstUse appends a custom sha1-fpr entry on first connection. On subsequent connects the entry is matched. A key change at the same host raises ConnectionException regardless of policy — mismatch is always treated as a MITM signal.

Why SHA-1, not SHA-256: SSH2_FINGERPRINT_SHA256 only ships in libssh2 ≥ 1.9; the library's floor is ext-ssh2 >= 1.4. The sha1-fpr keytype is non-standard but OpenSSH skips unknown keytypes when reading the file, so the file remains safe to share with ssh(1)'s own entries. Details in src/KnownHosts/KnownHostsFile.php.

Verified by Behat: known-hosts.feature (TOFU first connect + repeat connect, Reject on unknown host, tampered mismatch). Fingerprint mismatch via expectedFingerprint: connect.feature.

Operations

Verified by Behat: transfer.feature (SFTP round-trip), ops.feature (stat, fileExists, getFileList ± dot entries, recursive makeDirectory, rename), filesystem.feature (mkdir / remove / rename error paths), needs-shell.feature (scpUpload / scpDownload — runs against the OpenSSH fixture only), readme.feature → enableFileSizeVerification accepts a clean upload.

Atomic uploads & resume

upload() is atomic by default: the bytes land in a hidden .{basename}.partial-{uuid} sibling, then ssh2_sftp_renames onto the final path. Mid-transfer failures are best-effort cleaned up; nothing half-written ever appears at the destination filename. Disable on servers that reject overwrite-on-rename:

For resumable transfers on flaky links:

Behaviour:

Verified by Behat: atomic-and-resume.feature (atomic round-trip, resume from server-side partial, resume download into a partial local, noop when already complete, resume upload with explicit offset: arg), readme.feature → disableAtomicUploads writes the destination directly (no partial). Unit tests: AtomicUploadAndResumeTest. Runnable: examples/03-resume-upload.php.

Streaming uploads / downloads (incl. S3 sources)

The plain upload() / download() take filesystem paths. For sources or sinks that aren't files — S3 objects, generated payloads, HTTP bodies, in-memory buffers — use the stream variants:

uploadStream honours the atomic flag (writes to a partial, renames on success). Both methods accept an optional ProgressListenerInterface (§ Progress tracking) and respect the configured chunk size (setChunkSize(int $bytes)).

The plan's S3-from-AWS-SDK shape is the same as the HTTP fopen above once you've called \Aws\S3\S3Client::getObject(...)->get('Body')->detach() to get the underlying stream resource — no library-side AWS dependency needed.

Verified by Behat: s3-stream.feature (round-trips a payload served by minio), streaming-progress.feature (uploadStream from php://memory, downloadStream into an in-memory sink). Unit tests: StreamingAndProgressTest. Runnable: examples/07-stream-from-s3.php.

Progress tracking

Implement IDCT\Networking\Ssh\Progress\ProgressListenerInterface and pass it to any transfer:

Lifecycle contract: started → progress × N → exactly one of completed | failed. The terminator covers the whole operation, so atomic-rename failures fire failed() not a stray completed(). SCP transfers do NOT emit events — ext-ssh2 doesn't expose libssh2's per-chunk callbacks for scp_send / scp_recv.

Granularity is bounded by chunkSize (default 1 MiB). For finer-grained bars, tune it:

Verified by Behat: streaming-progress.feature (lifecycle started → progress → completed for "upload", final byte count). Unit tests: StreamingAndProgressTest. Runnable: examples/02-progress.php.

Recursive directory operations

The result types are immutable UploadResult / DownloadResult value objects with filesTransferred, bytesTransferred, skipped, and failures (a list<DirectoryFailure> populated only in best-effort mode). Per-file transfers reuse the existing upload() / download() so atomic writes, retries, file-size verification, and progress all apply on a per-file basis.

SymlinkPolicy::Follow is supported on the upload side with inode-set cycle detection ((dev, ino) tracking). On the download side, remote symlink-follow is documented as a deferred feature — the entry is recorded in skipped with a notice log line.

Verified by Behat: directory-ops.feature (nested round-trip + tree removal), readme.feature → walk() yields nested entries in post-order. Unit tests: DirectoryOperationsTest, Directory/DirectoryPolicyTest (ConflictPolicy / SymlinkPolicy / best-effort). Runnable: examples/05-upload-directory.php, examples/06-walk-and-cleanup.php.

Retry policy

Every transfer is wrapped in a RetryPolicyInterface. The default is ExponentialBackoffRetryPolicy (5 retries, 200 ms base, 30 s cap, ±30% jitter). Swap or disable per-client:

The retry helper:

  1. Never retries AuthenticationException, ConfigurationException, or InvalidPathException — those are caller bugs, not transient.
  2. Always retries ConnectionException (transport-level).
  3. Retries TransferException only on a curated message-fragment allowlist (Failed to copy, Unable to open remote, Could not SCP-download, Could not SCP-upload) — anything else indicates real filesystem state and is propagated.
  4. On each retry, if the SSH session is detected dead via ping(), it runs a one-shot doConnect() to revive it. The stored connect args are reused so transparent re-establishment is automatic.

SftpClient::ping(): bool is also exposed as a cheap keepalive (it stats / over SFTP and never throws).

To write your own policy:

Verified by Behat: fault-injection.feature (latency + bandwidth-cap toxics: retry holds up under real adversity), readme.feature → setRetryPolicy(NoRetryPolicy) at runtime takes effect, readme.feature → ping() returns true on a healthy session, readme.feature → ping() returns false after close(). Unit tests: RetryWiringTest, ExponentialBackoffRetryPolicyTest, NoRetryPolicyTest. Runnable: examples/04-retry-policy.php.

Checksum verification

For end-to-end integrity beyond size verification, plug a RemoteHasherInterface implementation:

Verified by Behat: readme.feature → RedownloadRemoteHasher passes on a clean round-trip, needs-shell.feature → ShellSumRemoteHasher verifies a clean round-trip using sha256sum (runs against the OpenSSH fixture only — see Operations note). Unit tests: Checksum/RemoteHasherTest.

Prefixes

setRemotePrefix is applied to BOTH sides of rename() (the original 0.x applied it only to the source — that was bug B6, fixed in 1.0).

Every remote path (with or without a prefix) goes through PathValidator before any SFTP call. Rejected inputs: null bytes (\0), CR/LF and other C0/C1 control characters, . / .. components, paths over 4096 bytes by default. Absolute paths bypass the configured remote prefix rather than concatenating to it (T6 contract); the joined result is re-validated so attackers can't smuggle traversal through the prefix.

Verified by Behat: malicious-paths.feature (T1 traversal / T3 CR-LF / T4 length / T5 dot-component / T6 prefix bypass against the live atmoz/sftp container), readme.feature → setRemotePrefix applies to relative paths, readme.feature → an absolute remote path bypasses the remote prefix (T6 contract). Unit tests: Path/PathValidatorPropertyTest (T2 null-byte and other byte-level cases — .feature files can't carry those bytes literally).

Error handling

All errors are typed exceptions extending IDCT\Networking\Ssh\Exception\SshException (itself a RuntimeException):

Exception When
ConfigurationException Missing credentials, bad mode, invalid key path
ConnectionException TCP/handshake failure, fingerprint mismatch, no SFTP session yet
AuthenticationException ssh2_auth_* rejected the credentials
TransferException Upload / download / SCP failure
RemoteFilesystemException Remote stat / mkdir / rmdir / rename / unlink / list failure
InvalidPathException Path validation rejected the input (extends ConfigurationException)

Verified by Behat: readme.feature → Every library error is caught by the SshException root. Unit tests: Exception/ExceptionHierarchyTest.

Logging

SftpClient implements Psr\Log\LoggerAwareInterface (defaults to NullLogger). Every record carries a correlation_id (per-connection 16-char hex), host, and port for downstream log aggregation. A lint test (tests/unit/LoggerRedactionLintTest.php) fails the build if any source line contains password / passphrase near a log call.

Verified by Behat: readme.feature → setLogContext merges static fields into every record (asserts every captured record carries the caller-supplied tenant + request_id keys plus the auto-injected correlation_id). Unit tests: LoggerIntegrationTest.

Examples

Runnable scripts live in examples/. They connect to the dockerised SFTP fixture used by the Behat suite — see examples/README.md for the one-line bring-up command.

Verified by Behat: The "Verified by Behat" callouts under each section point at the specific scenario(s) backing the example. The README-mirroring scenarios live in readme.feature; per-area behaviour matrices live alongside (ops.feature, directory-policies.feature, etc.). One small subset — needs-shell.feature covering scpUpload / scpDownload / ShellSumRemoteHasher — runs only against the OpenSSH fixture (port 2223): the default atmoz/sftp fixture is chrooted to internal-sftp, so it can't exec scp or sha256sum. CI runs both backends.

# Script What it shows
01 01-basic.php Round-trip a file via SFTP
02 02-progress.php CLI progress bar via ProgressListenerInterface
03 03-resume-upload.php Abort an upload mid-stream and resume
04 04-retry-policy.php Customise ExponentialBackoffRetryPolicy
05 05-upload-directory.php Recursive upload with ConflictPolicy::Skip
06 06-walk-and-cleanup.php walk() + removeDirectoryTree()
07 07-stream-from-s3.php uploadStream an HTTP object served by minio

Upgrading from 0.x

0.x 1.x
new AuthMode::PASSWORD (class const) AuthMode::Password (enum under Auth\)
new Credentials(); ->setMode(); ->setUsername(); … Credentials::withPassword($u, $p) etc.
\Exception everywhere typed Exception\* hierarchy under one SshException root
$client->connect($host, $port) same, plus timeoutSeconds, expectedFingerprint, fingerprintAlgorithm, fingerprintEncoding, knownHostsFile, onUnknownHost, securityProfile
Credentials::withPublicKey(...) accepted any string now validates that the key files exist
rename($from, $to) applied prefix only to $from applies prefix to both sides
getFileList() returned . and .. filtered by default; includeDotEntries: true to keep
close() ran ssh2_exec($conn, 'logout') (broken) uses ssh2_disconnect()
Upload landed bytes directly at the destination atomic .partial-{uuid} + rename by default

Class moves: everything under IDCT\Networking\Ssh\… is now grouped by domain (Auth\, HostKey\, Retry\, Path\, Progress\, Ssh2\, Directory\, KnownHosts\, Checksum\, Security\). The CHANGELOG.md "P1 (revised)" section has the full old→new FQN table.

See CHANGELOG.md for the per-version history. 1.0 bundles the original modernization pass (bug-fix list B1B12, security baseline S1S5) with the production-grade feature work (P1P11) — atomic transfers, resume, recursive directory ops, streaming, progress, retry, known-hosts, and opt-in checksums.

Development

CI runs PHP 8.2 / 8.3 / 8.4 across both atmoz and OpenSSH 9.x backends in .github/workflows/ci.yml. The unit-test coverage gate enforces 100% line coverage on every file except src/Ssh2/Ssh2Functions.php (the thin ext-ssh2 delegation layer covered by Behat).

License

MIT — see LICENSE.


All versions of sftp-client with dependencies

PHP Build Version
Package Version
Requires php Version >=5.4.0
ext-ssh2 Version >=0.12
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package idct/sftp-client contains the following files

Loading the files please wait ...