ftp — Transfer files over FTP — legacy plaintext protocol; prefer SFTP today across all 5 shells
Equivalents in every shell
ftp ftp.example.comConnects to a remote FTP server (default port 21). Authentication credentials and all data travel UNENCRYPTED — modern alternatives are `sftp` (SSH-based) and `https://...` for download-only. Linux `ftp` is typically `inetutils-ftp` or `ftp` from netkit. For scripted one-shot transfers, `curl ftp://...` is the more flexible binary.
ftp ftp.example.comSame external. macOS REMOVED the system FTP client in 10.13 (High Sierra) — install via `brew install inetutils` for `gftp`, or use `curl ftp://...` (which honours `~/.netrc` for credentials and supports passive mode by default).
ftp ftp.example.comSame external. Scripted FTP from fish is much cleaner via `curl` than the interactive client — fish's quoting rules and the FTP client's command-mode prompt mix awkwardly.
$w = [System.Net.WebClient]::new(); $w.Credentials = [System.Net.NetworkCredential]::new('user','pass'); $w.DownloadFile('ftp://ftp.example.com/file','file')PowerShell has NO `Invoke-FTPRequest` cmdlet. Standard options: (1) `.NET` `WebClient` / `FtpWebRequest` classes for full control, (2) the bundled Windows `ftp.exe`, (3) `Invoke-WebRequest` for basic `ftp://` GET/PUT (no listings). For real SFTP use the `Posh-SSH` module — modern, but not FTP.
ftp -s:commands.txtBuilt-in Windows FTP client. Interactive when launched bare; `-s:scriptfile` runs a sequence of FTP commands non-interactively. Limited (no passive-mode toggle on some builds), unencrypted, and reads the password from the script in cleartext — never check that file into git.
Worked examples
Download one file from an FTP server (one-shot)
curl -u user:pass ftp://ftp.example.com/path/file -o fileInvoke-WebRequest ftp://user:[email protected]/path/file -OutFile fileftp -s:get.txtUpload a file to an FTP server
curl -T file -u user:pass ftp://ftp.example.com/incoming/$c = [System.Net.WebClient]::new(); $c.Credentials = [System.Net.NetworkCredential]::new('user','pass'); $c.UploadFile('ftp://ftp.example.com/incoming/file','file')List a remote directory
curl -u user:pass ftp://ftp.example.com/incoming/((Invoke-WebRequest ftp://user:[email protected]/incoming/ -UseBasicParsing).Content -split '\r?\n')Gotchas
- Plaintext FTP transmits PASSWORDS and DATA unencrypted — anyone on the network sees everything. The replacement is `sftp` (SSH-based, port 22) for new work, or FTPS (FTP-over-TLS, `AUTH TLS` on port 21 / implicit on 990) for legacy servers that must stay on the FTP family. Don't deploy new FTP servers in 2026.
- PASV vs PORT (passive vs active) mode decides which side opens the data channel. Behind NAT / firewalls, passive mode (`ftp -p`, `curl --ftp-pasv`) is almost always required. Some Windows `ftp.exe` builds DON'T support passive at all — the symptom is `LIST` / `GET` hanging after auth succeeds. Switch to `curl` or `Invoke-WebRequest` in that case.
- macOS removed the system FTP client in 10.13. `brew install inetutils` provides `gftp`, but for most uses `curl ftp://...` is the right answer — one binary handles `.netrc` auth, passive mode, resume, and retry. The same `curl` recipe runs on Linux, macOS, and Windows (the bundled `curl.exe`).
- PowerShell 5.1's `Invoke-WebRequest -Uri ftp://...` works for GET and accepts `-Credential`, but it ALWAYS buffers the whole response in memory before writing `-OutFile` — multi-GB transfers OOM. For large files use `[System.Net.FtpWebRequest]` with a streaming read, or shell out to `curl.exe`.
- cmd `ftp -s:commands.txt` reads a script of FTP-level commands (`open host`, `user name`, `get file`, `bye`). The password line goes ON DISK in cleartext — never check it into git, and overwrite the file after use. The cleaner pattern is `curl` / `Invoke-WebRequest` with credentials passed via env or SecureString.