Most SFTP cheat sheets are either a wall of undifferentiated commands with no context, or a table with zero explanation for why a flag does what it does. This one's different. Commands are grouped by what you're actually trying to do — not by alphabetical order or whatever the man page happened to list first.
This guide covers connecting, uploading, downloading, recursive transfers, permissions, batch mode, scripting, CI usage, and debugging, all using the OpenSSH sftp client.
One important scope note: these examples target the OpenSSH sftp client, which is the default on Linux and macOS. Windows ships it in modern Windows 10/11 too, though plenty of Windows users reach for WinSCP or FileZilla as a GUI alternative — both speak the same SFTP protocol underneath. Commands like reget, reput, and progress are OpenSSH-specific, so don't expect them in other clients. If you're trying to move large files reliably, skip ahead to SFTP vs SCP vs rsync before picking your tool.
Jump to what you need:
- Connecting
- Navigating directories
- Uploading files
- Downloading files
- Managing files and permissions
- Batch mode and scripting
- One-liner transfers
- CI and GitHub Actions
- Common flags
- SFTP vs SCP vs rsync
- Testing your Rilavek SFTP endpoint
Connecting
Port 22 is the default. Most servers you'll hit use it. But some — Rilavek included — run on a non-standard port, so always check before assuming the connection failed.
# Standard connection (port 22)
sftp user@hostname
# Non-standard port — use -P (uppercase)
sftp -P 2222 user@hostname
# Connect with a specific SSH private key
sftp -i ~/.ssh/id_rsa user@hostname
# Connect with verbose output (debug connection issues)
sftp -v user@hostname
# Connect through a bastion host / jump host
sftp -J jumpuser@bastion user@target
# Override the SFTP subsystem path (when server doesn't expose it as default)
sftp -s /usr/lib/ssh/sftp-server user@hostname
The flag trap: -p (lowercase) preserves modification times, access times, and modes during transfer. -P (uppercase) sets the port. They're not the same flag. Mix them up and you'll get an error that doesn't immediately point you to the problem.
-J (ProxyJump) is worth knowing if you work in environments where servers aren't directly reachable from the internet. It replaces the older and more verbose -o ProxyCommand=... approach and works on any OpenSSH >= 7.3. You can also bake the jump host into ~/.ssh/config using ProxyJump bastion so you don't have to type it every time.
-s (subsystem) is one of those flags you won't touch for months and then desperately need. If a connection succeeds but immediately drops, or you get "Received message too long," the server probably isn't advertising SFTP as its default subsystem. Passing the subsystem path directly fixes it.
Navigating Directories
Once connected, you're in an interactive prompt. Most commands look like shell commands you already know — but there's one thing that trips people up almost every time: any command prefixed with l runs on your local machine, not the remote server. ls lists remote files. lls lists local files. Same idea applies to cd vs lcd and pwd vs lpwd.
# Show your current remote directory
pwd
# Show your current LOCAL directory
lpwd
# List remote directory contents
ls
# long listing with hidden files
ls -la
# List LOCAL directory contents
lls
lls -la
# Change remote directory
cd /path/to/remote/dir
# Change LOCAL directory
lcd /path/to/local/dir
If you're getting "no such file" errors on upload, run lpwd and lls before anything else. The interactive shell does start in your current local directory, but after a few lcd commands it's easy to lose track of where you actually are.
Uploading Files
Uploading a Single file
put localfile.txt
# upload with a different name
put localfile.txt remotefile.txt
# Upload from an absolute local path
put /full/path/to/localfile.txt
Uploading Multiple files with a wildcard
put *.csv
put /data/exports/*.json
Recursive directory upload
put -R local_directory/
put -R local_directory/ remote_directory_name/
The recursive gotcha: -R doesn't create the parent remote directory for you. If remote_directory_name doesn't already exist on the server, put -R will either fail outright or behave in confusing ways. Create the directory first with mkdir remote_directory_name, then run the recursive upload.
Resume an interrupted transfer (OpenSSH only)
OpenSSH's SFTP client supports reget and reput for resuming interrupted transfers:
reget largefile.bin # resume a partially downloaded file
reput largefile.bin # resume a partially uploaded file
reget/reput only work if the partial file already exists at the destination with the same filename, and the server has to support partial transfers — not all do. When it works, it's great. When it doesn't, rsync -avz --partial -e ssh is the reliable fallback.
Downloading Files
Downloading a Single file
get remotefile.txt
# save with a different name
get remotefile.txt /local/save/path/localfile.txt
Downloading Multiple files with a wildcard
get *.log
get /remote/path/*.csv /local/destination/
Recursive directory download
get -R remote_directory/
get -R /remote/path/directory/ /local/destination/
Wildcard expansion is a subtle one. Depending on the SFTP implementation, wildcards get expanded either by the client or by the server. If get *.csv comes back with "no such file," run ls *.csv first. If the server can resolve it there, it'll work in get too.
Managing Files and Permissions
Create and remove directories
mkdir new_directory
rmdir empty_directory # only removes empty directories
mkdir -p isn't in the SFTP spec, and OpenSSH sftp doesn't support it. If you need a nested path, you have to build it layer by layer:
mkdir path
mkdir path/to
mkdir path/to/nested
mkdir path/to/nested/directory
Delete files
rm remotefile.txt
There's no SFTP equivalent of rm -rf. If you need to recursively wipe a remote directory, drop to an SSH session and do it there — ssh user@host "rm -rf /path/to/dir" — or remove files one by one. Not pretty, but that's the protocol.
Rename and move
rename old_filename.txt new_filename.txt
rename old_directory/ new_directory/
Set permissions
chmod 644 remotefile.txt
chmod 755 remote_directory/
Check file size and metadata
# timestamps, size, permissions
ls -la
# check available remote disk space
# (not all servers support this)
df
Batch Mode and Scripting
Interactive SFTP is fine for one-off transfers. For anything repeatable — nightly jobs, deploy scripts, data pipelines — you want batch mode.
Run commands from a file
sftp -b commands.txt user@hostname
The contents of commands.txt look exactly like what you'd type at the interactive prompt:
cd /remote/uploads
lcd /local/exports
put report_2026.csv
put report_2026_summary.csv
bye
Error handling is more nuanced than you might expect. OpenSSH sftp aborts the batch when commands like get, put, rename, rm, mkdir, ls, or df fail. If there's a specific step you expect might fail — and you want to continue past it — prefix that line with - in the batch file. Either way, check $? in your wrapper and verify that the expected files actually made it to the destination. Don't just trust a zero exit code.
Pipe commands directly
echo "put localfile.txt" | sftp -b - user@hostname
Full automation script example
#!/bin/bash
HOST="sftp.example.com"
PORT="22"
USER="deploy_user"
KEY="~/.ssh/deploy_key"
REMOTE_DIR="/uploads/processed"
LOCAL_DIR="/tmp/ready_to_upload"
sftp -P "$PORT" -i "$KEY" -b - "$USER@$HOST" << EOF
cd $REMOTE_DIR
lcd $LOCAL_DIR
put *.csv
bye
EOF
if [ $? -ne 0 ]; then
echo "SFTP transfer failed" >&2
exit 1
fi
The << EOF heredoc sends commands to SFTP's batch mode via stdin. It's cleaner than writing a temp file, and in security-conscious environments where you're careful about what touches /tmp, it's good practice.
One-Liner Transfers
For quick, non-interactive uploads from the shell without writing a commands file:
# Upload a single file in one line
sftp -P 2222 user@hostname <<< $'put file.txt\nbye'
# Download a file in one line
sftp -P 2222 user@hostname <<< $'get remotefile.txt\nbye'
The $'...' ANSI-C quoting syntax is how you embed literal newlines in a single shell string. Works in bash and zsh. Handy for a quick one-off without spinning up a full script.
CI and GitHub Actions
SFTP in CI has one specific failure mode that will confuse you the first time: the host key prompt. If the known hosts file isn't pre-populated, the connection just... sits there waiting for input that never arrives, and the job times out.
StrictHostKeyChecking=no skips the prompt. UserKnownHostsFile=/dev/null keeps the runner filesystem clean. Both are fine on ephemeral CI machines that get destroyed after the job runs. Just don't copy-paste these flags onto a long-lived server. Disabling host key checking on a persistent machine removes your protection against man-in-the-middle attacks.
For GitHub Actions specifically:
- name: Upload artifacts via SFTP
env:
SFTP_KEY: ${{ secrets.SFTP_PRIVATE_KEY }}
run: |
echo "$SFTP_KEY" > /tmp/sftp_key
chmod 600 /tmp/sftp_key
sftp -o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
-i /tmp/sftp_key \
-P 2222 \
-b deploy/sftp_commands.txt \
[email protected]
rm /tmp/sftp_key
Write the private key to a temp file, lock it down with chmod 600 (SSH refuses to use keys with loose permissions — it won't just warn you, it'll reject the key outright), then clean up after. Passing it in via -i keeps it out of ssh-agent, which simplifies the job environment considerably.
Common Flags
| Flag | What it does | Notes |
|---|---|---|
-P port | Set port number | Uppercase P. Common values: 22, 2222 |
-i identity_file | SSH private key path | Can also be set in ~/.ssh/config |
-b batch_file | Run commands from a file | Use -b - to read from stdin |
-r | Recursive (with get/put) | Target remote directory must exist for put -r |
-J jump_host | Connect via bastion/jump host | Replaces the older -o ProxyCommand pattern |
-s subsystem | Override SFTP subsystem path | Use when server doesn't expose SFTP as default subsystem |
-v | Verbose output | Add more -v flags (-vv, -vvv) for more detail |
-q | Quiet mode | Suppresses progress meter — useful in scripts |
-C | Compression | Rarely worth enabling; most connections are already bandwidth-limited by other factors |
-o option | SSH option passthrough | e.g., -o StrictHostKeyChecking=no for first-time connections in CI |
-l limit | Bandwidth limit in Kbit/s | Useful when sharing a connection |
SSH config shortcut
Typing the full flag string on every sftp invocation gets old fast, especially when you're juggling multiple servers. Stick a host entry in ~/.ssh/config and you only type it once:
Host myserver
HostName sftp.myserver.com
User deploy
Port 2222
IdentityFile ~/.ssh/prod_key
Then sftp myserver is all you need.
Interactive Session Reference
Every command available at the interactive sftp> prompt, with accurate signatures for the OpenSSH client:
| Command | Description |
|---|---|
pwd | Print remote working directory |
lpwd | Print local working directory |
ls [path] | List remote directory |
lls [path] | List local directory |
cd [path] | Change remote directory |
lcd [path] | Change local directory |
get [-afpR] remote [local] | Download file |
put [-afpR] local [remote] | Upload file |
mkdir path | Create remote directory |
rmdir path | Remove remote directory (must be empty) |
rm file | Delete remote file |
rename old new | Rename remote file or directory |
chmod mode file | Change remote file permissions |
chown uid file | Change remote file owner |
chgrp gid file | Change remote file group |
copy old new / cp old new | Copy a remote file (server support required) |
ln [-s] old new | Create a remote hard link or symlink |
symlink old new | Create a remote symbolic link |
lmkdir path | Create a local directory |
lumask umask | Set the local umask |
reget [-fpR] remote [local] | Resume interrupted download (OpenSSH only) |
reput [-fpR] local [remote] | Resume interrupted upload (OpenSSH only) |
progress | Toggle progress meter on/off (OpenSSH client only) |
df | Show remote disk usage (server must support it) |
version | Show SFTP protocol version |
help / ? | List all available commands |
bye / exit / quit | Close the connection |
! | Drop to local shell (type exit to return) |
The ! command is genuinely underused. If you're mid-session and you want to check something locally before uploading — inspect a file, run a quick grep — !ls -la or !cat localfile.txt drops you to a local shell without closing the connection. Type exit to come back.
Testing Your Rilavek SFTP Endpoint
Rilavek runs a standard SFTP endpoint on port 2222, which means any OpenSSH sftp client, CI runner, or automation script connects to it without any special SDK or library. Host is sftp.rilavek.com, credentials come from the pipe you set up in the dashboard.
Basic connection test
sftp -P 2222 [email protected]
You'll get a password prompt (or skip it entirely with key auth if you've added your SSH public key to the pipe). A successful connection drops you into the sftp> prompt.
Upload a test file
sftp -P 2222 [email protected]
sftp> put /tmp/test.txt
sftp> bye
Check your S3 bucket — or whichever data store the pipe points at — and the file should be there immediately. Rilavek streams straight to your storage. It doesn't write to its own disk.
Automated upload script for Rilavek
#!/bin/bash
RILAVEK_HOST="sftp.rilavek.com"
RILAVEK_PORT="2222"
RILAVEK_USER="your_user"
KEY_PATH="~/.ssh/rilavek_key"
LOCAL_FILES="/data/exports/*.csv"
sftp -P "$RILAVEK_PORT" -i "$KEY_PATH" -b - "$RILAVEK_USER@$RILAVEK_HOST" << EOF
put $LOCAL_FILES
bye
EOF
Using SSH config for convenience
Host rilavek
HostName sftp.rilavek.com
User your_user
Port 2222
IdentityFile ~/.ssh/rilavek_key
After adding that to ~/.ssh/config, connecting is just:
sftp rilavek
For a quick one-liner upload using the alias:
sftp rilavek <<< $'put /local/file.csv\nbye'
Verbose connection debug
Connection failing? Verbose mode breaks down exactly where the handshake stops:
sftp -v -P 2222 [email protected]
Common failure points:
- Authentication failure: Check your Rilavek credentials or SSH key setup
- Connection refused: Verify port 2222 is not blocked by your firewall or VPN
- Connection hangs before password prompt: Almost always a firewall blocking outbound TCP 2222. The SYN packet gets through but the server's response is dropped. Test with
nc -zv sftp.rilavek.com 2222to confirm connectivity before debugging further. - Host key verification: On first connection, accept the host key. To skip in CI:
-o StrictHostKeyChecking=no
The One Thing Most Cheat Sheets Don't Tell You
SFTP and SCP use the same SSH transport. They're not the same tool. The old SCP protocol was deprecated over security concerns, and modern OpenSSH versions now run scp over SFTP internally anyway — so your scp commands still work, they're just speaking SFTP under the hood now. For anything new, write to sftp or rsync -e ssh directly.
SFTP vs SCP vs rsync
All three tools move files over SSH. They're not interchangeable — each has a specific situation where it's the right call.
| Tool | Best for | Weakness |
|---|---|---|
sftp | Interactive sessions, simple scripts, when the server speaks SFTP only | No built-in resume for arbitrary transfers; reget/reput are server-dependent |
scp | Quick one-off copies between two hosts | Legacy SCP protocol deprecated; now uses SFTP internally in modern OpenSSH |
rsync | Large files, resume, syncing directories, skipping unchanged files | Requires rsync installed on both client and server |
For big files, anything that might get interrupted, or syncing a whole directory, rsync is the better tool:
rsync -avz --partial --progress -e "ssh -p 2222" /local/path/ [email protected]:/
--partial keeps the partially transferred file in place so a retry picks up where it left off rather than starting over. rsync skips unchanged files, shows real progress output, and recovers from interruptions gracefully. Use sftp for interactive sessions, quick scripts, and servers that only speak SFTP. Use rsync when you need durability.