ftpserver: an FTP front end that mounts cloud storage as if it were a disk
Golang based autonomous FTP server with SFTP, S3, Dropbox, and Google Drive connectors.
At a glance
- What is it?
- fclairamb/ftpserver is a Go FTP server that treats S3, Dropbox, Google Drive, SFTP, email and Telegram as filesystems through the afero interface, so an old FTP client or a scanner can read and write cloud storage without knowing it is not a disk. It ships as a single binary, an Alpine container and a JSON config of users, backends and their parameters.
- Who is it for?
- ftpserver fits a lab with legacy FTP clients or hardware that can only speak FTP, and a team that wants to publish or collect files in cloud storage without rewriting the client side. It does not fit a production deployment that needs a modern protocol instead of FTP.
- 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 1 day ago.
- What is it written in?
- Mainly Go, 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
A gateway between FTP clients and cloud filesystems
The FTP server is described as a gateway between old-school FTP devices and modern cloud based file systems, using the afero Fs interface, and it acts as a reference implementation of the ftpserverlib library.
That afero choice is the design in one sentence. afero is a filesystem abstraction with the same interface for local disk, in-memory filesystems, S3, Google Cloud Storage, SFTP and more. Because the server addresses every backend through that one interface, adding a storage provider means writing an afero implementation rather than teaching an FTP server about cloud APIs, and the server code itself does not change.
The supported backends are local disk, S3 through afero-s3, Dropbox through afero-dropbox, Google Drive through afero-gdrive, SFTP through afero's own sftpfs, email through the go-mail library, and Telegram through telebot. Each of those also has common parameters that switch them to read-only, enable login access, or use a temporary directory file.
The email and Telegram backends are the ones worth understanding, because they are not storage. They are transport channels expressed as filesystems, so a client can send a file and it arrives as an attachment or a message, and the directory listing shows whatever the connector chooses to expose. The Telegram one is contributed by a different author from the email one, which is a good sign about how the project is structured for outside contributions.
The project is MIT licensed, the primary language is Go, and the last push was on 2026-10-22, with releases v0.16.3 in August 2026, v0.16.2 in July and v0.16.1 just before it.
What the FTP protocol layer actually supports
The protocol features come from ftpserverlib itself rather than from the gateway layer, and the list is worth reading because it shows how complete the FTP implementation is.
Uploading and downloading files are the basics, along with directory listing using both LIST and MLST, and file and directory deletion and renaming. TLS support covers AUTH and PROT. Transfer resume works through REST, and there is a complete driver for all of those features.
Both socket modes are implemented, passive connections through the EPSV and PASV commands and active connections through PORT. The library also advertises a small memory footprint, and the server itself relies only on the standard library apart from go-kit for logging and afero for generic filesystem handling.
The supported extensions list is longer and each one is an RFC number, which is a good sign of correctness. AUTH covers control session protection, AUTH TLS the TLS session itself, and PROT transfer protection. EPRT and EPSV bring IPv6 support. MDTM exposes file modification time, SIZE exposes a file's size, REST restarts an interrupted transfer, and MLST and MLSD give machine processing listings.
That last pair is what makes the server usable by a script rather than only by an interactive client, and REST is what makes a large download resumable over a flaky link, which matters when the remote end is a cloud provider rather than a local disk.
Running it, and the four ways to do that
There are four documented ways to run the server, and the range of them tells you how the project expects to be used.
The first is to fetch a binary for your platform from the latest release and run it. The archives are named ftpserver-<version>-<os>-<arch> and are provided for Linux, macOS and Windows on amd64 and arm64, plus armv6 and armv7 for Linux, which the documentation describes as Raspberry Pi and friends.
The second is to build and run it locally, which is a go install and a run:
go install github.com/fclairamb/ftpserver@main
ftpserver &The third is Docker, with the container described as 31MB and based on Alpine. The run command creates a directory for the files and starts the sample server with a port range and two volumes mounted:
# Creating a directory
mkdir -p files
# Starting the sample FTP server
docker run --rm -d -p 2121-2130:2121-2130 -v $(pwd)/files:/tmp -v $(pwd):/app fclairamb/ftpserverThe fourth is Docker Compose, with a service that publishes the same port range and mounts the same two volumes:
# docker-compose.yml
version: '3.3'
services:
ftpserver:
ports:
- '2121-2130:2121-2130'
volumes:
- ./files:/tmp
- .:/app
image: fclairamb/ftpserverThe repository also ships both a Dockerfile and a Dockerfile.alpine, plus a local-docker-test.sh script and a build.go with a build.go.tpl template for generating them.
Testing the server with curl
The README gives a quick end to end test that is worth copying verbatim into your own checklist, because it exercises upload, download and comparison in four commands.
# Download some file
[ -f kitty.jpg ] || (curl -o kitty.jpg.tmp https://placekitten.com/2048/2048 && mv kitty.jpg.tmp kitty.jpg)
# Upload it to the server
curl -v -T kitty.jpg ftp://test:test@localhost:2121/
# Download it back
curl ftp://test:test@localhost:2121/kitty.jpg -o kitty2.jpg
# Compare it
diff kitty.jpg kitty2.jpgThe download step writes to a temporary file and renames it, which is a small habit worth keeping: a half-written file will not be mistaken for a complete one. The final diff is the real assertion, since it proves the round trip preserved the bytes rather than just that the transfer did not error.
The test:test credentials come from the sample config, and the port is 2121, which is inside the 2121 to 2130 range the server publishes. That range is the passive data connection range, and the fact that the whole range has to be reachable from the client is a network detail that catches people out when the server sits behind NAT or in a container with a restricted port map.
The config file is where the credentials live
If you do not create an ftpserver.json file, one is created for you, and the sample config shows the whole shape of it.
The top level holds the version, the passive transfer port range with a start and an end, and a tls section with a server cert and key. The accesses array is where the interesting part lives. Each access has a user, a pass, an fs naming the backend, and a params object for that backend's specifics.
The local disk access is the simplest, with an fs of os and a basePath. The Dropbox access uses fs dropbox and a token parameter. The Google Drive one uses fs gdrive with a google_client_id, a google_client_secret and a base_path. The S3 one carries an endpoint, a region, a bucket, an access_key_id, a secret_access_key, a disable_ssl flag and a path_style flag. The SFTP one carries a username, a password, a hostname with a port and a known_hosts path. The Telegram one carries a Token, a ChatID and a shared flag at the access level rather than in params.
Two things about this configuration are worth flagging. The credentials sit in one file in plain form, and the repository carries a config-schema.json, which suggests the file is validated against a schema. And the SFTP backend is configured with a known_hosts path, which is the mechanism by which host key verification happens, so leaving that parameter out is the kind of small decision with security consequences.
The repository layout shows the separation of concerns: an fs directory holding the backend implementations, a server directory, a config directory, the main entry point, and a build.go with its template. There is also a CHANGELOG, a CODE_OF_CONDUCT, a CONTRIBUTING guide and a CLAUDE.md.
Where ftpserver is the wrong choice
The first limitation is the protocol itself, and no amount of implementation quality changes it. FTP sends credentials in the clear unless TLS is enabled, it has no modern authentication beyond the password, and it was designed for a network of trusted hosts. Exposing an FTP server to the internet in 2026 is a decision with consequences regardless of how good the code is, and the TLS and per-user controls in the config are mitigations rather than a fix.
The second is that the cloud backends are only as good as their connectors. A gateway that presents Dropbox as a filesystem gives you whatever the Dropbox API exposes, which is not the same thing as a POSIX filesystem. Rename semantics, partial uploads, and the behaviour of a directory listing are all defined by the provider. The temporary directory option and the read-only switch exist precisely because a connector's behaviour is not always filesystem-like, and a team that needs POSIX guarantees should not assume it gets them.
The third is the narrowness of the use case. The reason this exists is a device or a client that can only speak FTP. If you control both ends, S3's own SDK, or a WebDAV or SFTP server, is a better fit, and if you control neither end, the answer is a hosted service rather than a self-hosted gateway. A team adopting this has usually hit the legacy-hardware case, and if that is not your situation, the server is a solution to a problem you do not have.
The comparison with running the cloud storage directly is the honest one. S3 already speaks HTTP with a mature client ecosystem, and Google Drive and Dropbox have their own sync tools. The value of ftpserver is that it makes all of them look like the same old disk to something that only knows how to do FTP, which is a real and common need, and a narrow one.
Editorial conclusion
ftpserver fits a lab with legacy FTP clients or hardware that can only speak FTP, and a team that wants to publish or collect files in cloud storage without rewriting the client side. It does not fit a production deployment that needs a modern protocol instead of FTP. Before deploying it, read the config schema because the credential fields are per backend, decide whether the passive port range and the TLS certificate configuration match your network, and treat the read-only, login access and temporary directory options per user as the controls that decide what an exposed FTP account can actually do.
Frequently asked questions
What does fclairamb/ftpserver do?
It is a Go FTP server that acts as a gateway between old-school FTP clients and modern cloud storage, using the afero filesystem interface. Backends include local disk, S3, Dropbox, Google Drive, SFTP, email through go-mail and Telegram through telebot, and each access in the config can be set read-only, restricted from logging in, or given a temporary directory.
How do I run the ftpserver locally?
Install and run it with `go install github.com/fclairamb/ftpserver@main` and then `ftpserver &`. Prebuilt release archives are also published as ftpserver-<version>-<os>-<arch> for Linux, macOS and Windows on amd64 and arm64, plus armv6 and armv7 for Linux.
How do I test that the ftpserver is working?
The README shows a curl round trip: download a file, upload it with `curl -v -T kitty.jpg ftp://test:test@localhost:2121/`, download it back with `curl ftp://test:test@localhost:2121/kitty.jpg -o kitty2.jpg`, and then compare the two with `diff kitty.jpg kitty2.jpg`.
Which FTP features does the server support?
The protocol layer comes from ftpserverlib and covers upload and download, directory listing with LIST and MLST, deletion and renaming, TLS with AUTH and PROT, transfer resume with REST, passive connections through EPSV and PASV, active connections through PORT, and extensions including EPRT, MDTM, SIZE, MLST and MLSD.
What is in the ftpserver configuration file?
The config holds a version, the passive transfer port range with a start and end, a tls section with a server certificate and key, and an accesses array where each entry has a user, a pass, an fs naming the backend and a params object with that backend's details, such as a token for Dropbox, Google client credentials for Drive, an endpoint and bucket for S3, or a username and known_hosts path for SFTP.
Official sources
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.
[](https://hysenlabs.com/projects/fclairamb-ftpserver)Was this useful?