Fast, secure and simple SFTP uploads for GitHub Actions.
Deploy your build output to any SFTP server, from a three-line workflow step up to fully configured multi-target deployments.
- ⚡ Fast: written in Go, files are transferred in parallel with concurrent SFTP write requests per file.
- 🔒 Secure: optional host key pinning verifies the server's identity; atomic per-file uploads never leave half-written files; credentials are only ever read from environment variables.
- 🧩 Simple, but configurable: sensible defaults for the simple case; gitignore-style excludes, sync/clean strategies, delete guards, dry runs, retries and outputs for the complex ones.
- 🖥️ Cross-platform: runs natively on
ubuntu-*,macos-*andwindows-*runners, with no Docker required.
- Why easySFTP?
- Quick start
- Inputs & outputs
- Strategies
- Config file for multiple targets
- Security
- Documentation
- Versioning
- Contributing
- License
I needed an SFTP upload step for a GitHub Actions deploy, and none of the existing options really fit. They were either no longer actively maintained, not resource-friendly, hard or barely configurable, or they simply did not work reliably. So I built my own and made it available for everyone with the same problem: an action that stays out of your way for the simple case and still handles the complex ones.
Here is how easySFTP compares to other open-source actions that tackle the same job:
| Feature | easySFTP | Dylan700/sftp‑upload‑action | SamKirkland/FTP‑Deploy‑Action | wlixcc/SFTP‑Deploy‑Action | wangyucode/sftp‑upload‑action |
|---|---|---|---|---|---|
| Protocol | SFTP | SFTP | FTP / FTPS only | SFTP | SFTP |
| Implementation | Go binary | Node.js | Node.js | Docker (rsync) | Python |
| Linux / macOS / Windows | yes / yes / yes | yes / yes / yes | yes / yes / yes | Linux only (Docker) | needs Python on runner |
| Host key verification | yes (pinned fingerprints) | no | n/a | not documented | not documented |
| Atomic per-file upload | yes | no | no | rsync temp file | no |
| Skip unchanged files | yes (SHA256 manifest) | no | yes (state file) | partial (rsync) | yes (MD5 hashes) |
| Delete removed files | yes (tracked, via sync) | yes (full wipe) | yes | yes (full wipe) | yes (tracked) |
| Delete safety guards | yes (root refusal, max_deletes) | no | no | no | no |
| Multiple targets / strategies | yes (config file) | multiple mappings | single directory | single directory | single directory |
| Dry run | yes | no | yes | no | no |
| Actively maintained | yes | last release 2024 | yes | yes | yes |
The matrix reflects each project's public documentation as of July 2026. "Not documented" means the feature was not found in that action's README, not that it is impossible. SamKirkland's FTP-Deploy-Action is by far the most popular deploy action, but it speaks FTP/FTPS rather than SFTP, so it is listed for context rather than as a direct SFTP alternative.
easySFTP is a clean, from-scratch implementation in Go, inspired by the no-longer-maintained Dylan700/sftp-upload-action:
- compiled static binary instead of a Node.js runtime (fast startup, parallel transfers)
- works on Linux, macOS and Windows runners, with no Docker required
- host key verification, atomic uploads, retries with backoff, structured outputs and a job summary
- end-to-end test suite against an in-process SFTP server, plus a CI self-test against a real OpenSSH server
- name: Deploy via SFTP
uses: eiserv/easySFTP@v1
with:
server: sftp.example.com
username: ${{ secrets.SFTP_USERNAME }}
password: ${{ secrets.SFTP_PASSWORD }}
uploads: ./dist/ => /var/www/html/That's it. Everything else is optional. More recipes (key auth, multi-target
deploys, PR previews, .sftpignore) live in docs/examples.md.
The most used inputs:
| Input | Default | Description |
|---|---|---|
server / port / username |
- / 22 / - |
Where and as whom to connect. |
password / private-key / passphrase |
- | Authentication, at least one of password/key. Use secrets. |
host-key-fingerprint |
- | Pin the server's SHA256 host key(s). Strongly recommended. |
uploads |
- | One local => remote mapping per line; directories are recursive. |
strategy |
overlay |
How the remote side is reconciled: overlay, sync or clean. |
ignore / ignore-from |
- | Gitignore-style excludes (inline / from a file). |
dry-run |
false |
Log what would happen, change nothing. |
concurrency / retries / timeout |
4 / 2 / 30 |
Parallelism, per-file retries, connection timeout (s). |
Outputs: files-uploaded, files-deleted, files-skipped, bytes-uploaded,
duration-ms, plus a summary table in the job summary.
➡ Full reference with every input, output and rule: docs/configuration.md
| Strategy | Uploads | Deletes | Use for |
|---|---|---|---|
overlay (default) |
all files | nothing | adding/updating files, leaving everything else in place |
sync |
new & changed files | files a previous sync uploaded but that are now gone locally | keeping a directory an exact mirror of your build, safely |
clean |
all files | everything in the remote target first | a guaranteed-fresh deploy |
sync is manifest-based: it only ever deletes files it uploaded itself, skips
unchanged files by content hash, and re-deploys only transfer what changed.
Destructive strategies are protected by delete guards:
the remote root is always refused, and max_deletes caps how much a single run
may delete. Preview anything with dry-run: true.
➡ Details, manifest semantics and guard rules: docs/strategies.md
Multiple targets with different strategies? Point config-file at a YAML file
(connection settings stay in inputs/secrets):
# .github/easysftp.yml
version: 1
guards:
max_deletes: 200
targets:
- local: ./dist/
remote: /var/www/html/
strategy: sync
- local: ./docs/
remote: /var/www/docs/
strategy: cleanA JSON Schema for editor autocomplete/validation is bundled. See docs/configuration.md and the commented example config.
Two rules cover most of it:
-
Pin the host key. Without
host-key-fingerprint, any server is accepted. Set it so a man-in-the-middle fails the deploy instead:$ ssh-keyscan sftp.example.com | ssh-keygen -lf - -
Keep credentials in encrypted secrets, prefer key-based auth, and use a least-privilege deploy account.
➡ Full guide (key setup, chroot, SHA pinning): docs/security.md · Vulnerability reports: SECURITY.md
| Configuration reference | All inputs, outputs, mappings, ignore rules, config file. |
| Strategies | overlay/sync/clean, manifest, delete guards, dry runs. |
| Examples & use cases | Copy-paste recipes for common deployments. |
| Security guide | Host key pinning, credentials, supply chain. |
| Troubleshooting & FAQ | Common errors and fixes. |
easySFTP follows Semantic Versioning:
uses: eiserv/easySFTP@v1 # latest 1.x, recommended, gets fixes & features
uses: eiserv/easySFTP@v1.2.3 # exact, immutable pinv1/v1.2 are rolling tags; v1.2.3 and commit SHAs never move. Releases and
the changelog are generated automatically from Conventional Commits. See
docs/RELEASING.md.
Contributions are welcome! See CONTRIBUTING.md for the dev setup, test suite and PR conventions, and the good first issues for places to start. This project follows a Code of Conduct.