curl FTP, FTPS, and SFTP: Upload, Download, and Script Transfers From the Command Line
When a shell script has to move a file, curl is the shortest path between this machine and that server. The basic commands are short; the details are where scripts break. Here are both.
When a shell script needs to move a file, curl is often the shortest path between "it is on this machine" and "it is on the server."
It supports FTP, FTPS, and, depending on how it was built, SFTP. The basic commands are straightforward, and the details matter: FTP sends credentials in clear text, FTPS has two connection modes, SFTP support varies by installation, and unattended jobs need more than a command pasted into cron. This guide covers the commands worth knowing and the precautions that keep them reliable in production.
The FTP and FTPS examples use --netrc so credentials stay out of the command; the credential file is covered further down.
FTP transfers with curl
FTP is widely supported, and it does not encrypt credentials or file contents. Use it only when a server leaves no alternative or another trusted network layer protects the connection. The FTP versus FTPS versus SFTP post lays out the differences.
Download a file:
curl --netrc --output orders.csv ftp://ftp.harborline.example/exports/orders.csv
Without --output, curl writes the file to standard output. --remote-name saves it under its remote filename instead.
Upload a file with --upload-file, or its short form -T:
curl --netrc --upload-file orders.csv ftp://ftp.harborline.example/inbound/
The trailing slash matters. It tells curl to upload into that directory using the local filename. To give the remote file a different name, put it in the URL: ftp://ftp.harborline.example/inbound/orders-2026-09-02.csv.
List a directory with --list-only, which asks for a name-only listing with the FTP NLST command rather than the more verbose LIST:
curl --netrc --list-only ftp://ftp.harborline.example/exports/
--ftp-create-dirs creates missing remote directories on upload, subject to the account's permissions. --quote, or -Q, sends raw FTP commands; they run before the transfer by default, and a leading dash runs one afterward:
curl --netrc --upload-file orders.csv ftp://ftp.harborline.example/inbound/ --quote "-DELE orders.csv.old"
Be careful with paths there. FTP commands run relative to the server's current working directory, which curl may have changed while processing the URL.
curl uses passive FTP by default, which is right behind firewalls and NAT. --ftp-port - forces active mode for the rare older server that requires it, and active mode usually needs additional firewall configuration. The active versus passive post explains why.
FTPS transfers with curl
FTPS is FTP protected by TLS. It is not SFTP, and it comes in two connection styles. The FTPS explainer covers the protocol.
Explicit FTPS begins as a normal FTP connection, usually on port 21, and upgrades it with AUTH TLS. Use an ftp:// URL with --ssl-reqd, which makes the command fail rather than silently continue without TLS if the server cannot establish an encrypted connection:
curl --netrc --ssl-reqd --upload-file orders.csv ftp://ftp.harborline.example/inbound/
Implicit FTPS starts with TLS immediately and traditionally uses port 990. Use the ftps:// scheme:
curl --netrc --upload-file orders.csv ftps://ftp.harborline.example/inbound/
Use the mode the server requires; the two are not interchangeable. curl verifies the server certificate against its configured trust store, and that verification stays on. For a certificate signed by a private certificate authority, provide the CA certificate with --cacert /etc/company-certs/partner-ca.pem. --insecure, or -k, disables verification; it is useful in a controlled test and it is not a production fix, because encryption without identity verification leaves the connection open to impersonation.
SFTP transfers with curl
SFTP runs over SSH and, despite the name, is unrelated to FTP and FTPS.
Upload with an SSH key:
curl --user 'dana:' --key "$HOME/.ssh/id_ed25519" --knownhosts "$HOME/.ssh/known_hosts" \
--upload-file orders.csv sftp://sftp.harborline.example/inbound/
Download a file:
curl --user 'dana:' --key "$HOME/.ssh/id_ed25519" --knownhosts "$HOME/.ssh/known_hosts" \
--output invoices.zip sftp://sftp.harborline.example/outbound/invoices.zip
The empty password after dana: prevents an interactive password prompt when key authentication is intended. Some SSH backends also want the public key named separately with --pubkey "$HOME/.ssh/id_ed25519.pub"; usually the private key file carries enough information.
Always verify the SFTP server's host key in an unattended job, with --knownhosts pointing at a known_hosts file. Without it, encryption protects the connection and nothing proves the script reached the intended server. Obtain the expected host key through a trusted channel before adding it to known_hosts; do not accept whatever key appears on the first scheduled run.
Check whether your curl supports SFTP
Unlike FTP and FTPS, SFTP support is not in every curl build. curl has to be compiled with a supported SSH backend. Check before designing a script around it:
curl -V
Look for sftp in the Protocols line. If it is missing, curl reports curl: (1) Protocol "sftp" not supported. The curl that ships with macOS and the curl.exe bundled with Windows 10 and 11 omit SFTP; Homebrew's curl on macOS and most Linux distribution packages include it. The installed build, not the operating system, is the authority. If you install another curl, make sure the scheduled job runs that executable: cron, launchd, and Task Scheduler often have a different PATH from your interactive shell.
Keep credentials out of the command
curl --user dana:s3cret ... is convenient and unsafe. The password lands in shell history, in logs, and in the process list.
For FTP and FTPS, create a netrc file with machine ftp.harborline.example, login dana, and password s3cret on separate lines, restrict it with chmod 600 ~/.netrc, and use curl --netrc. --netrc-file names a dedicated file. A netrc file still holds a reusable secret in plain text, so protect it with file permissions and operating-system access controls.
For SFTP, use a dedicated SSH key with the narrowest permissions the server supports, and do not give a transfer-only account an interactive shell it does not need. Passphrase-protected keys work with curl, but agent and key-format support varies by SSH backend, so test the exact build and decide how the key is unlocked before deployment. Never put a passphrase on the command line.
Treat exit codes as part of the transfer
A script must never assume "curl ran" means "the file arrived." curl returns 0 on success and a nonzero status for failed authentication, DNS failure, certificate rejection, a missing remote file, or an unsupported protocol:
if ! curl --netrc --upload-file orders.csv ftp://ftp.harborline.example/inbound/; then
echo "Upload failed" >&2
exit 1
fi
For unreliable connections, --retry 5 --retry-delay 10 --retry-all-errors helps. Use broad retries carefully with uploads: the operation has to be safe to repeat, usually by uploading to a deterministic name or a temporary name that is renamed only after completion. --fail and --fail-with-body are for HTTP response errors; FTP and SFTP failures already produce nonzero exit codes, so capture and monitor those directly.
Why scheduled transfers need more than a curl command
Putting a working curl command into cron or Task Scheduler is easy. Operating it reliably is the harder part. Where are the password, API key, or private key stored? Is the FTPS certificate or SFTP host key actually verified? What happens when a connection drops halfway through an upload, and can the upload be retried safely? How are partial files kept away from downstream processes? Who hears when an expected file does not arrive? Is there an audit trail? You can build those controls around curl, and they quickly outgrow the transfer command itself.
Moving the protocol out of the script with Files.com
Files.com puts a partner's FTP, FTPS, or SFTP server behind a folder on your Files.com site with a Remote Server Mount. Files.com stores the remote credentials, verifies the FTPS certificate or pins the SFTP host key, and passes folder operations through to the partner's server in real time. The local job then uses HTTPS and one Files.com API key instead of carrying every partner's protocol configuration and credentials.
The Files.com CLI runs on Linux, macOS, and Windows, and an upload to the mounted folder is one command with resumable, parallel transfers:
files-cli upload orders.csv /partners/harborline/inbound/
For workflows that need no local code, a Sync moves files on a schedule and an Automation renames, routes, copies, or processes files after they arrive. Transfer activity lands in the audit log, and a change to a partner's server configuration is made once on Files.com rather than on every machine running a job.
curl remains an excellent tool for a direct, well-contained transfer. Once the job needs centralized credentials, monitoring, recovery, routing, and auditability, moving those responsibilities out of the script makes the system easier to operate. The SFTP automation post covers the same ground for the OpenSSH sftp client, and the Python FTP post for ftplib.
Frequently asked questions
How do I upload a file over FTP with curl?
curl --netrc -T localfile ftp://host/path/. The trailing slash preserves the local filename; to choose a remote name, include it as the final URL segment.
How do I use curl with FTPS?
For explicit FTPS, use an ftp:// URL with --ssl-reqd. For implicit FTPS, use ftps://, normally on port 990. Use --cacert when the server certificate is signed by a private CA.
How do I upload over SFTP with curl?
curl --user 'user:' --key ~/.ssh/id_ed25519 --knownhosts ~/.ssh/known_hosts -T localfile sftp://host/path/. The installed curl has to include SFTP support.
Why does curl say "Protocol sftp not supported"?
That curl build has no SSH backend with SFTP support. Run curl -V, check the Protocols line, install a curl package that lists sftp, and make sure the scheduled job invokes that version.
How do I keep a password out of the curl command?
For FTP and FTPS, store it in a permission-restricted netrc file and use --netrc or --netrc-file. For SFTP, use a dedicated SSH key and verify the server with a trusted known_hosts file.
Keep reading
- SFTP Automation: A Cron-Safe Script, and the Way That Needs No ScriptSFTP automation done properly: the sftp -b batch file, a bash script that is safe to run from cron (locking, logging, upload-then-rename, real exit codes), lftp for mirroring, and the way that needs no script, where Files.com makes the SFTP connections and runs the schedule with retries, logs, and alerts.
- Python FTP: Uploading, Downloading, and Securing Transfers With ftplibPython FTP with ftplib: upload, download, and list files, switch to FTP_TLS with a verified certificate and an encrypted data channel, and a third way where Files.com makes the FTP or FTPS connection so the script never speaks the protocol. Passive mode, firewalls, and running from cron.
- PowerShell FTP: FtpWebRequest, the WinSCP .NET Assembly, and curl.exe for FTP and FTPSPowerShell has no FTP cmdlet. The three real options for FTP and FTPS from PowerShell, with working upload and download code, the credential and scheduling details that decide whether the script survives, and a way to take FTP out of PowerShell entirely.
- FTPS Explained: Implicit vs Explicit FTPS, Ports 990 and 21, and What the Certificate DoesFTPS is FTP secured with TLS: not SFTP, not a new protocol, and heir to every quirk of the FTP it encrypts. How explicit and implicit FTPS differ, which ports each uses, what the certificate verifies, why firewalls struggle with it, and when it is still the right choice.
- How to Use scp: Examples, the Options That Matter, and When to Use SFTP or rsync InsteadThe scp command copies files between machines over SSH in one line. Every example you will need, the options worth remembering, SSH keys and host verification, what scp cannot do, and when SFTP or rsync is the better tool.