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:

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

  7. 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.

  8. 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.

  9. 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.

  10. 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.