A fast, lightweight, self-hosted file browser built in Rust.
Most self-hosted file browsers are written in Go or PHP. RustyFile takes a different approach -- Rust's zero-cost abstractions, no garbage collector, and a single static binary that starts in milliseconds and idles at ~15 MB.
| RustyFile | FileBrowser | Filestash | Nextcloud | |
|---|---|---|---|---|
| Language | Rust | Go | Go | PHP |
| Memory baseline | ~15 MB | Not documented | 128-512 MB | 512 MB+ |
| Startup | Sub-50 ms | Seconds | Seconds | Seconds (PHP init) |
| Single binary | Yes | Yes | Yes | No (requires LAMP stack) |
| License | MIT | Apache 2.0 | AGPL-3.0 | AGPL-3.0 |
| Storage backends | Local filesystem | Local filesystem | 20+ (S3, FTP, WebDAV...) | Local + integrations |
| Project status | Active | Maintenance-only | Active | Active |
Where RustyFile wins: startup speed, memory footprint, and deployment simplicity. Drop a single binary on a Raspberry Pi or NAS and go.
Where others win: Filestash supports 20+ remote storage backends. Nextcloud is a full collaboration suite (calendar, contacts, document editing). If you need those, RustyFile isn't the right tool.
- File browsing -- navigate directories, view metadata, sort by name/size/date/type
- Resumable uploads -- TUS 1.0.0 protocol with drag-and-drop, progress tracking, and automatic cleanup of expired uploads
- Filename search -- SQLite-powered indexed filename search with filters (type, size, date range, path scope)
- Video streaming -- HLS adaptive streaming with on-the-fly transcoding, plus direct MP4/WebM via HTTP Range requests
- Thumbnail generation -- on-demand JPEG thumbnails for images (PNG, JPEG, WebP) with disk caching
- In-browser text editing -- edit code and config files, saved atomically
- Authentication -- JWT (HS256) + Argon2id password hashing, HttpOnly cookie and Bearer token support
- File watching -- filesystem changes at the root level automatically update the search index and invalidate directory caches
- Rate limiting -- per-IP login throttling (10 attempts / 15 minutes) via token bucket
- Caching -- ETag conditional requests (304 Not Modified), LRU directory listing cache with TTL, immutable thumbnail cache
- Security headers -- CSP, X-Content-Type-Options, X-Frame-Options, restrictive CORS
- Zero-config onboarding -- first visit creates the admin account, no config files needed
curl -fsSL https://github.com/zaakirio/RustyFile/releases/latest/download/rustyfile-linux-amd64.tar.gz | tar xz
./rustyfile --root ./my-files --port 8080
# Visit http://localhost:8080 to create your admin accountdocker run -d \
-p 8080:80 \
-v /path/to/your/files:/data \
-v rustyfile_data:/config \
rustyfile:latestservices:
rustyfile:
image: rustyfile:latest
ports:
- "8080:80"
volumes:
- ./files:/data
- rustyfile_data:/config
restart: unless-stopped
volumes:
rustyfile_data:git clone https://github.com/zaakirio/RustyFile.git
cd RustyFile
make build
./target/release/rustyfile --root ./test-data --port 8080RustyFile uses layered configuration (highest priority wins):
CLI flags > Environment variables (RUSTYFILE_*) > config.toml > Defaults
| Option | Env Var | Default | Description |
|---|---|---|---|
--host |
RUSTYFILE_HOST |
0.0.0.0 |
Listen address |
--port |
RUSTYFILE_PORT |
8080 |
Listen port |
--root |
RUSTYFILE_ROOT |
./data |
Directory to serve |
--data-dir |
RUSTYFILE_DATA_DIR |
./rustyfile-data |
Database & state directory |
--cache-dir |
RUSTYFILE_CACHE_DIR |
./rustyfile-data/cache |
Cache directory (thumbnails, TUS temp files, HLS segments) |
--log-level |
RUSTYFILE_LOG_LEVEL |
info |
Log level (trace/debug/info/warn/error) |
--log-format |
RUSTYFILE_LOG_FORMAT |
pretty |
Log format (json or pretty) |
--jwt-expiry-hours |
RUSTYFILE_JWT_EXPIRY_HOURS |
2 |
JWT token lifetime |
--min-password-length |
RUSTYFILE_MIN_PASSWORD_LENGTH |
10 |
Minimum password length |
--max-password-length |
RUSTYFILE_MAX_PASSWORD_LENGTH |
128 |
Maximum password length (Argon2 DoS protection) |
--max-upload-bytes |
RUSTYFILE_MAX_UPLOAD_BYTES |
52428800 |
Max request body size (50 MB) |
--max-listing-items |
RUSTYFILE_MAX_LISTING_ITEMS |
10000 |
Max items in directory listing |
--setup-timeout-minutes |
RUSTYFILE_SETUP_TIMEOUT_MINUTES |
5 |
Setup wizard timeout |
--tus-expiry-hours |
RUSTYFILE_TUS_EXPIRY_HOURS |
24 |
Hours before incomplete uploads expire |
--cors-origins |
RUSTYFILE_CORS_ORIGINS |
same-origin |
Allowed CORS origins (comma-separated) |
--trusted-proxies |
RUSTYFILE_TRUSTED_PROXIES |
127.0.0.1 |
Trusted proxy IPs for X-Forwarded-For (comma-separated) |
--secure-cookie |
RUSTYFILE_SECURE_COOKIE |
true |
Set Secure flag on auth cookies (disable for HTTP dev) |
--blocked-upload-extensions |
RUSTYFILE_BLOCKED_UPLOAD_EXTENSIONS |
.php,.phtml,... |
Blocked upload extensions (comma-separated) |
--api-rate-limit |
RUSTYFILE_API_RATE_LIMIT |
60 |
Max requests per IP per minute for expensive endpoints (search, thumbs, HLS) |
See config.toml.example for a full example.
| Method | Path | Description |
|---|---|---|
GET |
/api/health |
Health check |
GET |
/api/setup/status |
Setup required? { setup_required: bool } |
POST |
/api/setup/admin |
Create initial admin (first run only) |
POST |
/api/auth/login |
Login with username/password |
POST |
/api/auth/logout |
Logout (clears HttpOnly cookie) |
POST |
/api/auth/refresh |
Refresh JWT token |
| Method | Path | Description |
|---|---|---|
GET |
/api/fs |
List root directory |
GET |
/api/fs/{path} |
List directory or get file info |
GET |
/api/fs/{path}?content=true |
Get file info with text content |
PUT |
/api/fs/{path} |
Save file content |
POST |
/api/fs/{path} |
Create directory ({"type":"directory"}) |
DELETE |
/api/fs/{path} |
Delete file or directory |
PATCH |
/api/fs/{path} |
Rename/move ({"destination":"new/path"}) |
GET |
/api/fs/download/{path} |
Download file (supports Range requests) |
GET |
/api/fs/download/{path}?inline=true |
View file inline in browser |
| Method | Path | Description |
|---|---|---|
GET |
/api/fs/search?q=... |
Filename search |
Search supports these query parameters: q (query), type (file/dir/image/video/audio/document), min_size, max_size, after, before, path (scope to directory), limit (default 50), offset.
| Method | Path | Description |
|---|---|---|
OPTIONS |
/api/tus/ |
TUS capability discovery |
POST |
/api/tus/ |
Create upload (Upload-Length + Upload-Metadata headers) |
HEAD |
/api/tus/{id} |
Query upload offset |
PATCH |
/api/tus/{id} |
Append chunk (Content-Type: application/offset+octet-stream) |
DELETE |
/api/tus/{id} |
Cancel and delete upload |
Upload metadata is base64-encoded in the Upload-Metadata header with filename and destination keys.
| Method | Path | Description |
|---|---|---|
GET |
/api/thumbs/{path} |
Get image thumbnail (JPEG, max 300px, cached) |
| Method | Path | Description |
|---|---|---|
GET |
/api/hls/playlist/{path} |
Get M3U8 playlist for video |
GET |
/api/hls/segment/{key}/{index} |
Get individual .ts segment |
The download endpoint supports HTTP Range requests for seeking:
# Seek to byte offset
curl -H "Authorization: Bearer $TOKEN" -H "Range: bytes=1000000-" \
http://localhost:8080/api/fs/download/video.mp4For adaptive streaming, the HLS endpoints transcode video into segments on demand (requires ffmpeg on the host).
1. POST /api/setup/admin -- First run: create admin, get JWT
2. POST /api/auth/login -- Subsequent: login, get JWT
3. Authorization: Bearer <token> (or HttpOnly cookie)
4. POST /api/auth/refresh -- Renew before expiry
All errors return { "error": "message" }.
| Status | When |
|---|---|
| 400 | Invalid input |
| 401 | Missing or invalid token |
| 403 | Insufficient permissions |
| 404 | Resource doesn't exist |
| 409 | Resource already exists |
| 410 | Setup timeout elapsed |
| 500 | Server error |
src/
main.rs -- config, logging, DB, filesystem watcher, server startup
lib.rs -- library root
config.rs -- layered config (Figment + Clap), 19 options
error.rs -- AppError -> HTTP response mapping
state.rs -- shared state (DB pool, config, JWT secret, rate limiter, caches)
frontend.rs -- SPA static file serving (rust-embed)
api/
mod.rs -- router assembly, middleware stack, CORS
health.rs -- health check
setup.rs -- first-run onboarding
auth.rs -- JWT login/logout/refresh with rate limiting
files.rs -- file CRUD (browse, edit, create, delete, rename)
download.rs -- streaming downloads + Range requests + ETag
search.rs -- filename search endpoint
tus.rs -- TUS 1.0.0 resumable upload protocol
hls.rs -- HLS video streaming (playlist + segments)
thumbs.rs -- thumbnail generation endpoint
middleware/
mod.rs -- middleware registry
auth.rs -- JWT validation middleware
db/
mod.rs -- SQLite pool, migrations (3 versions), interact() helper
user_repo.rs -- user CRUD queries
services/
mod.rs -- service registry
file_ops.rs -- path resolution, dir listing, atomic writes
cache.rs -- Moka LRU directory listing cache with TTL
search_index.rs -- SQLite indexed filename search with incremental updates
thumbnail.rs -- image thumbnail generation with disk caching
transcoder.rs -- FFmpeg-based HLS video transcoding
frontend/ -- React 19 + TypeScript + Vite + Tailwind CSS SPA
| Component | Choice | Why |
|---|---|---|
| Web framework | Axum 0.8 | Tower middleware, Tokio-native |
| Database | SQLite (rusqlite, bundled) | Zero-dependency, single-file, indexed search |
| Auth | JWT (HS256) + Argon2id | Stateless tokens, modern password hashing |
| Config | Figment + Clap | Layered: CLI > env > file > defaults |
| Logging | tracing | Structured, async-aware, JSON output |
| File I/O | tokio::fs + notify | Async non-blocking I/O with filesystem watching |
| Caching | Moka + blake3 | LRU with TTL for listings, content-hashed thumbnails |
| Rate limiting | Governor | Token-bucket per-IP login throttling |
| Image processing | image | Thumbnail generation with concurrency control |
| Frontend | React 19, Vite, Tailwind CSS | Embedded SPA via rust-embed, HLS.js + tus-js-client |
- Path traversal prevention -- all paths canonicalized against root
Content-Security-Policy: script-src 'none'on file downloadsX-Content-Type-Options: nosniff,X-Frame-OptionsCache-Control: no-storeon API responses (except thumbnails/HLS which areimmutable),privateon downloads- Atomic writes via temp file + rename (no corruption on crash)
- 5-minute setup timeout, then locked until restart
- Explicit CORS method/header allowlist
- Client IP logging via X-Forwarded-For / X-Real-IP with trusted proxy validation
- HttpOnly, SameSite=Strict auth cookies with optional Secure flag
- Per-IP rate limiting on login (10 attempts / 15 minutes) and expensive API endpoints (60 requests / minute)
- Argon2 DoS protection via max password length (128)
- Constant-time password verification (dummy hash fallback on missing user)
Prerequisites: Rust 1.91.0 (pinned in rust-toolchain.toml), Node 22+ and pnpm for frontend work. Optional: ffmpeg for HLS video transcoding.
make dev # frontend dev server + Rust backend in parallel
make test # integration tests
make build # production build (frontend + optimized release binary)
make lint # format + lint (cargo fmt/clippy + pnpm lint)
make docker # Docker image (single arch)
make docker-multi # Docker image (amd64 + arm64)
make clean # remove all build artifactsSee CONTRIBUTING.md for guidelines on adding endpoints and writing tests.
server {
listen 443 ssl;
server_name files.example.com;
client_max_body_size 10G;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_request_buffering off;
}
}- Set a strong admin password on first run
- Configure TLS (directly or via reverse proxy)
- Set
RUSTYFILE_ROOTto the directory you want to serve - Ensure
RUSTYFILE_DATA_DIRis on persistent storage - Set
RUSTYFILE_TRUSTED_PROXIESto your reverse proxy IPs - Set
RUSTYFILE_CORS_ORIGINSto your domain - Set
log_leveltowarnfor reduced noise - Set
log_formattojsonfor log aggregation
MIT
