FTP servers are notoriously difficult to test reliably, and JMeter’s built-in FTP Request sampler often falls short of providing the deep performance insights you need.

Let’s put JMeter’s FTP Request sampler to work and see how it behaves under load. First, we’ll set up a simple test plan.

<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.4.1">
  <hashTree>
    <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="FTP Performance Test" enabled="true">
      <stringProp name="TestPlan.comments"></stringProp>
      <boolProp name="TestPlan.functional_mode">false</boolProp>
      <boolProp name="TestPlan.serialize_thread_groups">false</boolProp>
      <elementProp name="TestPlan.userDefinedVariables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments">
        <collectionProp name="Arguments.arguments"/>
      </elementProp>
      <stringProp name="TestPlan.userDefineMacros"></stringProp>
      <stringProp name="TestPlan.encoding">UTF-8</stringProp>
      <boolProp name="TestPlan.disable_static_code_loader">false</boolProp>
    </TestPlan>
    <hashTree>
      <FTPRequest guiclass="FTPRequestGui" testclass="FTPRequest" testname="FTP Upload Test" enabled="true">
        <elementProp name="FTPRequest.command_data" elementType="FTPFileArgs" guiclass="FTPFileArgsGui" testclass="FTPFileArgs">
          <collectionProp name="FTPFileArgs.arguments">
            <elementProp name="argument" elementType="Argument">
              <stringProp name="Argument.name">STOR</stringProp>
              <stringProp name="Argument.value">/remote/path/testfile.txt</stringProp>
              <stringProp name="Argument.desc"></stringProp>
            </elementProp>
          </collectionProp>
        </elementProp>
        <stringProp name="FTPSampler.server">ftp.example.com</stringProp>
        <stringProp name="FTPSampler.port">21</stringProp>
        <stringProp name="FTPSampler.username">testuser</stringProp>
        <stringProp name="FTPSampler.password">password123</stringProp>
        <stringProp name="FTPSampler.localFile">/local/path/testfile.txt</stringProp>
        <stringProp name="FTPSampler.remoteFile">/remote/path/testfile.txt</stringProp>
        <stringProp name="FTPSampler.binaryMode">true</stringProp>
        <stringProp name="FTPSampler.passiveMode">true</stringProp>
        <stringProp name="FTPSampler.timeout">0</stringProp>
        <stringProp name="FTPSampler.controlEncoding">UTF-8</stringProp>
      </FTPRequest>
      <hashTree>
        <ResultCollector guiclass="ViewResultsTreeGui" testclass="ResultCollector" testname="View Results Tree" enabled="true">
          <boolProp name="IsSaveAllSamples">false</boolProp>
          <elementProp name="ResultCollector.actions" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments">
            <collectionProp name="Arguments.arguments"/>
          </elementProp>
          <stringProp name="filename"></stringProp>
        </ResultCollector>
        <hashTree/>
      </hashTree>
    </hashTree>
  </hashTree>
</jmeterTestPlan>

This setup uses the FTPRequest sampler to upload a file (testfile.txt) to ftp.example.com on port 21, using testuser with password123. It’s configured for binary mode and passive mode, with a local file path of /local/path/testfile.txt and a remote path of /remote/path/testfile.txt. The View Results Tree listener will show us the outcome of each request.

When you run this, you’ll observe that the FTPRequest sampler primarily measures the time taken for the entire FTP transaction, from the initial connection to the final confirmation. This includes connection establishment, login, command execution, data transfer, and disconnection. What it doesn’t easily expose are the granular timings of each distinct phase, which is crucial for pinpointing bottlenecks. For instance, you can’t directly see how long the actual data stream took versus how long the server took to acknowledge completion.

The core problem this solves is understanding where the time is spent during an FTP file transfer under load. Is it the network latency between the JMeter client and the FTP server? Is it the server’s disk I/O? Is it the FTP protocol overhead itself? Without breaking down the transaction, you’re guessing.

Internally, the FTPRequest sampler uses Apache Commons Net to interact with the FTP server. It establishes a control connection, sends commands (like USER, PASS, STOR), negotiates a data connection (either active or passive mode), transfers the file content, and then closes the connections. The sampler reports the total time from initiating the first command to receiving the final server response for that command sequence.

The key levers you control are the server, port, username, password, localFile, remoteFile, binaryMode, and passiveMode settings. Beyond these, the actual performance is dictated by your network conditions, the FTP server’s configuration and load, and the size of the file being transferred.

Most people don’t realize that the timeout parameter in the FTPRequest sampler doesn’t just limit the total transaction time. It applies individually to socket read operations on the control connection. If the server takes too long to respond to a command (e.g., an ABOR or QUIT), the sampler might time out, even if the data transfer itself was fast. This can lead to misleading results if you’re expecting it to cap the entire operation.

To get a deeper understanding, you’ll need to augment JMeter’s capabilities, often by correlating its results with network monitoring tools or by implementing custom logic to measure specific parts of the transfer. The next problem you’ll likely encounter is how to isolate and measure network latency versus server-side processing time.

Want structured learning?

Take the full Jmeter course →