Install & Compatibility
Where this runs
tested against v2.2.0 · pip install
no network on importno background threads
Install × environment matrix
Each cell = how many times install + import succeeded across repeated harness runs. Partial = flaky.
glibc = Debian/Ubuntu slim · musl = Alpine Linux
muslpy 3.10–3.920 runs
installs and imports cleanly · install 0.0s · import 0.017s · 18.4MB
glibcpy 3.10–3.920 runs
installs and imports cleanly · install 3.3s · import 0.013s · 19MB
17MB installed
● package 17MB
Code
Verified usage
Verified import paths — ran on the pinned version, not inferred.
DummyAuthorizer
✓ from pyftpdlib.authorizers import DummyAuthorizer
FTPHandler
✓ from pyftpdlib.handlers import FTPHandler
FTPServer
✓ from pyftpdlib.servers import FTPServer
TLS_FTPHandler
✓ from pyftpdlib.handlers import TLS_FTPHandler
Used for FTPS (FTP over TLS/SSL) servers, requires PyOpenSSL.
ThreadedFTPServer
✓ from pyftpdlib.servers import ThreadedFTPServer
For a multi-threaded concurrency model, if the default async model is not suitable.
This quickstart sets up a basic FTP server with a virtual user (configurable via environment variables FTP_USER, FTP_PASS, FTP_HOME) and an anonymous read-only user. It listens on port 2121, sets connection limits, and configures basic logging. Remember to set `FTP_HOME` to a directory you wish to share, and `FTP_USER`/`FTP_PASS` for authenticated access.
import os
import logging
from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer
def main():
# Setup a dummy authorizer for managing virtual users
authorizer = DummyAuthorizer()
# Add a user with read/write permissions
ftp_user = os.environ.get('FTP_USER', 'user')
ftp_pass = os.environ.get('FTP_PASS', '12345')
home_dir = os.environ.get('FTP_HOME', os.getcwd())
authorizer.add_user(ftp_user, ftp_pass, home_dir, perm='elradfmwMT')
# Add an anonymous user with read-only permissions
authorizer.add_anonymous(home_dir)
# Instantiate FTP handler class
handler = FTPHandler
handler.authorizer = authorizer
# Define a customized banner
handler.banner = "pyftpdlib based FTP server ready."
# Specify a masquerade address and the range of ports for passive connections
# Uncomment and configure if behind a NAT
# handler.masquerade_address = '151.25.42.11'
# handler.passive_ports = range(60000, 65535)
# Instantiate FTP server class and listen on all interfaces, port 2121
address = ('', 2121)
server = FTPServer(address, handler)
# Set limits for connections
server.max_cons = 256
server.max_cons_per_ip = 5
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(levelname)s:%(name)s:%(message)s')
# Start ftp server
print(f"Starting FTP server on {address[0] or '0.0.0.0'}:{address[1]} with user '{ftp_user}' and home '{home_dir}'")
server.serve_forever()
if __name__ == "__main__":
main()
pyftpdlib --version
Debug
Known issues
breakingPython 2.7 support has been removed in pyftpdlib 2.0.0. Users requiring Python 2.7 compatibility must install version 1.5.10.fixUpgrade to Python 3.6+ or downgrade pyftpdlib to version 1.5.10: `pip install pyftpdlib==1.5.10`.
affects: >=2.0.0
gotchaAs an asynchronous library, pyftpdlib will block the entire server if any long-running, blocking operations (e.g., `time.sleep()`, heavy database queries, slow disk I/O) are executed in the main event loop.fixDelegate blocking tasks to separate threads or processes using Python's `threading` or `multiprocessing` modules, or ensure all I/O operations are non-blocking.
affects: All
gotchaWhen the FTP server is behind a Network Address Translator (NAT), clients may fail to establish passive data connections unless `FTPHandler.masquerade_address` and `handler.passive_ports` are explicitly configured.fixSet `handler.masquerade_address` to the server's public IP and `handler.passive_ports` to a range of TCP ports for data transfers. Ensure these ports are forwarded by your NAT device.
affects: All
breakingStarting with version 2.0.0, the default SSL/TLS method for `TLS_FTPHandler` was changed from `SSLv23_METHOD` to `TLS_SERVER_METHOD`, disabling older and insecure SSLv2/SSLv3 protocols. This may break compatibility with very old FTP clients.fixEnsure clients support TLSv1.0 or newer. If legacy compatibility is critical, review `PyOpenSSL` configuration or consider older `pyftpdlib` versions (not recommended for security).
affects: >=2.0.0
gotchaOn Python 3.14+, `MultiprocessFTPServer` may be broken on POSIX systems (excluding macOS) due to a change in the default `multiprocessing` method from 'fork' to 'forkserver'.fixManually set the `multiprocessing` start method to 'fork' (e.g., `multiprocessing.set_start_method('fork', force=True)`) if using `MultiprocessFTPServer` on affected platforms. affects: Python >=3.14, pyftpdlib >=2.0.0
Errors
Common errors & fixes
ImportError: cannot import name ftpserver
The `FTPServer` class is imported directly from the top-level `pyftpdlib` package or using a deprecated name, instead of its proper location within the `pyftpdlib.servers` submodule.
fixChange the import statement to `from pyftpdlib.servers import FTPServer`.
OSError: [Errno 98] Address already in use
The specified port (e.g., 21 or 2121) for the FTP server is already occupied by another process, or a previous server instance did not shut down cleanly, leaving the port in a `TIME_WAIT` state.
fixBefore binding the socket, set the `socket.SO_REUSEADDR` option, choose a different port, or ensure no other process is using the desired port (e.g., using `netstat` to identify and terminate the occupying process). The `socket.SO_REUSEADDR` option can be set on the underlying socket of the FTPServer.
530 Login incorrect
The username or password provided by the FTP client does not match the credentials configured in the `DummyAuthorizer` (or a custom authorizer), or the authorizer is not properly assigned to the `FTPHandler`.
fixDouble-check the username, password, and permissions configured via `authorizer.add_user()` or `authorizer.add_anonymous()`, and ensure `handler.authorizer = authorizer` is set correctly.
Permission denied (when starting server on port < 1024)
On Unix-like operating systems, binding to network ports below 1024 (e.g., the standard FTP port 21) requires root privileges. Running the `pyftpdlib` server as an unprivileged user will result in this error.
fixRun the Python script with root privileges (e.g., `sudo python your_ftp_server.py`) or configure the FTP server to listen on a port number greater than 1024 (e.g., 2121).
421 Active data channel timeout / Passive data channel timed out (for external connections)
The FTP server is likely behind a Network Address Translation (NAT) device or firewall, causing it to advertise its private (internal) IP address for passive data connections, or the range of passive ports is not open/forwarded in the firewall/router.
fixSet `FTPHandler.masquerade_address` to the server's public IP address and configure `FTPHandler.passive_ports` with a specific range of TCP ports, ensuring these ports are open and forwarded from the router/firewall to the server's internal IP.
Upgrade
Version history
2.2.0latest on PyPI · released Feb 7, 2026
Audit
Dependencies
PyOpenSSLoptionalOptional, for FTPS (FTP over TLS/SSL) support.
psutiloptionalOptional, for tracking FTP server memory usage.