βββββββ βββ ββββββββββββββββββββ ββββββββββ βββ ββββββ ββββ βββ
βββββββββββ βββββββββββββββββββββββββββββββ ββββββββββββββββ βββ
βββββββββββ βββββββββββ βββ βββ ββββββββββββββββββββββ βββ
βββββββββββ βββββββββββ βββ βββ ββββββββββββββββββββββββββ
βββ ββββββββββββββββββββ βββ βββββββββββ ββββββ ββββββ ββββββ
βββ βββ βββββββ ββββββββ βββ ββββββββββ ββββββ ββββββ βββββ
Quick Start Β· Features Β· Optional Integrations Β· Configuration Β· Backup System Β· Deployment Β· Themes Β· Changelog
RustChan is a fully-featured imageboard engine compiled into a single Rust binary. Deploy it on a VPS, a Raspberry Pi, or a local machine β no containers, no runtime, no package manager required. All persistent data lives in a single directory alongside the binary, making migrations as simple as cp -r.
Two external tools are supported as optional enhancements: ffmpeg for video transcoding and audio waveforms, and Tor for anonymous .onion access. Neither is required β RustChan degrades gracefully without them.
|
|
|
|
|
|
RustChan is fully functional without either tool. When detected at startup, additional capabilities activate automatically.
When ffmpeg is available on PATH:
- MP4 β WebM transcoding (VP9 + Opus) for maximum browser compatibility
- AV1 WebM β VP9 re-encoding for browsers without AV1 support
- Audio waveform thumbnails via the
showwavespicfilter - Video thumbnail extraction from the first frame for catalog previews
Without ffmpeg, videos are served in their original format and audio posts use a generic icon. Set require_ffmpeg = true in settings.toml to enforce its presence at startup. The ffmpeg execution timeout is configurable via ffmpeg_timeout_secs (default: 120).
See SETUP.md β Installing ffmpeg for platform-specific instructions.
When enable_tor_support = true and a Tor daemon is running:
- The
.onionaddress is read from the hidden-servicehostnamefile and displayed on the home page and admin panel - Setup hints are printed to the console if Tor is detected but not yet configured
Tor handles all onion routing independently β RustChan binds to its normal port while your torrc forwards .onion traffic to it.
See SETUP.md β Installing Tor for configuration details.
# 1. Build
cargo build --release
# 2. Create an admin account
./rustchan-cli admin create-admin admin "YourStrongPassword!"
# 3. Create boards
./rustchan-cli admin create-board b "Random" "General discussion"
./rustchan-cli admin create-board tech "Technology" "Programming and hardware"
# 4. Start the server
./rustchan-cliOpen http://localhost:8080 β the admin panel is at /admin.
On first launch, rustchan-data/settings.toml is generated with a fresh cookie_secret and all settings documented inline. Edit and restart to apply changes.
All data lives in rustchan-data/ alongside the binary. Nothing is written elsewhere unless explicitly overridden via environment variables.
rustchan-cli β single self-contained binary
rustchan-data/
βββ settings.toml β instance configuration (auto-generated)
βββ chan.db β SQLite database (WAL mode)
βββ full-backups/ β full site backups
β βββ rustchan-backup-20260304_120000.zip
βββ board-backups/ β per-board backups
β βββ rustchan-board-tech-20260304_120000.zip
βββ boards/
βββ b/
β βββ <uuid>.<ext> β uploaded files
β βββ thumbs/
β βββ <uuid>_thumb.jpg β auto-generated thumbnails & waveforms
βββ tech/
βββ <uuid>.<ext>
βββ thumbs/
Auto-generated on first run. Edit and restart to apply.
# Site identity
forum_name = "RustChan"
site_subtitle = "A self-hosted imageboard"
# Default theme served to new visitors. Options: terminal, frutiger-aero,
# dorific-aero, fluorogrid, neoncubicle, chan-classic
default_theme = "terminal"
# TCP port (binds to 0.0.0.0:<port>).
port = 8080
# Upload size limits (MiB).
max_image_size_mb = 8
max_video_size_mb = 50
max_audio_size_mb = 150
# Auto-generated on first run. Do not change after first use β
# existing IP hashes and bans will become invalid.
cookie_secret = "<auto-generated 32-byte hex>"
# Display .onion address if a Tor daemon is running.
enable_tor_support = false
# Hard-exit if ffmpeg is not found (default: warn only).
require_ffmpeg = false
# Maximum time (seconds) to allow a single ffmpeg job to run.
ffmpeg_timeout_secs = 120
# WAL checkpoint interval in seconds (0 = disabled).
wal_checkpoint_interval_secs = 3600
# Automatic VACUUM: compact the database this many hours after startup,
# then repeat on the same interval. Set to 0 to disable.
auto_vacuum_interval_hours = 24
# Expired poll vote cleanup interval (hours). Vote rows for expired polls
# are deleted; poll questions and options are preserved.
poll_cleanup_interval_hours = 72
# Show a red warning banner in the admin panel when the DB exceeds this size.
db_warn_threshold_mb = 2048
# Maximum number of pending background jobs. Excess jobs are dropped with
# a warning log rather than causing OOM under a post flood.
job_queue_capacity = 1000
# Maximum waveform/thumbnail cache size per board's thumbs/ directory (MiB).
# A background task evicts oldest files when the limit is exceeded.
waveform_cache_max_mb = 200
# Tokio blocking thread pool size. Defaults to logical_cpus Γ 4.
# Tune downward on memory-constrained hardware (e.g. Raspberry Pi).
# blocking_threads = 16
# Archive overflow threads globally before any hard-delete, even on boards
# where per-board archiving is disabled.
archive_before_prune = trueAll settings can be overridden via environment variables, which take precedence over settings.toml.
| Variable | Default | Description |
|---|---|---|
CHAN_FORUM_NAME |
RustChan |
Site display name |
CHAN_SITE_SUBTITLE |
(from settings.toml) | Home page subtitle |
CHAN_DEFAULT_THEME |
terminal |
Default theme for new visitors |
CHAN_PORT |
8080 |
TCP port |
CHAN_BIND |
0.0.0.0:8080 |
Full bind address (overrides CHAN_PORT) |
CHAN_DB |
rustchan-data/chan.db |
SQLite database path |
CHAN_UPLOADS |
rustchan-data/boards |
Uploads directory |
CHAN_COOKIE_SECRET |
(from settings.toml) | CSRF tokens and IP hashing key |
CHAN_MAX_IMAGE_MB |
8 |
Max image upload size (MiB) |
CHAN_MAX_VIDEO_MB |
50 |
Max video upload size (MiB) |
CHAN_MAX_AUDIO_MB |
150 |
Max audio upload size (MiB) |
CHAN_THUMB_SIZE |
250 |
Thumbnail max dimension (px) |
CHAN_BUMP_LIMIT |
500 |
Replies before a thread stops bumping |
CHAN_MAX_THREADS |
150 |
Max threads per board before pruning/archiving |
CHAN_RATE_POSTS |
10 |
Max POSTs per rate window per IP |
CHAN_RATE_WINDOW |
60 |
Rate-limit window (seconds) |
CHAN_SESSION_SECS |
28800 |
Admin session duration (default: 8 hours) |
CHAN_BEHIND_PROXY |
false |
Trust X-Forwarded-For behind a reverse proxy |
CHAN_HTTPS_COOKIES |
(same as CHAN_BEHIND_PROXY) |
Set Secure flag on session cookies |
CHAN_WAL_CHECKPOINT_SECS |
3600 |
WAL checkpoint interval; 0 to disable |
CHAN_AUTO_VACUUM_HOURS |
24 |
Scheduled VACUUM interval (hours); 0 to disable |
CHAN_POLL_CLEANUP_HOURS |
72 |
Expired poll vote cleanup interval (hours) |
CHAN_DB_WARN_MB |
2048 |
DB size warning threshold (MiB) |
CHAN_JOB_QUEUE_CAPACITY |
1000 |
Max pending background jobs |
CHAN_FFMPEG_TIMEOUT_SECS |
120 |
Max duration for a single ffmpeg job |
CHAN_WAVEFORM_CACHE_MB |
200 |
Max waveform thumbnail cache per board (MiB) |
CHAN_BLOCKING_THREADS |
cpus Γ 4 |
Tokio blocking thread pool size |
CHAN_ARCHIVE_BEFORE_PRUNE |
true |
Archive globally before any hard-delete |
CHAN_TOR_HOSTNAME_FILE |
(auto-detected) | Override path to the Tor hostname file |
RUST_LOG |
rustchan-cli=info |
Log verbosity |
The entire backup system is accessible from the admin panel β no shell access required. All backup operations stream from disk in 64 KiB chunks; peak RAM overhead is roughly 64 KiB regardless of instance size. Backups are written to disk as temp files with an atomic rename on success, so partial backups never appear in the saved list.
A full backup is a .zip containing a consistent SQLite snapshot (via VACUUM INTO) and all uploaded files.
| Action | Description |
|---|---|
| πΎ Save | Creates a backup and writes it to rustchan-data/full-backups/ |
| β¬ Download | Streams a saved backup to your browser |
| βΊ Restore (server) | Restores from a file already on the server |
| βΊ Restore (upload) | Restores from a .zip uploaded from your computer (max 512 MiB) |
| β Delete | Permanently removes the backup file |
Board backups are self-contained: a board.json manifest plus the board's upload directory. Other boards are never affected.
Restore behaviour:
- Board exists β content is wiped and replaced from the manifest
- Board doesn't exist β created from scratch
- All row IDs are remapped on import to prevent collisions
Restore uses SQLite's
sqlite3_backup_init()API internally β pages are copied directly into the live connection, so no file swapping, WAL deletion, or restart is needed.
# Admin accounts
./rustchan-cli admin create-admin <username> <password>
./rustchan-cli admin reset-password <username> <new-password>
./rustchan-cli admin list-admins
# Boards
./rustchan-cli admin create-board <short> <name> [description] [--nsfw]
./rustchan-cli admin delete-board <short>
./rustchan-cli admin list-boards
# Bans
./rustchan-cli admin ban <ip_hash> "<reason>" [duration_hours]
./rustchan-cli admin unban <ban_id>
./rustchan-cli admin list-bans<short> is the board slug used in URLs (e.g. tech β /tech/). Lowercase alphanumeric, 1β8 characters.
See SETUP.md for a complete production guide covering:
- System user creation and hardened directory layout
- systemd service with security directives
- nginx reverse proxy with TLS via Let's Encrypt
- ffmpeg and Tor installation on Linux, macOS, and Windows
- First-run configuration walkthrough
- Raspberry Pi SD card wear reduction and blocking thread tuning
- Security hardening checklist
# ARM64 (Raspberry Pi 4/5)
rustup target add aarch64-unknown-linux-gnu
cargo install cross
cross build --release --target aarch64-unknown-linux-gnu
# Windows x86-64
rustup target add x86_64-pc-windows-gnu
cargo build --release --target x86_64-pc-windows-gnuThe release profile enables strip = true, lto = "thin", and panic = "abort". Typical binary size: 12β18 MiB.
RustChan is intentionally minimal β no template engine, no ORM, no JavaScript framework. HTML is rendered with plain Rust format! strings. The result is a single binary that starts in under a second.
| Layer | Technology |
|---|---|
| Web framework | Axum 0.8 |
| Async runtime | Tokio 1.x (manually sized blocking pool) |
| Database | SQLite via rusqlite (bundled, 32 MiB page cache) |
| Connection pool | r2d2 + r2d2_sqlite (5-second acquisition timeout) |
| Image processing | image crate + kamadak-exif for JPEG orientation |
| Video transcoding | ffmpeg (optional, configurable timeout) |
| Audio waveforms | ffmpeg showwavespic filter (optional) |
| Password hashing | argon2 crate (Argon2id) |
| Timing-safe comparison | subtle crate |
| Response compression | tower-http CompressionLayer (gzip, Brotli, zstd) |
| HTML rendering | Plain Rust format! strings |
| Configuration | settings.toml + env var overrides via once_cell::Lazy |
| Logging | tracing + tracing-subscriber |
src/
βββ main.rs β entry point, router, background tasks, keyboard console
βββ config.rs β settings.toml + env var resolution
βββ error.rs β error handling and ban page rendering
βββ models.rs β database row structs
βββ middleware/mod.rs β rate limiting, CSRF, IP hashing, proxy trust
βββ workers/mod.rs β background job queue, media transcoding, cache eviction
βββ handlers/
β βββ admin.rs β admin panel, moderation, backup/restore, appeals
β βββ board.rs β board index, catalog, archive, search, thread creation
β βββ mod.rs β streaming multipart, shared upload helpers
β βββ thread.rs β thread view, replies, polls, editing
βββ db/
β βββ mod.rs β connection pool, schema init, shared helpers
β βββ boards.rs β site settings, board CRUD, stats
β βββ threads.rs β thread listing, creation, mutation, archiving, pruning
β βββ posts.rs β post CRUD, file deduplication, polls, job queue
β βββ admin.rs β sessions, bans, word filters, reports, mod log, appeals
βββ templates/
β βββ mod.rs β base layout, pagination, timestamp formatting, utilities
β βββ board.rs β home page, board index, catalog, search, archive
β βββ thread.rs β thread view, post rendering, polls, edit form
β βββ admin.rs β login page, admin panel, mod log, VACUUM results, IP history
β βββ forms.rs β new thread and reply forms
βββ utils/
βββ crypto.rs β Argon2id, CSRF, sessions, IP hashing, PoW verification
βββ files.rs β upload validation, thumbnails, EXIF stripping + orientation
βββ sanitize.rs β HTML escaping, markup (greentext, spoilers, dice, embeds)
βββ tripcode.rs β SHA-256 tripcode generation
| Concern | Implementation |
|---|---|
| Passwords | Argon2id (t=2, m=65536, p=2) β memory-hard, GPU-resistant |
| Brute-force | Progressive lockout after 5 failed admin login attempts per IP |
| Sessions | HttpOnly, SameSite=Strict, Max-Age aligned to server config |
| CSRF | Double-submit cookie with constant-time token comparison (subtle::ct_eq) |
| Security headers | CSP (script-src 'self', no unsafe-inline), HSTS (1 year + subdomains), Permissions-Policy |
| Inline JavaScript | Fully eliminated β all JS in external files; CSP enforced with no unsafe-inline |
| IP privacy | Raw IPs never stored or logged β HMAC-keyed SHA-256 hash used everywhere |
| Rate limiting | Sliding-window per hashed IP: POST endpoints (10/min), page-load GETs (60/min); /api/ routes excluded |
| Proxy support | All handlers use proxy-aware IP extraction when CHAN_BEHIND_PROXY=true |
| File safety | Content-Type + magic byte validation; file extensions never trusted |
| EXIF stripping | All JPEG uploads re-encoded β GPS, device IDs, and all metadata discarded; EXIF orientation applied before strip |
| XSS | All user input HTML-escaped before rendering; markup applied post-escape |
| Zip-bomb protection | Backup restore capped at 1 GiB per entry, 50,000 entries max |
| Backup upload cap | Full and board restore endpoints reject uploads over 512 MiB |
| Redirect hardening | Backslash and percent-encoded variants blocked on return_to parameters |
| Path traversal | Backup filenames validated against [a-zA-Z0-9._-] before filesystem access |
| Body limits | Per-route limits on small endpoints (64 KiB) to prevent memory exhaustion |
| Connection pool | 5-second acquisition timeout prevents thread-pool exhaustion under load |
| PoW CAPTCHA | SHA-256 hashcash (20-bit difficulty), verified server-side with 5-minute grace window; covers threads and replies |
| PoW nonce replay | Used nonces tracked in memory; stale entries auto-pruned after the validity window expires |
| Job queue | Capped at job_queue_capacity; excess jobs logged and dropped, never causing OOM |
| Streaming uploads | Multipart fields validated against size limits in flight; memory use bounded regardless of payload |
>quoted text greentext line
>>123 reply link to post #123
>>>/board/ cross-board index link
>>>/board/123 cross-board thread link (with hover preview)
**text** bold
__text__ italic
[spoiler]text[/spoiler] hidden until clicked or hovered
[dice NdM] server-side dice roll (e.g. [dice 2d6] β π² 2d6 βΈ β β
= 11)
:fire: :think: :based: :kek: β¦ (25 emoji shortcodes)
Six built-in themes, selectable via the floating picker on every page. Persisted in localStorage with no flash on load. The site-wide default for new visitors is set via default_theme in settings.toml or from the admin panel.
| Theme | Description |
|---|---|
| Terminal (default) | Dark background, matrix-green monospace, glowing accents |
| Frutiger Aero | Frosted glass panels, pearl-blue gradients, rounded corners |
| DORFic Aero | Dark stone walls, torchlit amber/copper glass panels |
| FluoroGrid | Pale sage, muted teal grid lines, dusty lavender panels |
| NeonCubicle | Cool off-white, horizontal scanlines, steel-teal borders |
| ChanClassic | Light tan/beige background, maroon accents, blue post-number links β classic imageboard styling |
See CHANGELOG.md for the full version history.
Latest β v1.0.13:
Scheduled VACUUM Β· expired poll vote cleanup Β· DB size warning banner Β· job queue back-pressure Β· duplicate media job coalescing Β· configurable ffmpeg timeout Β· global archive_before_prune flag Β· waveform cache eviction Β· streaming multipart Β· ETag / Conditional GET (304) Β· gzip/Brotli/zstd response compression Β· manual Tokio blocking pool sizing Β· EXIF orientation correction Β· streaming backup I/O (peak RAM ~64 KiB) Β· ChanClassic theme Β· default_theme + site_subtitle in settings.toml Β· default theme selector in admin panel Β· admin panel reorganised Β· prepared statement caching audit Β· RETURNING clause for inserts Β· 32 MiB SQLite page cache Β· two new DB indexes (idx_posts_thread_id, idx_posts_ip_hash)
v1.0.12: Database module split into 5 focused files Β· template module split into 5 focused files Β· PoW bypass on replies fixed (critical) Β· PoW nonce replay protection Β· inline JS fully eliminated (script-src 'self' CSP) Β· backup upload size cap (512 MiB) Β· post rate limiting simplified Β· /api/ routes excluded from GET rate limit Β· trailing slash 404s fixed
v1.0.11: Security headers (CSP, HSTS, Permissions-Policy) Β· proxy-aware IP extraction on all handlers Β· GET rate limiting (60 req/min) Β· zip-bomb protection on restore Β· IP hashing everywhere Β· admin brute-force lockout Β· constant-time CSRF comparison Β· poll input caps Β· session cookie Max-Age Β· connection pool timeout Β· per-route body limits Β· open redirect hardening Β· worker exponential backoff Β· file dedup race fix Β· per-post ban+delete Β· ban appeal system Β· PoW CAPTCHA Β· video embeds Β· cross-board hover previews Β· new-reply pill Β· live thread metadata Β· "(You)" tracking Β· spoiler text
v1.0.9: Per-board editing toggle Β· configurable edit window Β· per-board archive toggle Β· AV1βVP9 transcoding fix
v1.0.8: Thread archiving Β· mobile reply drawer Β· dice rolling Β· sage Β· post editing Β· draft autosave Β· WAL checkpointing Β· VACUUM button Β· IP history
v1.0.7: EXIF stripping Β· image+audio combo posts Β· audio waveform thumbnails
v1.0.6: Web-based backup management Β· board-level backup/restore Β· GitHub Actions CI
v1.0.5: MP4βWebM auto-transcoding Β· home page stats Β· macOS Tor detection fix
Built with π¦ Rust Β Β·Β Powered by SQLite Β Β·Β Optional integrations: ffmpeg Β· Tor
Drop it anywhere. It just runs.