Skip to content

Latest commit

Β 

History

145 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

zftpd



A zero-copy FTP daemon built for speed, correctness, and portability.
Runs anywhere POSIX runs. Saturates Gigabit. Ships a console payload too.


C11 MIT Downloads Platform


Overview Β· Performance Β· Features Β· Best Setup Β· Build Β· Running Β· Configuration Β· ZHTTP



Repository architecture

The C code is split into responsibility-based modules (ftp, http, transfer, archive, runtime, platform, and app) rather than a flat src//include/ namespace. Generated assets live under build/, not in the source tree. See docs/architecture.md for the dependency rules and layout.

Overview

zftpd is a high-performance FTP server written in C11. It was designed around a single idea: the data path should be as fast as the hardware allows, with no unnecessary work anywhere between file and socket.

In practice, this means using sendfile where the OS supports it, keeping the hot path free of allocations, and handling TCP backpressure correctly so the pipe never stalls under load. The result is an FTP daemon that saturates a full Gigabit Ethernet link in both directions β€” on Linux, macOS, or any POSIX-compliant system β€” without any client-side tuning.

The same binary model also targets PS4 and PS5 as console payloads, with on-screen notifications and an optional browser-based file explorer. This is an extension of the same codebase, not a fork β€” the POSIX foundation is identical.

Philosophy
  β”œβ”€β”€ Keep the data path fast          β†’  sendfile fast path, zero-copy where available
  β”œβ”€β”€ Handle TCP correctly             β†’  partial sends, EINTR, backpressure-aware buffers
  β”œβ”€β”€ Stay portable                    β†’  C11, POSIX, standard toolchain
  β”œβ”€β”€ Be predictable under load        β†’  no dynamic allocation per transfer, no surprises
  └── Extras are opt-in               β†’  encryption, rate limiting, web UI β€” all compile-time

⚑ Performance

zftpd saturates a full Gigabit Ethernet link β€” ~117 MB/s sustained in both directions.

This is the physical ceiling of a 1 GbE connection. It is achieved out of the box, with no kernel tuning required.

  Benchmark β€” single stream, wired 1 GbE, plain transfer

  Download  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ  117 MB/s
  Upload    β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ     108 MB/s
                                                          ────────
  Physical ceiling (1 GbE)                                125 MB/s

What makes it fast:

Technique What it does
sendfile kernel fast path Moves file data directly to the socket β€” zero userspace copies
Partial-send loop Handles short writes without stalling or corrupting the stream
EINTR-safe I/O Signal interrupts are absorbed cleanly in the hot loop
Allocation-free transfer path No malloc, no locking per packet or per transfer
Token-bucket limiter is opt-in Adds zero overhead when rate limiting is not needed

On encryption: enabling AUTH XCRYPT (ChaCha20) disables sendfile and switches to buffered I/O. Throughput becomes CPU-bound. For maximum speed on a trusted network, use plain transfers β€” that is what sendfile is there for.


✦ Features

Transfer engine

  • sendfile zero-copy fast path (Linux Β· BSD Β· macOS)
  • Fallback to buffered I/O when encrypted
  • Backpressure-aware send loop, EINTR-safe
  • Upload resume: REST + STOR
  • Append mode: APPE
  • Server-side copy: CPFR/CPTO, COPY (async background thread)
  • Cross-device move: RNTO fallback with async copy
  • Transfer rate limiting via token bucket (compile-time, opt-in)

Connection handling

  • Active mode: PORT
  • Passive mode: PASV, EPSV
  • Control and data channel timeouts
  • Session idle timeout
  • Up to FTP_MAX_SESSIONS concurrent sessions

Security

  • Path canonicalization β€” no traversal possible
  • Optional blocklist for /dev, /proc, /sys
  • Optional ChaCha20 stream cipher with PSK (AUTH XCRYPT)

Observability

  • Structured per-session logging
  • Transfer stats: bytes sent/received, files transferred
  • Per-command logging (compile-time toggle)

Platform extras

  • Linux, macOS, PS4, PS5
  • On-screen IP/port notification on PS4 and PS5
  • Rest Mode resilience β€” listener recreate + daemon instance_id (see docs/restmode.md)
  • ZHTTP web file explorer (compile-time, see ZHTTP)
Complete FTP command reference
Group Commands
Authentication USER PASS QUIT NOOP
Navigation CWD CDUP PWD
Directory listing LIST NLST MLSD MLST
File transfer RETR STOR APPE REST
File management DELE RMD MKD RNFR RNTO
Server-side copy CPFR CPTO COPY β€” async background thread
Data connection PORT PASV EPSV
Metadata SIZE MDTM STAT SYST FEAT HELP
Transfer parameters TYPE MODE STRU
Negotiation OPTS CLNT
Site extensions SITE CHMOD β€” change Unix permission bits
Encryption AUTH XCRYPT β€” ChaCha20 with PSK (opt-in)

πŸ›  Best Setup

Network β€” wired is the only choice

zftpd performs at the physical limit of your network. The bottleneck is almost always the medium, not the software. Wi-Fi is the bottleneck β€” even Wi-Fi 6 introduces retransmissions and variable latency that collapse sustained FTP throughput. Use a wired connection.

The optimal topology is a direct Ethernet cable between source and destination, eliminating every unnecessary hop:

  [Source machine]
        β”‚
  Ethernet cable
        β”‚
  [Destination machine]

If a switch is needed, any Gigabit switch works. Avoid powerline adapters and MoCA bridges β€” they introduce jitter that disrupts sustained transfers.

Assign static IPs on both ends (e.g. 192.168.100.1 / 192.168.100.2). This removes DHCP latency and keeps the setup fully deterministic.


FTP clients

Client Platform Recommendation
FileZilla Windows Β· macOS Β· Linux Best general-purpose choice. Enable parallel transfers for directory trees.
WinSCP Windows Excellent throughput and error recovery.
lftp Linux Β· macOS Best CLI option. pget -n 4 enables parallel chunked downloads.
Cyberduck macOS Β· Windows Solid for occasional transfers.
OS built-in FTP any ❌ Avoid β€” artificially capped speeds, no resume support.

Things that matter on the client side:

  • Transfer mode must be Binary (TYPE I). zftpd defaults to Binary, but a misconfigured client can override this silently β€” always verify.
  • Use passive mode (PASV). It's the default and works cleanly behind NAT and firewalls. Active mode requires the server to reach back to the client and is frequently blocked.
  • Enable parallel connections for large directory trees. FileZilla exposes this under Site Manager β†’ Transfer Settings. It will not increase single-file speed, but dramatically reduces total time for many small files.
  • Disable client-side CRC or integrity checks if offered. TCP guarantees delivery; checksumming again adds latency for no benefit.

Linux β€” optional kernel tuning

zftpd reaches full Gigabit speed with default kernel parameters. If you are feeding a very fast NVMe drive into the network and want to raise the ceiling further:

# Raise socket buffer limits β€” run as root, optional
sysctl -w net.core.rmem_max=134217728
sysctl -w net.core.wmem_max=134217728
sysctl -w net.ipv4.tcp_rmem="4096 87380 134217728"
sysctl -w net.ipv4.tcp_wmem="4096 65536 134217728"

To make these persistent, add them to /etc/sysctl.conf.

PS4 / PS5 β€” console-specific notes
  • zftpd requires a payload loader to run on console (WebKit / PPPwn / GoldHEN on PS4; etaHEN or equivalent on PS5). The daemon itself does not require a resident HEN.
  • Launch after the system is fully booted and the loader is ready. The on-screen notification will display the IP and port.
  • For maximum throughput: direct cable from console to PC, static IPs, no router in between.
  • Avoid initiating transfers while background downloads or system updates are active β€” the network stack is shared.
  • If you see "payload already loaded": a previous instance is still active. The new payload asks it to shut down on its control port (127.0.0.1:28888) and the previous instance runs its own teardown even when its threads are blocked, so it always releases the FTP, HTTP and MCP ports. Instances that predate the control port β€” or one wedged after accepting it β€” are identified by thread name and stopped, never signalling the loader's process group. If a payload still holds the console, the new instance stops instead of binding ports another instance owns.
PS5 β€” firmware-dependent transfer speed

Transfer speed to the internal storage (/data/...) varies significantly across PS5 firmware versions. This is not a zftpd limitation β€” it is caused by Sony's kernel-level I/O driver improvements across firmware updates.

  Upload speed to internal storage (wired 1 GbE, measured across multiple consoles)

  FW  4.03   β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ                                      ~20 MB/s
  FW  8.60   β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ                           ~50 MB/s
  FW  9.00   β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ              ~85 MB/s
  FW 10.00   β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ   ~113 MB/s
                                                             ────────
  Physical ceiling (1 GbE)                                   125 MB/s

Why it happens:

  • The PS5 internal storage uses the PFS (PlayStation File System) with mandatory block-level encryption. Every write() syscall goes through the kernel's crypto + NVMe pipeline.
  • Sony has incrementally improved this pipeline (write scheduling, page cache, NVMe queue depth) across firmware releases. On FW 10.00 the kernel saturates Gigabit.
  • This is not related to PFS crypto cost alone β€” if it were, speeds would be constant across all firmware. The scaling pattern proves the bottleneck is kernel I/O scheduling, not encryption.

External USB storage (/mnt/usb0/..., typically exFAT) bypasses PFS entirely and consistently reaches ~113 MB/s regardless of firmware version.

What this means for users:

  • On older firmware (< 9.00), internal storage writes are kernel-limited. No FTP server (zftpd, ftpsrv, GoldHEN) can exceed these speeds β€” the limit is in the OS.
  • For maximum speed on older firmware, transfer to external USB-C storage instead.
  • On FW 9.00+ the internal storage speed approaches Gigabit saturation.

πŸ“¦ Build & commands

Make targets (host auto-detection, best-effort toolchains):

  • make β€” build default target (Linux on Linux host, macOS on macOS host) release.
  • make release-all β€” release build per platform detected (macos, linux, ps3, ps4, ps5).
  • make debug-all β€” debug build per platform detected.
  • make release-matrix β€” release build per platform and variant ENABLE_ZHTTPD=0/1, producing ELF/BIN dove applicabile.
  • make TARGET=<platform> BUILD_TYPE=<release|debug> [ENABLE_ZHTTPD=0|1] clean all β€” build singolo.

Useful Makefile variables:

  • TARGET: linux, macos, ps3, ps4, ps5 (auto su host). Case-insensitive.
  • BUILD_TYPE: release (default), debug.
  • ENABLE_ZHTTPD: 1 abilita web UI zhttp (default 0 su console, 1 su PC). Influenza naming: es. zftpd-ps5-zhttp-v1.3.0.bin.
  • ARTIFACT_PREFIX: prefisso binari (default zftpd).
  • ffi_langs: opzionale, per build dei binding FFI.

Output naming (release):

  • ELF: build/<target>/release[/ -zhttp]/zftpd-<platform-tag>[-zhttp]-v<version>.elf
  • BIN (console): ... .bin

Execution (binaries host):

./build/macos/release/zftpd-macos-$(uname -m)-v1.3.0 -p <port> -d <root>
./build/linux/release/zftpd-linux-$(uname -m)-v1.3.0.elf -p <port> -d <root>

Supported options:

  • -p <PORT> (default 2121)
  • -d <DIR> root FTP
  • -h help

πŸ“¦ Build

Output artifacts are versioned and platform-tagged, placed in build/<target>/<build_type>/.

Requirements

Compiler C11 β€” gcc or clang
Build system make
.bin generation objcopy (binutils or llvm-objcopy); PS4: orbis-objcopy; PS5: prospero-objcopy
PS4 PS4_PAYLOAD_SDK set in environment; zhttp builds with downloads use the PacBrew PS4 portlibs, installed by tools/fetch_pacbrew_ps4.sh
PS5 PS5_PAYLOAD_SDK set in environment; zhttp builds require the PacBrew SDK bundle with libcurl + libnfs

Commands

# Targets
make TARGET=linux
make TARGET=macos
make TARGET=ps4
make TARGET=ps5

# Modifiers
make TARGET=linux BUILD_TYPE=debug
make TARGET=linux ENABLE_ZHTTPD=1     # enable web UI (off by default on POSIX)
make TARGET=ps5   ENABLE_ZHTTPD=0     # disable web UI (on by default on console)

# Tests (POSIX only)
make TARGET=linux test
make TARGET=macos test

Console web downloads

PS4 and PS5 zhttp builds use the maintained PacBrew ports of libcurl (PS5 also libnfs). The downloader supports HTTP/HTTPS/FTP/FTPS and nfs:// NAS sources; HTTPS certificate verification stays enabled and the PacBrew Mozilla CA bundle is embedded into the payload at build time. See docs/dependencies.md.

PS5 zhttp releases also accept magnet:? links. They use a native libtorrent-rasterbar engine inside the payload; no companion computer is required. For local PS5 builds, install a PS5 SDK, then run bash tools/build_ps5_libtorrent.sh "$PS5_PAYLOAD_SDK" followed by make TARGET=ps5 ENABLE_LIBTORRENT=1 clean all. The release workflow builds this dependency automatically.

PS5 extracts ZIP archives with its built-in reader. To enable other formats supported by libarchive, install PacBrew ps5-payload-libarchive in the PS5 SDK and build with make TARGET=ps5 ENABLE_LIBARCHIVE=1. ZIP files continue to use the built-in reader in that build.

PS4 builds detect the PacBrew package through the target toolchain only (orbis-pkg-config / orbis-curl-config inside PS4_PAYLOAD_SDK, or the OPENORBIS environment), never through the host's libcurl. When the package is missing the downloader is disabled at build time and the rest of zftpd is unaffected; the same behaviour is available explicitly with ENABLE_LIBCURL=0. Override PS4_PKG_CONFIG, PS4_CURL_CONFIG and PS4_CA_BUNDLE to point at a custom install.

Download queue and resume

Links pasted into the Transfers view line up in a FIFO queue: TRANSFER_MAX_CONCURRENT downloads run at a time (2 by default, override at build time with -DTRANSFER_MAX_CONCURRENT=<n>) and the rest wait, showing their position. Queued jobs can be held back or cancelled before they ever start; up to TRANSFER_MAX_ACTIVE (16) jobs are tracked.

HTTP/FTP/NFS downloads write to <name>.zftpd.part and rename it after fsync(). Magnet downloads write a <name>.zftpd.part directory containing the torrent files; the directory is renamed after every piece is verified. Download status, destination and progress are recorded atomically in /data/zftpd/transfers.state (falling back to /tmp/zftpd/). BitTorrent resume data is stored alongside that state file. On startup zftpd restores the visible list and resumes unfinished jobs from the bytes already on disk. Paused jobs stay paused, and failed jobs remain visible for retry. The Transfers view can delete a saved partial and its record, or use Start over to delete it and start the same link from zero. Removing a completed record leaves the completed file untouched.

Artifacts

Platform Output
Linux build/linux/release/zftpd-linux-<arch>-v<ver>.elf
macOS build/macos/release/zftpd-macos-<arch>-v<ver>
PS4 zftpd-ps4-v<ver>.bin Β· zftpd-ps4-v<ver>.elf
PS5 zftpd-ps5-v<ver>.bin Β· zftpd-ps5-v<ver>.elf

πŸš€ Running

Linux

./build/linux/release/zftpd-linux-<arch>-v<version>.elf [-p <port>] [-d <root>]

macOS

./build/macos/release/zftpd-macos-<arch>-v<version> [-p <port>] [-d <root>]

PS4

Send .bin to your payload loader, or .elf if the loader accepts ELF directly.
On startup: on-screen notification displays IP and port.

PS5

Send .bin or .elf depending on your loader.
On startup: FTP: <ip>:<port> notification.


βš™οΈ Configuration

All configuration is compile-time, in include/ftp/ftp_config.h.

Macro Default Notes
FTP_DEFAULT_PORT 2121 (POSIX) Β· 2120 (console) Listening port
FTP_MAX_SESSIONS β€” Maximum concurrent client sessions
FTP_SESSION_TIMEOUT β€” Idle session timeout
FTP_TRANSFER_RATE_LIMIT_BPS disabled Token-bucket average rate cap
FTP_TRANSFER_RATE_BURST_BYTES disabled Token-bucket burst allowance
FTP_LOG_COMMANDS β€” Log every received command

🌐 ZHTTP

ZHTTP is a lightweight HTTP server embedded in zftpd that serves a browser-based file explorer. It allows browsing, downloading, and optionally uploading files from any browser on the local network β€” no FTP client required.

Target Default To override
PS4 / PS5 βœ… on make TARGET=ps5 ENABLE_ZHTTPD=0
Linux / macOS ❌ off make TARGET=linux ENABLE_ZHTTPD=1

Once the daemon is running, open http://<ip>:<port>/ β€” the HTTP port mirrors the configured FTP port.

Upload support is enabled automatically alongside ZHTTP (ENABLE_WEB_UPLOAD=1).

Security: ZHTTP has no authentication beyond network access. It is designed for local-network use. Do not expose it on a public interface.

After console Rest Mode, ZHTTP auto-reconnects via /api/status (see docs/restmode.md).

Custom console toasts are available via GET /api/notify?text=Hello (useful for Home Assistant and similar local automation β€” see docs/API.md).

The System view also drives the hardware: fan threshold, network-listener restart and Blu-ray eject (POST /api/system/eject, hidden on Digital Edition consoles). The eject path comes from BD-EJ.

Downloads and previews send the raw file bytes. Console SELF containers can be requested decrypted instead, exactly like the FTP path does: enable Settings β†’ Downloads β†’ Decrypt protected files (or pass ?decrypt=1 to /api/file/get). The kernel pager is swapped in for that transfer only, so every other file β€” and every other client β€” keeps getting the on-disk bytes untouched.

Selecting many items (folders included) offers Download as ZIP: the archive is built while it streams (/api/archive/zip), so nothing is written to the console and even multi-gigabyte selections need no free space.


Acknowledgements

I would like to express our sincere thanks to:

  • hippie68 β€” for the PS4 FTP reference implementation
  • John TΓΆrnblom β€” for the PS5 payload framework
  • Drakmor β€” for the inspiration in the implementation of PFR / CPTO / COPY
  • The PlayStation homebrew community β€” for testing, feedback, and ongoing support

Contributors

Special thanks to M///Class for contributing to the project through testing and for the steady commitment shown in following its development.


Released under the MIT License

About

Zero-copy FTP/HTTP Daemon compatible with all POSIX systems

Resources

Stars

107 stars

Watchers

1 watching

Forks

Releases

Sponsor this project

Packages

Contributors

Languages