Current setup and deployment guide for Linux, macOS, and Windows.
Current version: 1.7.1.
This guide reflects the current RustChan architecture:
- Tor onion hosting is built in via Arti. You do not install or manage a separate
torservice. - Images, GIF/WebP animation, HEIC/HEIF, container metadata, common audio waveforms, and supported PDF previews run internally in Rust. Unsupported PDF previews use the built-in SVG placeholder.
ffmpegis optional, but recommended for video thumbnails, WebM transcoding, and uncovered audio codecs. Standaloneffprobeand external PDF renderers are not required.- The post edit form and self-delete flow share a 60-second self-action window after posting.
- What RustChan Needs
- Quick Start
- Install Rust
- Install ffmpeg
- Verify WebP and WebM Support
- Build and Run
- First-Run Files and Layout
- Important settings.toml Options
- Tor Onion Service
- HTTPS and TLS
- Linux Service Setup
- Reverse Proxy Notes
- Admin Bootstrapping
- Banner Artwork Requirements
- Updating
- Troubleshooting
RustChan is a single Rust binary. A basic install only needs:
- Rust 1.99 or newer to build it
- a writable runtime data directory (next to the binary by default, or selected with
--data-dir) ffmpegfor video processing and uncovered audio codecs
RustChan does not require:
- Docker
- Postgres or MySQL
- Redis
- a separate Tor daemon
git clone https://github.com/csd113/RustChan.git
cd RustChan
cargo build --release
./target/release/rustchan-cliOn first launch RustChan creates rustchan-data/settings.toml, rustchan-data/logs/, and the rest of its runtime directories next to the binary.
Then in another terminal:
./target/release/rustchan-cli admin create-admin admin "ChangeThisPasswordNow"
./target/release/rustchan-cli admin create-board b "Random" "General discussion"Open:
http://localhost:8080- admin panel:
http://localhost:8080/admin
If TLS is enabled in settings.toml, RustChan also serves HTTPS on port 8443 by default.
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"
rustc --version
cargo --versionInstall Rust with rustup-init.exe from rustup.rs, then open a new PowerShell window and verify:
rustc --version
cargo --versionffmpeg is optional, but RustChan is significantly better with it.
When ffmpeg is available, RustChan can:
- extract video thumbnails
- generate waveform thumbnails for uncovered audio codecs (for example AC-3 or Speex)
- transcode MP4 uploads to WebM when VP9 and Opus are available
Without ffmpeg, images, animation, metadata inspection, supported PDF previews, and common audio waveforms still work. Video processing and uncovered-codec waveform previews use their existing placeholders.
sudo apt update
sudo apt install -y ffmpegsudo dnf install -y ffmpegIf your base Fedora repos do not provide the codec-enabled build you want, use RPM Fusion.
brew install ffmpegwinget install --id Gyan.FFmpeg -eThen make sure the FFmpeg bin directory is on PATH.
ffmpeg -versionIf you want RustChan to refuse startup when ffmpeg is missing, set:
require_ffmpeg = trueWebP images, GIF → animated WebP, and animated WebP validation are built into RustChan through Rust crates and remain available without FFmpeg. Video thumbnails use FFmpeg to extract a PNG frame and Rust to encode WebP.
For video/WebM conversion, RustChan checks the installed decoder, encoder and muxer lists independently:
libvpx-vp9for WebM video encodinglibopusfor WebM audio encoding- the
webmmuxer for output - AV1 decoders for AV1 inputs (AV1 encoder availability is reported separately; output remains VP9 + Opus)
Use these commands:
ffmpeg -encoders | rg libvpx-vp9
ffmpeg -encoders | rg libopus
ffmpeg -muxers | rg webm
ffmpeg -decoders | rg av1If you do not have rg, use:
ffmpeg -encoders | grep libvpx-vp9
ffmpeg -encoders | grep libopusWebM conversion needs both selected encoders and the WebM muxer. AV1 input also needs an AV1 decoder; an AV1 encoder is optional and is not selected for RustChan output.
libvpx-vp9+libopus: MP4/Matroska and WebM/AV1 to VP9/Opus WebM conversion. Compatible VP8/VP9 WebM with Opus/Vorbis audio is preserved.
On Debian-family systems, the usual install is:
sudo apt update
sudo apt install -y ffmpeg libvpx-dev libopus-devThe important part is still the actual ffmpeg -encoders output. Package names alone do not guarantee your installed FFmpeg binary was built with every encoder enabled.
Most Homebrew FFmpeg installs are fine, but verify with:
ffmpeg -encoders | rg 'libvpx-vp9|libopus'If one is missing, reinstall FFmpeg from a build source that includes that codec set.
Use a full FFmpeg build rather than a minimal one, then verify with:
ffmpeg -encoders | Select-String libvpx-vp9
ffmpeg -encoders | Select-String libopusRustChan will log warnings and continue:
- missing VP9, Opus or the WebM muxer: video uploads retain their originals
- missing an AV1 decoder: AV1 conversion fails with a useful diagnostic and retains the original
- missing FFmpeg: Rust-native still and animated WebP continue to work
These warnings appear in the console at startup and in rustchan-data/logs/.
cargo build --releaseBinary:
- Linux/macOS:
target/release/rustchan-cli - Windows:
target/release/rustchan-cli.exe
./target/release/rustchan-cli./target/release/rustchan-cli --port 9090
./target/release/rustchan-cli --data-dir /absolute/path/to/rustchan-dataBy default RustChan stores runtime state in rustchan-data/ next to the binary.
Pass --data-dir with an absolute, non-root path to place the complete runtime
layout elsewhere. This is the supported layout for service installations; it
includes settings.toml, the database, uploads, logs, backups, and runtime
secrets. The selected data directory has this layout:
rustchan-data/
├── settings.toml
├── chan.db
├── logs/
│ └── rustchan.YYYY-MM-DD.log
├── backups/
│ ├── full/
│ └── boards/
├── runtime/
│ ├── tls/
│ ├── tor/
│ │ ├── state/
│ │ └── cache/
│ ├── favicon/
│ └── tmp/
└── boards/
## Banner Artwork Requirements
RustChan `1.4.0` includes board banners plus a separate home-page announcement banner.
Banner upload requirements:
- exact `468x60` aspect ratio
- minimum size `468x60`
- recommended size `936x120`
- input can be PNG, JPEG, or WebP
- RustChan converts uploaded banner images to WebP automatically
Board banner placement:
- board index: under the board name/description, above `[Index] [Catalog] [Archive]`
- catalog: under the board name/description, above `Sort By:` and `Show OP Comment:`
- no banner on thread pages
- no banner on archive pages
- no banner on search pages
Home page banner placement:
- separate centered banner box on the home page
- intended for MOTD/news/announcement use
Banner link behavior:
- internal board and internal-path links work directly
- external links can be enabled in the admin panel
- when enabled, external banner clicks go through an on-site warning page before redirecting
Important notes:
settings.tomlis generated automatically on first runcookie_secretis generated automatically on first run- Tor state and onion keys live under
rustchan-data/runtime/tor/state/ - logs rotate daily under
rustchan-data/logs/
The generated file documents every setting inline. Commonly tuned settings:
forum_name = "RustChan"
site_subtitle = "select board to proceed"
default_theme = "forest"
enabled_builtin_themes = ["forest", "blue-sky", "deep-orbit", "terminal", "dorfic", "chanclassic", "aero", "neoncubicle", "fluorogrid"]
port = 8080
max_image_size_mb = 8
max_video_size_mb = 50
max_audio_size_mb = 150
enable_tor_support = true
# tor_only = false
# tor_bootstrap_timeout_secs = 120
# tor_max_concurrent_streams = 512
# tor_service_nickname = "rustchan"
require_ffmpeg = false
# ffmpeg_path = "/usr/local/bin/ffmpeg"
ffmpeg_timeout_secs = 600
[tls]
enabled = false
require_https = false
port = 8443
# redirect_http = true
# http_port = 8080enable_tor_support = true: built-in onion service is ontor_only = true: bind RustChan to loopback and serve only through Torrequire_ffmpeg = true: fail startup if ffmpeg is missing[tls].enabled = true: explicitly enable RustChan's native HTTPS listener[tls].require_https = true: opt into disabling public plaintext application accessffmpeg_timeout_secs = 600: max runtime for a single ffmpeg job
RustChan includes built-in Tor onion service hosting through Arti.
You do not need to:
- install
tor - write a
torrc - manage a hidden service directory manually
On current builds, the generated settings.toml enables Tor support by default:
enable_tor_support = trueOn first startup with Tor enabled, RustChan:
- creates the Tor runtime directories
- bootstraps to the Tor network
- generates or loads the onion service keypair
- starts serving the site over
.onion
The first bootstrap usually takes longer than later boots because Tor directory data has to be downloaded and cached.
Back up:
rustchan-data/runtime/tor/state/
That directory contains the persistent onion identity. If you lose it, the next startup will generate a new onion address.
If you want RustChan reachable only through Tor:
enable_tor_support = true
tor_only = trueIn this mode RustChan binds to loopback instead of 0.0.0.0, so clearnet access is blocked.
RustChan creates the Tor state directory with restricted permissions on Unix. If you move the data directory manually, preserve write access for the RustChan service user.
RustChan has built-in HTTPS support.
The generated settings.toml currently includes:
[tls]
enabled = false
port = 8443This means:
- HTTP is available on the main app port
- HTTPS support is configured but disabled until you turn it on
- when enabled, RustChan can generate a local self-signed development certificate
/healthz is public and intentionally minimal. /readyz returns only a readiness status by default, and /metrics returns 404 unless explicitly enabled.
Only enable detailed readiness or metrics for a trusted scrape path:
public_readiness_details = true
public_metrics_enabled = trueDetailed readiness and metrics include operational state such as database schema health, backup freshness, media backlog, maintenance state, and Tor readiness. If you expose them, use a reverse-proxy allowlist, private network, or equivalent network boundary. Tor-facing deployments should keep the defaults unless you intentionally monitor those endpoints externally.
| # | Setting | Scope | Default | Enabled by default? | Admin? | Config? | Notes |
|---|---|---|---|---|---|---|---|
| 3 | Homepage board-card new-thread badges | site-wide | true |
true | Yes | Yes | First boot seeds DB from settings.toml; later admin-owned. |
| 4 | Board/catalog thread-card new-reply badges | site-wide | true |
true | Yes | Yes | First boot seeds DB from settings.toml; later admin-owned. |
| 9 | Allow external banner links after warning page | banner | false |
false | Yes | No | DB-backed admin setting only. |
| 12 | Board NSFW flag | per-board | false |
false | Yes | No | New-board create form leaves this unchecked. |
| 16 | Archive overflow threads | per-board | true |
true | Yes | No | Global prune config can still override hard-delete behavior. |
| 20 | CAPTCHA on threads and replies | per-board | false |
false | Yes | No | Per-board admin toggle. |
| 24 | Allow images | per-board | true |
true | Yes | No | New boards start with image uploads enabled. |
| 25 | Allow video | per-board | true |
true | Yes | No | New boards start with video uploads enabled. |
| 26 | Allow audio | per-board | false |
false | Yes | No | New-board create form leaves this unchecked. |
| 27 | Allow PDF uploads | per-board | false |
false | Yes | No | Per-board admin toggle. |
| 28 | Allow any file uploads | per-board | false |
false | Yes | No | Only available when the global arbitrary-file gate is enabled. |
| 29 | Allow tripcodes | per-board | true |
true | Yes | No | Per-board admin toggle. |
| 30 | Embed video links (YouTube) | per-board | true |
true | Yes | No | New-board default for fresh and existing installs. |
| 31 | Show thread-local poster IDs | per-board | true |
true | Yes | No | New-board default for fresh and existing installs. |
| 32 | Collapse long greentext | per-board | false |
false | Yes | No | Per-board render behavior toggle. |
| 33 | Allow users to edit their own posts during the 60-second grace window | per-board | true |
true | Yes | No | New-board default for fresh and existing installs. |
| 34 | Allow users to delete their own posts during the 60-second grace window | per-board | true |
true | Yes | No | New-board default for fresh and existing installs. |
| 38 | Master arbitrary-file upload gate | media | false |
false | Indirect | Yes | When off, boards cannot enable generic file uploads. |
| 39 | Require ffmpeg at startup | media | false |
false | No | Yes | When off, RustChan degrades gracefully where possible. |
| 46 | TLS enabled | TLS | false |
false | No | Yes | Generated settings.toml keeps native HTTPS off until explicitly enabled. |
| 48 | Redirect HTTP to HTTPS | TLS | false |
false | No | Yes | Only relevant when native TLS is enabled. |
| 50 | ACME enabled | TLS | false |
false | No | Yes | The ACME section stays commented out by default. |
| 51 | ACME staging | TLS | true |
true | No | Yes | Applies if the ACME section is enabled and the field is omitted. |
| 53 | Built-in Tor support | Tor | true |
true | No | Yes | Generated config enables Tor support by default. |
| 54 | Tor-only mode | Tor | false |
false | No | Yes | Keeps clearnet access on unless you explicitly disable it. |
| 55 | Public detailed readiness | observability | false |
false | No | Yes | Keep off unless /readyz is behind a trusted scrape path. |
| 56 | Public metrics | observability | false |
false | No | Yes | Keep off unless /metrics is behind a trusted scrape path. |
| 60 | Include Tor hidden-service keys in automatic full backups | backup / Tor | true |
true | Yes | Yes | Admin saves rewrite settings.toml; existing installs keep their current configured value until changed. |
| 66 | Archive before prune | maintenance / archive | true |
true | No | Yes | Global override: prune archives instead of hard-deletes. |
| 75 | New banner enabled flag | banner | true |
true | Yes | No | Applies to newly uploaded global, board, and home banners. |
| 76 | New global/board banner shows on board index | banner | true |
true | Yes | No | Home banners do not use this placement flag. |
| 77 | New global/board banner shows on catalog | banner | true |
true | Yes | No | Home banners do not use this placement flag. |
Keep built-in TLS enabled and use the self-signed certificate.
Many operators still prefer putting nginx or Caddy in front and terminating TLS there.
RustChan also supports ACME-based certificates when built with the tls-acme feature and configured in [tls.acme].
Run RustChan as a dedicated unprivileged user.
sudo useradd --system --home /var/lib/rustchan --create-home --shell /usr/sbin/nologin rustchancargo build --release
sudo install -o root -g root -m 0755 target/release/rustchan-cli /usr/local/bin/rustchan-cli
sudo mkdir -p /var/lib/rustchan
sudo chown -R rustchan:rustchan /var/lib/rustchanThis creates settings.toml and the runtime layout:
sudo -u rustchan -H /usr/local/bin/rustchan-cli --data-dir /var/lib/rustchanStop it after the first start, edit /var/lib/rustchan/settings.toml, then continue.
Create /etc/systemd/system/rustchan.service:
[Unit]
Description=RustChan
After=network-online.target
Wants=network-online.target
[Service]
User=rustchan
Group=rustchan
WorkingDirectory=/var/lib/rustchan
StateDirectory=rustchan
StateDirectoryMode=0700
ExecStart=/usr/local/bin/rustchan-cli --data-dir /var/lib/rustchan serve
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
[Install]
WantedBy=multi-user.targetStateDirectory=rustchan asks systemd to keep /var/lib/rustchan writable by
the unprivileged service account. The explicit --data-dir keeps all runtime
state there even though the root-owned executable lives under /usr/local/bin.
Then:
sudo systemctl daemon-reload
sudo systemctl enable --now rustchan
sudo journalctl -u rustchan -fYou can add overrides with:
sudo systemctl edit rustchanExample:
[Service]
Environment=CHAN_BIND=127.0.0.1:8080
Environment=CHAN_REQUIRE_FFMPEG=trueIf you put nginx or Caddy in front of RustChan:
- point the proxy at the RustChan HTTP listener
- set
CHAN_BEHIND_PROXY=trueif you want proxy headers trusted - set
CHAN_TRUSTED_PROXY_CIDRSto the proxy's loopback or private CIDR when the proxy is not on localhost - decide whether TLS terminates at the proxy or inside RustChan
When RustChan's built-in TLS is enabled, HTTPS is an additional listener and the
main HTTP application listener remains available by default. Set
require_https = true under [tls] to opt into HTTPS-only access. Independently,
redirect_http = true exposes tls.http_port as a redirect listener instead of
serving application routes there. With the built-in Tor service enabled,
HTTPS-only mode also keeps a loopback HTTP backend that accepts only connections
registered by its in-process Tor proxy.
RustChan accepts bounded Content-Length request bodies and rejects
Transfer-Encoding at the application boundary. Configure a reverse proxy to
dechunk request bodies before forwarding them. Request headers are limited to
32 KiB per value and 64 KiB in aggregate.
Typical loopback setup:
internet -> nginx/caddy -> 127.0.0.1:8080 -> RustChan
If you use a reverse proxy and terminate TLS there, make sure your proxy forwards the usual headers and that RustChan is not accidentally exposed directly on the public interface.
Create the first admin account:
./target/release/rustchan-cli admin create-admin admin "UseAStrongPassword"Create a board:
./target/release/rustchan-cli admin create-board tech "Technology" "Programming and hardware"Other useful commands:
./target/release/rustchan-cli admin list-admins
./target/release/rustchan-cli admin list-boards
./target/release/rustchan-cli admin reset-password admin "NewStrongPassword"git pull
cargo build --release
sudo install -o root -g root -m 0755 target/release/rustchan-cli /usr/local/bin/rustchan-cli
sudo systemctl restart rustchanBefore major updates, back up:
rustchan-data/chan.dbrustchan-data/boards/rustchan-data/runtime/tor/state/rustchan-data/settings.toml
Or use the built-in backup tools from the admin panel.
RustChan 1.5.0 retains the database structure introduced in 1.4.1.
Fresh installs create the current baseline directly. Existing structurally valid
1.4.1 databases are verified and stamped as schema version 1.5.0; recognized
older schema shapes use the existing compatibility repairs. Partial, unknown, or
corrupt schemas are rejected without deleting data. Historical chan_net_*
tables remain for database and backup compatibility, but ChanNet itself and the
--chan-net option have been removed. Remove that option from service commands
before upgrading. Obsolete ChanNet settings can remain in settings.toml and
are ignored. Back up your data before upgrading. Future structural schema
changes should add forward migrations tied to RustChan release versions.
Run:
ffmpeg -version
ffmpeg -encoders | rg 'libvpx-vp9|libopus'If one of those encoders is missing, RustChan will still run but some media features will be downgraded.
Check:
- outbound network connectivity
- whether the RustChan service user can write to
rustchan-data/runtime/tor/ - whether
tor_bootstrap_timeout_secsneeds to be raised
Also review:
rustchan-data/logs/rustchan.YYYY-MM-DD.log
Check:
- whether
[tls] enabled = trueis intentional - whether the configured HTTPS port is available
- whether your ACME or manual cert settings are correct if you use those modes
Make sure the RustChan user can write to:
rustchan-data/- the uploads directory
rustchan-data/runtime/
That usually means the Tor state directory was deleted, replaced, or not persisted:
rustchan-data/runtime/tor/state/
Back that directory up if the onion address matters.
Eligible native Linux installs use the same rustchan-cli executable for the application and update controller. Start with the existing account and data directory; see Software Updates for ownership requirements, verified backups, source adoption and exceptional reboot recovery. Protected installations and containers use their deployment's upgrade procedure.
RustChan handles JPEG/PNG/BMP/TIFF, static and animated WebP, GIF conversion, HEIC/HEIF still pictures, container metadata, and common audio waveforms in Rust. PDF previews cover a small bounded vector/text subset; compressed streams, embedded images/fonts and other unbudgeted features use the existing SVG preview while the original PDF remains available.
FFmpeg remains the video backend. It also preserves waveform support for audio variants the internal decoders do not cover, including AC-3, Speex, unsupported AAC profiles and Opus multistream/surround. See the media migration report for format coverage, resource limits and verification.
Live controls keep applying immediately. Other controls show their active, saved and next-start values. Saving never restarts RustChan; managed Linux and opted-in supervised containers expose Restart RustChan for pending settings. See settings restarts for the complete classification, supervisor setup, graceful shutdown and failure recovery.
Threads leave the active index when background maintenance finds more than the board's Max threads non-sticky active threads. It keeps the newest last-bump and thread-ID pairs; locked threads count, sticky active threads do not. New thread creation durably schedules maintenance, so overflow can be visible briefly. Startup and periodic reconciliation recover outstanding work.
Replies bump until the board's bump limit is reached. Sage replies count toward that limit without bumping. The bump limit stops bumps; it does not immediately archive the thread. There is no time-based thread expiry.
Archive overflow threads controls automatic archival, with the global
archive_before_prune safety net (default true) also enabling it. Hard deletion
of active overflow occurs only when both are off. Existing archives and their
navigation remain available even when automatic archival is disabled.
Max archived threads is a count cap (1–10,000), not permanent retention. Maintenance keeps the most recently bumped archived threads, breaking ties by ascending thread ID. Archived sticky threads are also subject to the cap; sticky is not an archive preservation setting. Lowering the cap permanently removes overflow threads. Invalid retention values are rejected rather than clamped; omitted retention fields preserve the saved limits.
Archival keeps the same /{board}/thread/{id} URL and media references. Archived
threads are read-only, including polls; users can browse their results, quoting,
spoilers and attachments. Board search includes archived posts. Board index,
catalog and unread history show active threads. Administrators can manually
archive or delete a thread using the existing authenticated, CSRF-protected
controls. There is no public unarchive/preservation control.
The archive and prune transitions use one SQLite write transaction. Pruned media is removed only when no other database references remain, using durable, restart-replayable file cleanup. Failed filesystem cleanup stays queued and logged. Archive policy is read when maintenance executes, not from stale job payloads. Changing the global TOML/environment safety net requires restart; board settings are live. Monitor background-job failures if maintenance is unable to acquire the database lock or complete cleanup.