How to Download and Upload Files to an FTP Server in Python
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
# FTP server credentials
HOSTNAME = "ftp.example.com"
USERNAME = "user@example.com"
PASSWORD = "password"
# Connect and authenticate
ftp_server = ftplib.FTP(HOSTNAME, USERNAME, PASSWORD)
# Force UTF-8 encoding for file names
ftp_server.encoding = "utf-8"
# Print the welcome message from the server
print(ftp_server.getwelcome())
Sample output:
220 Welcome to the Test FTP Server
- Never hardcode real credentials in your source code. Use environment variables or a
.envfile instead.
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"
# Connect to the server
ftp_server = ftplib.FTP(HOSTNAME, USERNAME, PASSWORD)
ftp_server.encoding = "utf-8"
# File to upload
filename = "example.txt"
# Open the file in binary read mode and upload
with open(filename, "rb") as file:
ftp_server.storbinary(f"STOR {filename}", file)
print(f"'{filename}' uploaded successfully.")
# List remote directory to confirm
print("\nRemote directory contents:")
ftp_server.dir()
# Close the connection
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
- The file is opened in binary read mode (
"rb"). storbinary(command, file)reads the file object and sends its contents to the server.- The
STORcommand tells the FTP server to store the incoming data as a file with the given name. 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"
# Connect to the server
ftp_server = ftplib.FTP(HOSTNAME, USERNAME, PASSWORD)
ftp_server.encoding = "utf-8"
# File to download
filename = "example.txt"
# Open a local file in binary write mode and download
with open(filename, "wb") as file:
ftp_server.retrbinary(f"RETR {filename}", file.write)
print(f"'{filename}' downloaded successfully.")
# Display the file contents to verify
with open(filename, "r") as file:
print(f"\nFile content:\n{file.read()}")
# Close the connection
ftp_server.quit()
Sample output:
'example.txt' downloaded successfully.
File content:
Hello from Tutorial Reference!
How It Works
- A local file is opened in binary write mode (
"wb"). retrbinary(command, callback)retrieves the remote file in chunks and passes each chunk to the callback (file.write).- 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"
# Detailed directory listing
print("Detailed listing:")
ftp_server.dir()
print()
# Simple list of file names
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 |
Navigating Directories on the Server
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 current directory
print(f"Current directory: {ftp_server.pwd()}")
# Create a new directory
ftp_server.mkd("test_folder")
# Change into the new directory
ftp_server.cwd("test_folder")
print(f"Changed to: {ftp_server.pwd()}")
# Go back to the parent directory
ftp_server.cwd("..")
# Remove the directory (must be empty)
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:
# Connect to the FTP server
ftp_server = ftplib.FTP(HOSTNAME, USERNAME, PASSWORD, timeout=10)
ftp_server.encoding = "utf-8"
print("Connected successfully.")
# Attempt to download a file
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
ftplib.error_perm is raised for permanent errors like invalid credentials (530), file not found (550), or permission denied. ftplib.all_errors catches all FTP-related exceptions.
Common Mistake: Forgetting to Close the Connection
Leaving an FTP connection open wastes server resources and may cause your program to hang:
# ❌ Wrong: connection is never closed
ftp_server = ftplib.FTP(HOSTNAME, USERNAME, PASSWORD)
ftp_server.retrbinary(f"RETR {filename}", open(filename, "wb").write)
# Program ends without calling quit()
The correct approach is to always close the connection, ideally using a try/finally block or a context manager:
# ✅ Correct: connection is always closed
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"
# Connect using FTP over TLS
ftp_server = ftplib.FTP_TLS(HOSTNAME, USERNAME, PASSWORD)
# Secure the data connection as well
ftp_server.prot_p()
ftp_server.encoding = "utf-8"
# Now upload/download as usual
ftp_server.dir()
ftp_server.quit()
Never use plain FTP for transferring sensitive data over the internet. Always prefer FTPS (FTP_TLS) or SFTP (which uses SSH and is available via the third-party paramiko library).
Complete Example: Upload and Download
Here is a complete, self-contained script that demonstrates both uploading and downloading a file:
import ftplib
import os
# Configuration
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():
# Step 1: Create a sample file
create_sample_file(FILENAME)
try:
# Step 2: Connect to the FTP server
ftp_server = ftplib.FTP(HOSTNAME, USERNAME, PASSWORD, timeout=15)
ftp_server.encoding = "utf-8"
print(f"Connected to {HOSTNAME}")
# Step 3: Upload the file
upload_file(ftp_server, FILENAME)
# Step 4: List remote files
print("\nRemote directory:")
ftp_server.dir()
# Step 5: Download the file with a different local name
downloaded_name = "downloaded_copy.txt"
download_file(ftp_server, FILENAME, downloaded_name)
# Step 6: Verify the download
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.")
# Clean up local files
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 andretrbinary()to download them. - Always close connections with
quit()in afinallyblock. - Add error handling with
ftplib.all_errorsandftplib.error_permfor robust production code. - Use
FTP_TLSinstead of plainFTPwhen 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.