# Primitive FTPd: running an FTP or SFTP server on the phone in your pocket

> Primitive FTPd turns an Android device into an FTP and SFTP server so you can move files to and from it over a local network, with SAF based SD-card access when full filesystem permission is not granted. It suits someone who needs ad hoc file transfer to a phone, and it is not a server for the open internet.

**wolpi/prim-ftpd** — FTP server app for android

- Repository: https://github.com/wolpi/prim-ftpd
- Stars: 908 · Forks: 98
- Language: Java
- License: NOASSERTION
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/wolpi-prim-ftpd

## The problem is a phone full of files and no way in

The gap this app fills is narrow and real. You have an Android device holding a video, a set of documents or a folder of photos, and you want them on a laptop without a cloud account, a cable, or an app installed on the other machine. Primitive FTPd answers that by running an FTP server, and optionally an SFTP server, on the device itself, reachable from any client on the same network.

The project description calls it an FTP server app for Android, and the README widens it to FTP and SFTP with external SD-card access. The audience is a person, not a data centre team. There is no multi-user account management, no quota system, no web interface and no clustering, and none of that is missing by accident, since the app is designed to be opened, run for a while, and closed.

The vocabulary matters for a security conversation later. Plain FTP sends credentials and file contents unencrypted, which is fine on a home wifi and a poor idea anywhere else. The optional SFTP mode is the encrypted path, and it is opt in rather than the default, so the choice is made by whoever opens the app.

Written in Java, the project ships as an Android application with a Gradle build. The repository layout shows the app module named primitiveFTPd, a shared library module called pftpd-pojo-lib, and a vendored sshd-core-0.14.0 directory, which is the SSH implementation the SFTP mode is built on. The presence of that vendored server library is a good structural clue about where the encryption support comes from.

## Two ways in: a client protocol and a share sheet

Most of the interesting engineering in this app is not the FTP protocol. It is getting at files that modern Android does not want an app to touch.

Android has progressively closed off the filesystem, and the app offers two routes. The first is granting all files access, declared in the manifest as android.permission.MANAGE_EXTERNAL_STORAGE, which gives broad access but runs into a policy wall on Google Play. The project includes a copy of the Google policy mail in the repository and notes that the app is considered non compliant under that policy, which is why the F-Droid and GitHub release routes are the practical install paths.

The second route is the Storage Access Framework, and it is the clever part. Instead of asking for a permission on the whole filesystem, the user selects a directory through a system picker, and the app gets a scoped grant for that directory. The catch is documented in detail: you are supposed to select a directory, not the root of the SD card, because a grant on the root tends to be revoked. Revocation can happen as soon as the app is closed, or after Android kills the process in a few hours or days, and after an update the selection may have to be repeated.

There is a workaround for the root case. If you first select a directory on the SD card and then select the root, permanent access can become possible. Whether it stuck can be checked under Android Settings, in Apps, then Special access, then Directory access, where the app appearing in that list is a sign that the grant survived.

The third route is QuickShare, which the project lists as a way in when full filesystem access is not granted, and SAF gets a separate mention as well. The result is a server that can serve a specific shared folder on an SD card without ever holding a global filesystem permission.

## Getting the app: F-Droid, releases and the signing fingerprint

There are several documented install routes, and they are not equivalent. The project lists installing from F-Droid, downloading from GitHub releases, and using a development snapshot from the GitHub packages page. The F-Droid listing uses the application id org.primftpd.

Pre-built releases on GitHub are signed with a key the project publishes, and the fingerprints are given so you can check them:

```
SHA1: 51:C8:76:95:D7:D7:69:FD:65:6E:55:41:79:EE:09:84:CD:DA:83:3D
SHA256: 71:51:AE:6D:56:F1:56:E6:72:24:E7:CA:3F:9C:00:DA:6F:70:30:B6:6C:C8:44:3F:3B:B4:D3:F8:4E:EB:5C:95
```

Publishing the fingerprint is a small thing that avoids a real problem. An FTP server binary that is trusted to receive your files and, with root access enabled, to act on the device, is worth verifying. Comparing what you downloaded against these values is the check.

There is also a source build path for anyone who would rather not trust a prebuilt artifact. The repository carries build.gradle, settings.gradle, gradle.properties, the gradlew wrapper and a gradle/ directory, so a standard Gradle invocation builds the app. There is a release-build.py script and a fastlane/ directory, which points to release automation rather than a manual upload process.

## SFTP, host keys and the scp -O trap

If you turn on the encrypted mode, the app is running an SSH server, and that brings SSH client behaviour into the picture. The most common surprise is documented plainly: when accessing the server with scp, some clients, specifically certain versions of ssh shipped with Linux distributions, need the legacy protocol flag:

```
scp -O ...
```

The reason is the shift in newer OpenSSH toward SFTP for file transfer, where a client speaking only the SFTP subsystem fails against a server that expects the old scp command. If a transfer hangs or errors with no useful message, that flag is the first thing to try.

Host keys are the other SSH detail the project handles. Since version 7.4 the app shows at which time host keys had been generated, which gives you an indication of whether they have grown too old. The advice in the documentation is to generate new host keys for pftpd in that situation. A long lived host key is the one piece of state an SSH client will warn you about, and being able to see its age from the app is unusually practical.

Beyond that, the feature list adds several things that make the app usable as a real server rather than a demo. It can optionally be started on system boot. It shows a status bar notification while the server runs, and the server can be stopped from that notification. An optional wakelock prevents uploads and downloads from being aborted, which matters on a phone that would otherwise sleep mid transfer. The server can be announced on the local network so clients can find it.

Authentication options cover several postures: public key authentication for SFTP, and an optional anonymous login for the plain FTP mode. Anonymous login is a convenient default for a quick transfer and a bad default for anything else, so it is a setting you should confirm rather than inherit.

## Android will close the app, and that is the real failure mode

The most likely thing to go wrong is not a protocol bug. It is Android deciding to kill the process while a transfer is running. The documentation is blunt about it: depending on your Android version, the system keeps closing apps running in the background, and this happens even if you think you have configured it not to. The project points at dontkillmyapp.com for the battery saving settings involved.

The consequence is concrete. You start a large upload, the phone sleeps or the process is reclaimed, and the transfer dies somewhere in the middle with no server to resume it. The wakelock option exists for exactly this, and it is optional, which means the default behaviour is a server that can stop without warning. Treat the wakelock as required rather than decorative when you move big files.

The SD-card grant problem compounds it. If the server is serving a directory through the Storage Access Framework and the grant is revoked while the app is closed, the same directory simply stops being available, and the fix is a manual reselection. On some devices and Android versions the grant persists, and the project explains how to check. On others you will be re-picking the folder regularly, and that is a maintenance routine rather than a one time setup.

A related detail: a granted root of the SD card can be revoked after an update, so the steps may need repeating even when nothing changed on your side.

## Where Primitive FTPd is the wrong tool

Three cases rule it out, and the first is the one that matters most.

Do not expose this server beyond a trusted local network. The plain FTP mode is unencrypted, and the app is built to be started on demand by a person who is standing there, not hardened for exposure. There is no documentation here about fail2ban style blocking, rate limiting, TLS for the FTP mode or audit logging, and the anonymous login option in particular removes authentication entirely. If you need a server reachable from another site or from a phone on mobile data, a purpose built server on a machine you control is the right instrument.

Second, if you need a durable long running service, the design fights you. Starting on boot and a status bar notification are conveniences for a personal tool, and the background process handling is the acknowledged weak point.

Third, if you need remote access to a device that is not on your network, the answer here stops at the local network.

There is a real alternative worth naming, and the difference is architectural. A general purpose file server on a desktop, such as a Samba or SSH server, runs on a machine that has a stable filesystem, a stable network position and a power supply, so a transfer that fails can be retried, and the server can stay up for days. Primitive FTPd inverts that: the machine is a phone, the storage is a scoped and revocable grant, and the operating system is the main source of interruption. The app is better precisely when the phone is the only device that has the file, which is also when the alternatives do not apply.

For cloud or multi device workflows, none of this is a substitute for a sync client. The scope here is a single device on a single network at a single moment.

## A custom licence, a 8.x release line and translation work

The licence situation is unusual enough to flag. The repository carries a LICENSE file, and the licence field is a custom one that GitHub cannot classify, so the terms have to be read from that file rather than assumed from a standard identifier. No advice on how it applies to your use is offered here, but the practical point stands: read the LICENSE file before you redistribute or build on it, because a bespoke licence can carry terms that MIT or GPL would not.

Release cadence has been steady through the current line. The recent tags are prim-ftpd-8.2 from 2026-07-17, prim-ftpd-8.1 from 2026-05-03 and prim-ftpd-8.0 from 2026-02-22. The repository is not archived, and the last push was on 2026-08-13.

The repository also shows a split of effort between the app and the surrounding project. Translation is handled on hosted weblate in a project named pftpd, and the README invites contributions there. The fastlane/ directory, the release-build.py script and the .github/ directory point to a maintained release pipeline, and a privacy-policy.md sits at the root.

One maintenance point deserves its own note. The vendored sshd-core-0.14.0 directory means the SSH implementation is pinned inside the repository rather than pulled from a dependency manager, so it does not update on its own. Tracking what the project has bundled is worth doing if you rely on the SFTP mode, since the SSH layer is exactly the kind of code where an unpatched upstream issue matters, and there is no configuration in the documentation for that side.

## Conclusion

Primitive FTPd is a sensible pick for someone who needs to move files to or from an Android phone over a local network without installing anything on the other machine, and for the specific case where full filesystem access is not granted and SAF or QuickShare is the only route. It is not a general purpose server, and it is not a fit for a service exposed beyond a trusted network. Before adopting, check the custom LICENSE file in the repository, confirm that the permission model on your Android version actually grants the directory you want to serve, and verify the release signing fingerprint against the one the project publishes before installing a build.

## FAQ

### How do I install Primitive FTPd on Android?

The project lists three routes: install it from F-Droid, download it from the GitHub releases, or take a development snapshot from the GitHub packages page. The F-Droid listing uses the application id org.primftpd, and pre-built releases carry a published signing fingerprint you can verify before installing.

### Why does scp fail to connect to the Primitive FTPd SFTP server?

Some versions of ssh shipped with Linux distributions need the legacy transfer option, given as scp -O, because newer clients default to the SFTP subsystem. The project also notes that you should probably generate new host keys for pftpd in that case, and since version 7.4 the app shows when the host keys were generated so you can judge their age.

### Can Primitive FTPd write to an external SD card without all files access?

Yes, through the Android Storage Access Framework, but you must select a directory rather than the root of the SD card. A grant on the root tends to be revoked, possibly as soon as the app is closed, and selecting a subdirectory first and the root second can make the access permanent, which you can verify under Android Settings, Apps, Special access, Directory access.

### What licence is Primitive FTPd released under?

The repository carries a LICENSE file and the licence field is a custom one that GitHub cannot classify, so it is not a standard SPDX identifier. Read the LICENSE file directly to see the terms, since a bespoke licence may include conditions that a standard permissive licence would not.

### Does Primitive FTPd support encrypted transfers?

Yes, SFTP support is optional and can be enabled, including public key authentication for SFTP. Plain FTP mode is unencrypted, and anonymous login is offered as an option, so both the protocol and the authentication mode are settings to check before you open the server to a network.

## Sources

- [Issues](https://github.com/wolpi/prim-ftpd/issues)
- [README](https://github.com/wolpi/prim-ftpd/blob/master/README.md)
- [Releases](https://github.com/wolpi/prim-ftpd/releases)
- [wolpi/prim-ftpd on GitHub](https://github.com/wolpi/prim-ftpd)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/wolpi-prim-ftpd
