SFTPsync
Small PHP CLI utility to synchronize directories and manage files on a remote server over SFTP.
What It Does
- Sync local directory to remote (
--sync) - Sync remote directory to local (
--sync-down) - Delete one remote file (
--delete) - Delete remote directory recursively (
--delete-dir) - Combine multiple actions in one run (executed in CLI order)
- Skip selected paths during sync or delete using repeatable rules
Requirements
- PHP 8+ (CLI)
- PHP
ssh2extension enabled (ssh2_connect,ssh2_sftp, ...) - Network access from your machine to the target SFTP host
Quick Start
From repository root:
php src/SFTPsync.php --host example.com --user myuser --password mypass --sync ./local /var/www/app
If --host, --user, or --password is missing, the script asks for it interactively (TTY only).
CLI Usage
php src/SFTPsync.php --host <host> --user <user> --password <password> [--port <port>] <actions...>
Actions (repeatable)
--sync <local_dir> <remote_dir>: upload local changes to remote--sync-down <remote_dir> <local_dir>: download remote changes to local--delete <remote_file>: delete one remote file--delete-dir <remote_dir>: delete remote directory recursively (with safety checks)
Options
--host <host>: required (or prompted)--user <user>: required (or prompted)--password <password>: required (or prompted)--port <port>: optional, default22--print-relative: show paths relative to action root in logs--no-print-skip: suppressSKIPstatus lines during execution--skip <pattern>: repeatable, exact names/paths or glob patterns (*,?), applied to--syncand--sync-down--skip-delete <pattern>: repeatable, exact names/paths or glob patterns (*,?), applied to--deleteand--delete-dir-h,--help: show help
Examples
# Upload local -> remote
php src/SFTPsync.php --host example.com --user u --password p --sync ./app /srv/app
# Download remote -> local
php src/SFTPsync.php --host example.com --user u --password p --sync-down /srv/backups ./backups
# Multiple actions in one run (executed left-to-right)
php src/SFTPsync.php --host example.com --user u --password p \
--sync ./a /remote/a \
--delete /remote/a/old.zip \
--sync-down /remote/logs ./logs
# Skip selected entries during sync
php src/SFTPsync.php --host example.com --user u --password p \
--skip .git --skip node_modules --skip "*.log" --skip "cache/*" \
--sync ./app /srv/app
# Delete remote directory but keep selected subpaths
php src/SFTPsync.php --host example.com --user u --password p \
--skip-delete .well-known --skip-delete uploads/keep \
--delete-dir /srv/app
Sync Behavior
For each file pair, transfer happens when:
- target file does not exist, or
- file size differs, or
- source mtime is newer than target mtime
After upload/download, mtime is propagated to the target when possible.
Skip Rule Matching
- Rule without wildcard characters (example:
node_modules) keeps exact matching. - Exact rule without slash (example:
node_modules) matches any path segment with that name. - Exact rule with slash (example:
cache/tmp) matches that subpath within a relative path. - Rule containing
*or?is treated as a glob pattern.*matches any characters, and?matches one character. - Glob rule without slash (example:
*.log) can match file or directory names at any depth. - Glob rule with slash (example:
src/temp/*.logorcache/*) is matched against relative paths. - Rules are normalized to forward slashes.
Examples:
--skip=*.batskipstest.batandtools/deploy.bat, but nottest.bat.txt.--skip=*.logskipsapp.logandsrc/temp/test.log, but notapp.log.1.--skip=backup-*skipsbackup-2025,backup-old, andbackup-test.--skip=cache/*skips content undercache.--skip=node_modulesand--skip=.gitkeep the original exact-name behavior.
Safety Notes
--delete-dirrefuses dangerous roots such as empty path,/,.,.., and similar dot paths.- Delete operations run only on the remote side.
- Path handling normalizes slashes and trims duplicate separators.
Exit Codes
0success1runtime/SFTP error2invalid CLI arguments
Output
The script prints status lines (MKDIR, UPLOAD, DOWNLOAD, DELETE, RMDIR, SKIP, ERROR) and a final summary with operation counters.
Use --no-print-skip if you want to keep skipped-item accounting in the final summary but hide individual SKIP log lines during the run.
Languages
PHP
100%