Library / SDK
giampaolo/pyftpdlib avatar
giampaolo/pyftpdlib

pyftpdlib writes an FTP server in one process, and lets you drop to threads

Extremely fast and scalable Python FTP server library

1,825 stars285 forksPythonMIT

At a glance

What is it?
pyftpdlib is a pure Python library for building RFC-959 FTP servers, with FTPS, IPv6, Unicode names and MLSD/MLST support. It runs asynchronously on epoll, kqueue or select, and the demo directory shows how to step down to a thread or process model when your storage blocks.
Who is it for?
pyftpdlib is the right choice when you need FTP inside a Python application you already run, because virtual users and a virtual filesystem mean you can avoid provisioning real system accounts. It is the wrong choice for raw throughput on Linux, since the upload path depends on sendfile(2) through pysendfile and the README's own STOR figures put it behind vsftpd and proftpd.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Concurrency is epoll by default, with a thread escape hatch on purpose

The feature list states that pyftpdlib uses epoll(), kqueue() or select() to handle concurrency asynchronously, and then adds a clause that carries the design's whole philosophy: it can optionally skip to a multiple thread or process model, so you are free to block or use slow filesystems.

That is unusual for a library that otherwise sells itself on performance, and it exists because of how FTP servers are actually deployed. A single-process event loop is the right shape for network concurrency, and the wrong shape for a filesystem that stalls. Rather than pretend the problem away, the library gives you a switch, and the demo directory documents both sides of it with `demo/basic_ftpd.py`, `demo/multi_thread_ftp.py` and `demo/multi_proc_ftp.py`.

The single process claim has a second motivation. The README argues that pyftpdlib scales better than vsftpd and proftpd partly because those servers use multiple processes for concurrency while pyftpdlib uses one, which it ties to the C10K problem. Under the connect and login benchmark with 300 concurrent clients, the README reports 0.19 seconds against 9.98 secs for proftpd 1.3.4 and 20.39 secs for vsftpd 2.3.5.

The README's own benchmarks show the upload path losing

The performance tables in the README are more interesting read carefully than quoted as a claim. Against proftpd 1.3.4, STOR is 585.90 MB/sec for pyftpdlib against 600.49 MB/sec, listed as -0.02x. Against vsftpd 2.3.5, pyftpdlib reaches 585.90 MB/sec versus 611.73 MB/sec, listed as -0.04x. Upload is where the library is behind.

RETR is the other way round: 1652.72 MB/sec against 1524.05 and 1512.92 MB/sec respectively, so a few percent ahead on download.

The concurrency rows are where the gap is large, and they are also the rows that show what the benchmark actually measures. 300 concurrent clients connecting and logging in takes 0.19 seconds on pyftpdlib. STOR with 300 idle clients still runs at 585.59 MB/sec, essentially the same as the uncontended figure, which says the event loop keeps moving while other sessions wait. The QUIT row, 0.03 secs against 0.39 and an implied larger multiple for vsftpd, measures teardown rather than data movement.

The README also notes that it uses sendfile(2) via the separate pysendfile project for uploads, and that this is Linux only. That single platform dependency accounts for where the STOR gap lives, and it means the upload advantage, such as it is, does not exist elsewhere.

Authorizers and a virtual filesystem are the reason to embed it

If you only needed a working FTP daemon you would install vsftpd. The reason to reach for a library is the parts you cannot get from a daemon: support for virtual users and a virtual filesystem, and a flexible system of authorizers able to manage both virtual and real users on UNIX and on Windows.

A virtual user is an account that exists only inside the server's own tables, mapped at authentication time to a real system user or to no user at all. A virtual filesystem is a mapping from the paths the client sees to paths on disk, which is what lets each account see a different root. Both matter when the FTP service is one feature of a larger Python application with its own user model, or when creating a real Unix account per customer is not something you want to do.

On the protocol side the README lists FTPS per RFC-4217, IPv6 per RFC-2428, Unicode file names per RFC-2640, and the MLSD/MLST command family per RFC-3659, on top of the RFC-959 base it describes as the most complete implementation available for the language. Note that the extension RFCs are listed as supported, while `pyproject.toml` also carries rfc1123 and rfc2228 among its keywords; the base standard is what everything else is negotiated against.

The demo directory is the API tutorial

There is no getting-started section in the README. Installation comes from the Makefile instead:

bash
make install

The demo directory is the nearest equivalent to a tutorial, and the file names tell you what each variant changes. Open `demo/basic_ftpd.py` first. From there the shape is consistent across the rest: `basic_ftpd.py` for the plain case, `unix_ftpd.py` and `win_ftpd.py` for the two platforms, `unix_daemon.py` for daemonising, `tls_ftpd.py` for FTPS, `throttled_ftpd.py` for bandwidth limits, `anti_flood_ftpd.py` for connection rate limiting, `md5_ftpd.py` for hashed password storage, and `multi_thread_ftp.py` plus `multi_proc_ftp.py` for the blocking cases discussed above.

The components are a handler class, an authorizer instance attached to it as a class attribute, and a server bound to an address. `keycert.pem` in the same directory is the certificate the TLS demo uses, which is a test certificate and not something to deploy.

Python 3.12 changed the dependency contract

The `pyproject.toml` is where the modern packaging story sits, and it contains one detail worth catching. The runtime dependencies are conditional:

toml
dependencies = [
    "pyasynchat; python_version >= '3.12'",
    "pyasyncore; python_version >= '3.12'",
]

On Python 3.12 and later, pyftpdlib pulls in the `pyasynchat` and `pyasyncore` packages. That is a consequence of those modules being removed from the standard library, and it means the install footprint on a current interpreter is larger than it was on older ones. `requires-python` is `>=3.6`, and the classifiers list only Python 3, so the declared support range is wide even though the dependency split only matters at the top of it.

Optional extras cover the rest. The `ssl` extra is `PyOpenSSL`, and the `test` extra pulls in pytest, pytest-xdist, pytest-instafail, psutil, pyopenssl and setuptools, with pywin32 added on Windows. The `dev` extra is the full lint and release toolchain: black, ruff, pylint, coverage, pytest-cov, build, twine, check-manifest, rstcheck and toml-sort. The version is dynamic, declared as `dynamic = ["version"]`, so it is read from the package rather than pinned in the file. There are no GitHub releases; HISTORY.rst in the repository root is the changelog.

Maintenance is quiet, and the repository has not been pushed to since April

The last push to this repository was on 2026-04-23, which is six months before this article's date. The repository is not archived, and pyproject.toml classifies it as Development Status :: 5 - Production/Stable, but the commit cadence is slower than that classifier suggests and you should plan around the date rather than the badge.

For a library that many distributions vendor, that matters less than it would for an application. There is no API surface here that changes with a browser or a cloud provider, and MIT licensing means vendoring it into a product carries no copyleft obligation. The cost sits elsewhere: the README's benchmark references point at a `why` discussion in issue 203 and a separate benchmarks page, both of which are tied to the versions of proftpd and vsftpd named in the tables.

The test suite is substantial enough to be worth running before you upgrade anything. The Makefile drives it with pytest under `PYTHONWARNINGS=always PYTHONUNBUFFERED=1 PYTEST_DISABLE_PLUGIN_AUTOLOAD=1`, and it exposes a per-test form such as `make test ARGS="-v -s tests/test_functional.py::TestIPv6MixedEnvironment::test_port_v4"`. GitHub Actions runs `tests.yml`. The Makefile also installs a pre-commit hook by symlinking `scripts/internal/git_pre_commit.py` into `.git/hooks`, which is worth knowing about before your first commit in a fresh clone.

Editorial conclusion

pyftpdlib is the right choice when you need FTP inside a Python application you already run, because virtual users and a virtual filesystem mean you can avoid provisioning real system accounts. It is the wrong choice for raw throughput on Linux, since the upload path depends on sendfile(2) through pysendfile and the README's own STOR figures put it behind vsftpd and proftpd. Before choosing it, install it with `pip install -e .`, run `make test`, and check whether your disk operations block, because that single property decides whether you use the async demos or the multi_thread_ftp.py one.

Frequently asked questions

How do I install pyftpdlib?

The Makefile's install target runs `$(PYTHON) -m pip install -e .`, with `--user` added when you are outside a virtualenv. You can override the interpreter with something like `make install PYTHON=python3.7`.

Why does pyftpdlib need pyasynchat and pyasyncore?

Both are conditional dependencies applied only when `python_version >= '3.12'`. Those modules left the standard library in that release, so the packages are needed there.

Does pyftpdlib support FTPS and IPv6?

Yes. The README lists FTPS per RFC-4217 and IPv6 per RFC-2428, along with Unicode file names per RFC-2640 and MLSD/MLST per RFC-3659. The `demo/tls_ftpd.py` and `demo/keycert.pem` files show the TLS setup.

Official sources

  1. giampaolo/pyftpdlib on GitHub
  2. Issues
  3. License: MIT
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/giampaolo-pyftpdlib.svg)](https://hysenlabs.com/projects/giampaolo-pyftpdlib)

Was this useful?