Skip to content

How to Download and Upload Files to an FTP Server in Python

Learn how to use Python's built-in ftplib module to securely connect to an FTP server, upload/download files, manage directories, and implement FTPS encryption for secure transfers.

FTP (File Transfer Protocol) is one of the oldest and most widely used protocols for transferring files between a client and a remote server. Whether you need to automate backups, sync files across systems, or integrate with legacy infrastructure, knowing how to interact with an FTP server programmatically is a valuable skill.

Python's built-in ftplib module makes it straightforward to connect to an FTP server, upload files, download files, and manage remote directories, all without installing any third-party packages.

This guide walks you through the entire process with complete, runnable examples.

What Is FTP?

File Transfer Protocol (FTP) is an application-layer protocol that runs on top of TCP. It is designed specifically for transferring files between a client and a server over a network. FTP uses two separate connections:

  • Control connection (port 21): handles commands and responses (login, directory listing, etc.).
  • Data connection (port 20): transfers the actual file data.

While FTP is widely supported, it transmits data (including credentials) in plain text. For secure transfers, consider using FTPS (FTP over TLS) via ftplib.FTP_TLS, which is also covered in this guide.

What Is the ftplib Module?

The ftplib module is part of Python's standard library, so no installation required. It provides the FTP class (and FTP_TLS for secure connections) that implements the client side of the FTP protocol. With it, you can:

  • Connect and authenticate with an FTP server
  • Upload and download files
  • List, create, rename, and delete remote directories and files
  • Switch between binary and text transfer modes

Connecting to an FTP Server

The first step in any FTP operation is establishing a connection and authenticating.

import ftplib

HOSTNAME = "ftp.example.com"
USERNAME = "user@example.com"
PASSWORD = "password"

ftp_server = ftplib.FTP(HOSTNAME, USERNAME, PASSWORD)

ftp_server.encoding = "utf-8"

print(ftp_server.getwelcome())

Sample output:

220 Welcome to the Test FTP Server

Using Environment Variables for Credentials

import os
import ftplib

HOSTNAME = os.getenv("FTP_HOST", "ftp.example.com")
USERNAME = os.getenv("FTP_USER", "user@example.com")
PASSWORD = os.getenv("FTP_PASS", "password")

ftp_server = ftplib.FTP(HOSTNAME, USERNAME, PASSWORD)
ftp_server.encoding = "utf-8"

Uploading a File to an FTP Server

To upload a file, use the storbinary() method, which transfers data in binary mode. The command passed to the method follows the format STOR <filename>.

import ftplib

HOSTNAME = "ftp.example.com"
USERNAME = "user@example.com"
PASSWORD = "password"

ftp_server = ftplib.FTP(HOSTNAME, USERNAME, PASSWORD)
ftp_server.encoding = "utf-8"

filename = "example.txt"

with open(filename, "rb") as file:
    ftp_server.storbinary(f"STOR {filename}", file)

print(f"'{filename}' uploaded successfully.")

print("\nRemote directory contents:")
ftp_server.dir()

ftp_server.quit()

Sample output:

'example.txt' uploaded successfully.

Remote directory contents:
-rw-r--r--    1 0        0              25 Jul 23 12:00 example.txt

How It Works

  1. The file is opened in binary read mode ("rb").
  2. storbinary(command, file) reads the file object and sends its contents to the server.
  3. The STOR command tells the FTP server to store the incoming data as a file with the given name.
  4. ftp_server.dir() lists the remote directory contents to verify the upload.

Downloading a File from an FTP Server

To download a file, use the retrbinary() method with the RETR <filename> command. The method accepts a callback function that processes each chunk of data, typically file.write.

import ftplib

HOSTNAME = "ftp.example.com"
USERNAME = "user@example.com"
PASSWORD = "password"

ftp_server = ftplib.FTP(HOSTNAME, USERNAME, PASSWORD)
ftp_server.encoding = "utf-8"

filename = "example.txt"

with open(filename, "wb") as file:
    ftp_server.retrbinary(f"RETR {filename}", file.write)

print(f"'{filename}' downloaded successfully.")

with open(filename, "r") as file:
    print(f"\nFile content:\n{file.read()}")

ftp_server.quit()

Sample output:

'example.txt' downloaded successfully.

File content:
Hello from Tutorial Reference!

How It Works

  1. A local file is opened in binary write mode ("wb").
  2. retrbinary(command, callback) retrieves the remote file in chunks and passes each chunk to the callback (file.write).
  3. After the download completes, the file is read back in text mode to verify its contents.

Listing Remote Files and Directories

Before uploading or downloading, you may want to inspect the remote directory structure:

import ftplib

ftp_server = ftplib.FTP("ftp.example.com", "user@example.com", "password")
ftp_server.encoding = "utf-8"

print("Detailed listing:")
ftp_server.dir()

print()

print("File names only:")
files = ftp_server.nlst()
for f in files:
    print(f"  {f}")

ftp_server.quit()
Method Description
ftp.dir() Prints a detailed directory listing (like ls -l)
ftp.nlst() Returns a list of file and directory names
ftp.mlsd() Returns an iterable of (name, facts) tuples with metadata

You can change the working directory, create new directories, and remove them:

import ftplib

ftp_server = ftplib.FTP("ftp.example.com", "user@example.com", "password")
ftp_server.encoding = "utf-8"

print(f"Current directory: {ftp_server.pwd()}")

ftp_server.mkd("test_folder")

ftp_server.cwd("test_folder")
print(f"Changed to: {ftp_server.pwd()}")

ftp_server.cwd("..")

ftp_server.rmd("test_folder")

ftp_server.quit()

Adding Error Handling

Production code should handle connection failures, authentication errors, and missing files gracefully:

import ftplib

HOSTNAME = "ftp.example.com"
USERNAME = "user@example.com"
PASSWORD = "password"

try:
    ftp_server = ftplib.FTP(HOSTNAME, USERNAME, PASSWORD, timeout=10)
    ftp_server.encoding = "utf-8"
    print("Connected successfully.")

    filename = "example.txt"
    with open(filename, "wb") as file:
        ftp_server.retrbinary(f"RETR {filename}", file.write)
    print(f"'{filename}' downloaded successfully.")

except ftplib.error_perm as e:
    print(f"FTP permission error: {e}")

except ftplib.all_errors as e:
    print(f"FTP error: {e}")

except FileNotFoundError:
    print(f"Local file path is invalid.")

finally:
    try:
        ftp_server.quit()
        print("Connection closed.")
    except Exception:
        pass

Common Mistake: Forgetting to Close the Connection

Leaving an FTP connection open wastes server resources and may cause your program to hang:

ftp_server = ftplib.FTP(HOSTNAME, USERNAME, PASSWORD)
ftp_server.retrbinary(f"RETR {filename}", open(filename, "wb").write)

The correct approach is to always close the connection, ideally using a try/finally block or a context manager:

ftp_server = ftplib.FTP(HOSTNAME, USERNAME, PASSWORD)
try:
    with open(filename, "wb") as file:
        ftp_server.retrbinary(f"RETR {filename}", file.write)
finally:
    ftp_server.quit()

Using FTPS for Secure Transfers

Standard FTP sends credentials and data in plain text, which is a security risk. For encrypted transfers, use ftplib.FTP_TLS:

import ftplib

HOSTNAME = "ftp.example.com"
USERNAME = "user"
PASSWORD = "password"

ftp_server = ftplib.FTP_TLS(HOSTNAME, USERNAME, PASSWORD)

ftp_server.prot_p()

ftp_server.encoding = "utf-8"

ftp_server.dir()

ftp_server.quit()

Complete Example: Upload and Download

Here is a complete, self-contained script that demonstrates both uploading and downloading a file:

import ftplib
import os

HOSTNAME = "ftp.example.com"
USERNAME = "user@example.com"
PASSWORD = "password"
FILENAME = "test_upload.txt"

def create_sample_file(filename):
    """Create a sample text file for testing."""
    with open(filename, "w") as f:
        f.write("Hello! This file was uploaded via Python ftplib.\n")
    print(f"Created local file: {filename}")

def upload_file(ftp, filename):
    """Upload a file to the FTP server."""
    with open(filename, "rb") as file:
        ftp.storbinary(f"STOR {filename}", file)
    print(f"Uploaded: {filename}")

def download_file(ftp, remote_filename, local_filename):
    """Download a file from the FTP server."""
    with open(local_filename, "wb") as file:
        ftp.retrbinary(f"RETR {remote_filename}", file.write)
    print(f"Downloaded: {remote_filename} -> {local_filename}")

def main():
    create_sample_file(FILENAME)

    try:
        ftp_server = ftplib.FTP(HOSTNAME, USERNAME, PASSWORD, timeout=15)
        ftp_server.encoding = "utf-8"
        print(f"Connected to {HOSTNAME}")

        upload_file(ftp_server, FILENAME)

        print("\nRemote directory:")
        ftp_server.dir()

        downloaded_name = "downloaded_copy.txt"
        download_file(ftp_server, FILENAME, downloaded_name)

        with open(downloaded_name, "r") as f:
            print(f"\nDownloaded file content:\n{f.read()}")

    except ftplib.all_errors as e:
        print(f"FTP error: {e}")

    finally:
        ftp_server.quit()
        print("Connection closed.")

        for f in [FILENAME, "downloaded_copy.txt"]:
            if os.path.exists(f):
                os.remove(f)

if __name__ == "__main__":
    main()

Quick Reference: Key ftplib Methods

Method Description
FTP(host, user, passwd) Connect and authenticate
storbinary(cmd, file) Upload a file in binary mode
retrbinary(cmd, callback) Download a file in binary mode
dir() Print detailed directory listing
nlst() Return list of file names
cwd(path) Change working directory
pwd() Print current working directory
mkd(dirname) Create a directory
rmd(dirname) Remove a directory
delete(filename) Delete a file
rename(old, new) Rename a file or directory
quit() Close the connection gracefully

Conclusion

Python's built-in ftplib module provides everything you need to interact with FTP servers, from simple file uploads and downloads to directory management and secure FTPS connections:

  • Use storbinary() to upload files and retrbinary() to download them.
  • Always close connections with quit() in a finally block.
  • Add error handling with ftplib.all_errors and ftplib.error_perm for robust production code.
  • Use FTP_TLS instead of plain FTP when security matters.
  • Store credentials in environment variables, not in your source code.

With these techniques, you can automate file transfers, build backup systems, or integrate with any FTP-based infrastructure reliably and securely.

Navigation