FTP Commands and Reply Codes: Reading the Conversation Like a Pro
Somewhere in your logs right now there is a line like 550 Permission denied or 425 Can't open data connection, and somebody is guessing at what it means. They don't have to guess. FTP's replies are not random error strings — they are a tidy, three-digit classification system in which every digit carries meaning. Learn the system once and you can read any FTP log, from any server, and know within seconds whether the problem is credentials, firewalls, permissions, or disk space.
This article is the working reference for that skill. We cover the command vocabulary that actually matters in practice, decode the reply-code system digit by digit, walk through a healthy session with annotations, and finish with a field table that maps the codes you will actually meet to their most common root causes and first fixes. It is part of our The FTP Protocol series, and it pairs naturally with the two-channel architecture — that article explains where the conversation happens; this one teaches you to read it.
The Shape of the Conversation
Everything on FTP's control connection follows one grammar. The client sends a command: a short keyword, optionally followed by an argument, ending the line. The server answers with a reply: a three-digit code, a space, and human-readable text. Command, reply, command, reply, for the life of the session.
Two facts about that grammar save you from a great deal of confusion later:
- The code is the contract; the text is commentary. The standard fixes what each number means, but leaves the wording to the server. One server says
530 Not logged in, another says530 Authentication failed, a third says530 Sorry. Same code, same meaning. Never match on the text in a script — match on the number. - Your client paraphrases. When a graphical client shows "The server refused the connection," a raw numbered reply arrived underneath. The wire truth is in the client's log or debug window, and it is always more precise than the paraphrase. Get in the habit of finding the raw log first.
The Commands That Matter
The protocol defines a few dozen commands; day-to-day work uses about twenty. One translation note before the list: what you type is not what gets sent. Typing ls sends LIST; typing get budget.xlsx sends RETR budget.xlsx; typing put sends STOR. The list below is the wire vocabulary — the words you will see in logs and captures.
Session and identity:
USER/PASS— offer a username, then its password. Expect331afterUSERand230afterPASS.SYST— ask the server what kind of system it is;FEAT— ask which optional features it supports.QUIT— end the session;NOOP— "no operation," a do-nothing command some clients send to look busy and keep idle connections alive.
Navigation and listing:
PWD— print the current directory;CWD— change directory;CDUP— go up one level.LIST— full directory listing;NLST— bare filenames only;MLSD— a machine-readable listing format that well-behaved modern automation prefers because its output is standardized.
Transfer setup:
TYPE A/TYPE I— ASCII (text, with line-ending translation) versus image (exact binary copy). The wrong choice silently corrupts files.PASV,PORT,EPSV,EPRT— negotiate the data connection for the next transfer. These four are the active/passive machinery, covered properly in the four data-connection commands.
Moving data:
RETR— download a file;STOR— upload a file;APPE— append to a remote file.REST— restart a transfer from a byte offset, the basis of resume support;ABOR— abort the transfer in progress.
Managing files:
DELE— delete a file;MKD/RMD— make and remove directories.RNFR/RNTO— rename, in two steps: "rename from" this name, then "rename to" that name. The pair matters for reliable automation, as we will see below.SIZE/MDTM— ask a file's size and modification time;SITE— a grab-bag for server-specific extras like permission changes.
The Three-Digit Reply System, Decoded
Now the other half of the conversation. Every reply code is three digits, and the digits are positional — each one answers a different question.
The first digit answers: "how did that go, and what should I do now?" This is the digit to read first, always, because it alone determines your next move:
| First digit | Meaning | What to do |
|---|---|---|
| 1xx | Preliminary — action started, another reply is coming | Wait for the follow-up reply |
| 2xx | Success — the action completed | Proceed |
| 3xx | Intermediate — fine so far, the server needs the next piece | Send the expected follow-up command |
| 4xx | Transient failure — didn't work, might work later | Retry after a delay; investigate if it persists |
| 5xx | Permanent failure — will fail the same way every time | Stop retrying; fix the cause first |
The second digit answers: "what is this about?" A 0 concerns command syntax; a 2 concerns connections (control or data); a 3 concerns authentication and accounts; a 5 concerns the file system. (A 1 marks informational replies, and 4 is officially "unspecified" — you will rarely need either.)
The third digit just distinguishes replies within a category, and is only meaningful alongside the first two.
Put the digits together and codes stop being trivia. 530: permanent failure (5), about authentication (3) — bad credentials, fix before retrying. 425: transient failure (4), about a connection (2) — the data channel could not be opened, and hammering retry without fixing the network will not help much, but it is not a credentials problem and never will be. 550: permanent failure (5), about the file system (5) — the path, the permissions, or the file itself. You just triaged three tickets without reading a single word of reply text.
Remember: the first digit is the retry policy. 4xx means "try again later — the world may change." 5xx means "trying again is pointless until you change something." Automation that treats these two the same either gives up too easily or retries forever against a wall.
A Healthy Session, Annotated
Here is a complete, successful upload session as it appears on the wire — the kind of transcript you should be able to skim and pronounce healthy at a glance. Lines starting with a code are the server; the rest are the client; our notes follow the # marks. This one uses the safe-upload pattern worth stealing for your own jobs: upload under a temporary name, then rename.
220 FTP service ready. # 2xx greeting: the server is willing USER pipeline 331 Password required for pipeline. # 3xx: fine so far — send the next piece PASS ******** 230 User pipeline logged in. # 2xx: authentication complete TYPE I 200 Type set to I. # binary type: bytes will not be altered CWD inbound 250 CWD command successful. PASV 227 Entering Passive Mode (198,51,100,20,196,10). # data port = 196*256+10 = 50186 STOR invoice-batch.zip.part # upload under a TEMPORARY name 150 Opening BINARY mode data connection. # 1xx: work started — bytes now flowing 226 Transfer complete. # 2xx: all bytes arrived, data channel closed RNFR invoice-batch.zip.part 350 File exists, ready for destination. # 3xx: rename step one accepted RNTO invoice-batch.zip 250 Rename successful. # the file appears under its real name, whole QUIT 221 Goodbye.
Notice the rhythm: 3xx replies always sit in the middle of a two-step exchange (USER/PASS, RNFR/RNTO), 1xx marks the start of a transfer, and 2xx closes every completed action. The temporary-name-then-rename ending is not decoration — it guarantees that any process watching the inbound folder can never grab a half-written file, because the real filename only appears after the final byte has landed.
The Field Table: Codes You Will Actually Meet
The standard defines many codes; operations reality uses a much shorter list. This table is the one to keep within reach mid-incident.
| Code | The server means | Usual root causes | First move |
|---|---|---|---|
| 421 | Service not available; closing connection | Connection limit reached; idle timeout expired; server going down | Reconnect later; check limits and timeouts |
| 425 | Can't open data connection | Firewall blocking; passive range not reachable; mode mismatch | Switch mode; verify firewall and passive range |
| 426 | Connection closed; transfer aborted | Data connection died mid-transfer: network drop, timeout, reset | Retry; investigate stability if recurring |
| 450 | File action not taken; file unavailable | File locked or in use by another process | Retry shortly; find what holds the lock |
| 451 | Local error in processing | Server-side fault: disk error, internal failure | Check the server's own logs |
| 452 | Insufficient storage space | Disk full or nearly full on the server | Free space; add monitoring so it never recurs |
| 500 / 502 | Syntax error / command not implemented | Client sent a command this server doesn't support | Usually harmless — clients probe features, then fall back |
| 503 | Bad sequence of commands | Out-of-order steps, e.g. RNTO without RNFR | Fix the script's command order |
| 530 | Not logged in | Wrong credentials; disabled account; anonymous refused | Verify the account before anything else |
| 550 | File unavailable | Not found; no permission; wrong path or case | Check path, case, and permissions — in that order |
| 552 | Exceeded storage allocation | The account's quota is full | Clean up or raise the quota |
| 553 | File name not allowed | Illegal characters; naming policy; no right to create that name | Rename to something plain and retry |
Codes 220, 226, 227, 230, 250, 331, and 350 are the healthy-session cast you met in the transcript above — when those are the only codes in a log, nothing went wrong.
Same Code, Different Stories
The table gives you the shortlist; experience teaches you to pick the right suspect. Three codes are worth extra depth because each hides several distinct stories:
550 is three problems wearing one number. "File not found," "permission denied," and "that's a directory, not a file" can all arrive as 550. Narrow it down in seconds: run a listing of the parent directory. If the name is absent, it is a path problem — check spelling and remember many servers are case-sensitive, so Report.CSV and report.csv are different files. If the name is present but the transfer fails, it is a permissions or ownership problem, which on a multi-user server usually traces back to how the account was set up — two accounts can truthfully see different rights on the same folder.
421 is about the service, not you. It appears when the server refuses or drops the whole control connection: the connection limit is full (common when an automation job leaks sessions and never sends QUIT), the idle timeout expired while a human wandered off to lunch, or the server is shutting down. If a scheduled job gets 421 every night at the same minute, count concurrent connections before blaming the network.
425 versus 426 is "never started" versus "died partway." A 425 means the data connection could not be established at all — firewall and mode territory, diagnosed step by step in diagnosing FTP mode failures. A 426 means the data connection opened and then collapsed mid-transfer — flaky networks, timeouts, or something actively cutting long-lived connections. The distinction matters because the fixes live in different places: 425 sends you to configuration, 426 sends you to network stability.
To see the digits earn their keep, here is a failing session in miniature — a nightly job that had worked for months:
CWD inbound 250 CWD command successful. STOR nightly-export.zip.part 150 Opening BINARY mode data connection. 552 Transfer aborted. Exceeded storage allocation. # 5 = permanent, 5 = file system QUIT 221 Goodbye.
Reading by digits: permanent failure, file-system category — the account's quota is full, and retrying tonight, tomorrow, and forever will produce the same 552 until someone cleans up or raises the quota. A job that understood the first digit would have paged someone on the first failure instead of failing quietly for a week. That is the entire case for learning this system.
When the code alone is not enough, remember there are two logs. The client saw one side of the conversation; the server logged the same session with more context — which account, which real file path, what the server was doing at the time. On a Windows server such as Sysax Multi Server, the activity log gives you that server-side view, and reading both sides together usually turns "mystery" into "obvious."
Multiline Replies and Other Parsing Traps
One wrinkle catches nearly every home-grown script eventually. A reply can span multiple lines. The format: the first line uses a hyphen after the code (230-), continuation lines follow, and the reply ends with the same code followed by a space (230 ).
230-Welcome to the Example Corp transfer server. 230-Scheduled maintenance windows are announced on the intranet. 230-All sessions are logged. 230 User pipeline logged in.
Only that last line ends the reply. A script that reads one line and moves on will desynchronize — it treats a banner line as the answer to its next command, and every exchange after that is off by one. The same trap hides in the multi-line replies to FEAT and STAT, and in login banners some servers send before 220. Mature FTP libraries handle this correctly; fragile inherited scripts often do not, which is one of several reasons to read reading the FTP automation you inherited before trusting an old job.
Reply Codes and Automation
For a human, reply codes are a convenience. For unattended transfers, they are the only truth available — and two rules follow.
Branch on codes, not on luck. A scheduled job should treat 2xx as success, 4xx as retry-with-backoff, and 5xx as stop-and-alert. The classic counterexample is the built-in Windows ftp.exe, beloved of ancient batch files: it exits with success status even when a transfer failed, so the batch file reports a green run while the file never moved. If you have jobs built on it, that discovery is waiting for you.
Let the tool do the bookkeeping. Hand-rolled scripts rarely implement backoff, distinguish 4xx from 5xx, or record which reply ended the run. A purpose-built scheduler does this as its day job — Sysax FTP Automation, for instance, builds retry and error handling into every scheduled transfer, so a transient 4xx becomes a quiet retry and a hard failure becomes an error you actually hear about, instead of a silent gap in tomorrow's data.
Reading a Log Like a Pro: The Summary
The whole skill compresses to four habits. Find the raw log, not the client's paraphrase. Read the first digit for the verdict and the retry policy. Read the second digit for the subject — connection, authentication, or file system. Then check the field table for the specific code's usual suspects. With those habits, 530 stops meaning "FTP is broken" and starts meaning "check the account"; 425 stops meaning "the network is haunted" and starts meaning "check the data channel."
To go deeper, FTP's failure modes approaches the same territory from the opposite direction — symptom first, code second — and the FTP lab guide shows you how to trigger most of these codes on purpose, which is the fastest way to make them stick. For the connection-class codes, the Active vs Passive FTP series is the definitive companion.
Frequently Asked Questions
What is the difference between 4xx and 5xx errors in FTP?
Why do I get 550 when I can see the file in the listing?
Does a 226 reply guarantee my file transferred correctly?
What causes a 421 error?
Are FTP reply codes the same on every server?
From the Sysax team: we build secure file transfer software for Windows — Sysax Multi Server, an FTP, FTPS, SFTP, and HTTPS server, and Sysax FTP Automation for scheduled, scripted transfers. Free trials are on the download page.
