cURL can talk to both FTP and SFTP from one CLI, which makes it practical for scripted uploads, pulls, and directory checks when an environment still has legacy FTP endpoints alongside SSH-backed SFTP servers. The transfer verbs stay familiar across both schemes, but the remote URL, authentication method, and security posture are not interchangeable.
For FTP, the path in ftp://host/path is relative to the login directory unless it begins with a double slash that targets the server root. For SFTP, the path in sftp://host/path is absolute by default, and /~/ is the shorthand for the remote account's home directory. That path difference is where many copied commands go wrong, especially when a transfer succeeds but lands in the wrong place.
Use ~/.netrc or a restricted automation account for password-based FTP jobs, and prefer SSH keys for SFTP. List the target directory before upload or download, because --remote-name saves the filename from the URL as-is and --ftp-create-dirs can create missing remote directories on both FTP and SFTP transfers.
Steps to use cURL with FTP and SFTP:
- Confirm that the local cURL build supports both FTP and SFTP before you script transfers.
$ curl --version | awk '/^Protocols:|^Features:/' Protocols: dict file ftp ftps gopher gophers http https imap imaps ipfs ipns ldap ldaps mqtt pop3 pop3s rtsp scp sftp smb smbs smtp smtps telnet tftp Features: alt-svc AsynchDNS HSTS HTTP2 HTTPS-proxy IPv6 Largefile libz SSL threadsafe UnixSockets
The Protocols line must include both ftp and sftp for this mixed workflow.
- Prepare one small local file so the upload, download, and verification steps all target the same masked payload.
$ mkdir -p ~/curl-transfer-demo $ cd ~/curl-transfer-demo $ printf 'account_id,generated_at\n48217,2026-03-28T16:14:00Z\n' > partner-feed-2026-03.csv $ wc -c partner-feed-2026-03.csv 51 partner-feed-2026-03.csv
A short CSV keeps the examples realistic while making filename and byte-count checks easy to confirm.
- List the target FTP directory before transferring files so the remote path is confirmed first.
$ curl --silent --show-error --list-only --netrc ftp://ftp.example.net/incoming/ 2026-03 incoming.manifest
In FTP URLs, /incoming/ is relative to the login directory. Use ftp://ftp.example.net//incoming/ when the server expects an absolute path from the filesystem root.
- Upload the local file to FTP and create any missing remote directories in the same command.
$ curl --ftp-create-dirs --netrc --upload-file ~/curl-transfer-demo/partner-feed-2026-03.csv ftp://ftp.example.net/incoming/2026-03/partner-feed-2026-03.csv % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 51 0 0 100 51 0 428 --:--:-- --:--:-- --:--:-- 432
--ftp-create-dirs creates missing remote path segments for both FTP and SFTP transfers.
- Download the same FTP file with the remote filename so the saved local name matches the URL path automatically.
$ mkdir -p ~/curl-transfer-demo/ftp-download $ cd ~/curl-transfer-demo/ftp-download $ curl --remote-name --netrc ftp://ftp.example.net/incoming/2026-03/partner-feed-2026-03.csv $ ls -lh partner-feed-2026-03.csv -rw-r--r-- 1 user staff 51B Mar 28 16:14 partner-feed-2026-03.csv
--remote-name overwrites an existing local file with the same name in the current directory.
Related: Save cURL output to a file
- List the target SFTP directory with SSH key authentication before writing to it.
$ curl --silent --show-error --list-only --user svc_partnerdrop: --key ~/.ssh/id_ed25519 sftp://sftp.example.net/~/incoming/ 2026-03 incoming.log
In SFTP URLs, /~/incoming/ means the incoming directory under the remote login account's home directory. Use a full path such as /var/data/incoming/ when the destination is outside the home tree.
- Upload the same file to SFTP with the SSH private key and create the missing remote subdirectory on the server.
$ curl --ftp-create-dirs --upload-file ~/curl-transfer-demo/partner-feed-2026-03.csv --user svc_partnerdrop: --key ~/.ssh/id_ed25519 sftp://sftp.example.net/~/incoming/2026-03/partner-feed-2026-03.csv % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 51 0 0 100 51 0 236 --:--:-- --:--:-- --:--:-- 239
cURL can usually extract the matching public key from --key automatically, so --pubkey is only needed when the SSH backend cannot derive it.
- Download the SFTP file with its remote filename so the transfer pattern stays close to the FTP example.
$ mkdir -p ~/curl-transfer-demo/sftp-download $ cd ~/curl-transfer-demo/sftp-download $ curl --remote-name --user svc_partnerdrop: --key ~/.ssh/id_ed25519 sftp://sftp.example.net/~/incoming/2026-03/partner-feed-2026-03.csv $ ls -lh partner-feed-2026-03.csv -rw-r--r-- 1 user staff 51B Mar 28 16:18 partner-feed-2026-03.csv
The saved filename comes from the last path segment in the URL, so switch to the intended local directory first or add --output-dir.
Related: Save cURL output to a file
- Resume an interrupted FTP download with the same output path instead of restarting the file from byte zero.
$ cd ~/curl-transfer-demo/ftp-download $ curl -C - -o partner-archive-2026-03-28.tar.gz --netrc ftp://ftp.example.net/incoming/2026-03/partner-archive-2026-03-28.tar.gz ** Resuming transfer from byte position 262144 % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 768k 100 768k 0 0 2679k 0 --:--:-- --:--:-- --:--:-- 2688k
Resume is safe only when the partial local file still matches the same remote object. If the remote file changed, delete the partial copy and start again.
Related: Resume a download with cURL
- Verify that both remote locations now contain the expected uploaded filename before handing the transfer off to a deploy or backup job.
$ curl --silent --show-error --list-only --netrc ftp://ftp.example.net/incoming/2026-03/ | grep '^partner-feed-2026-03.csv$' partner-feed-2026-03.csv $ curl --silent --show-error --list-only --user svc_partnerdrop: --key ~/.ssh/id_ed25519 sftp://sftp.example.net/~/incoming/2026-03/ | grep '^partner-feed-2026-03.csv$' partner-feed-2026-03.csv
A quick directory check catches wrong path assumptions earlier than a failed restore, unpack, or downstream automation step.
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.
