-
Notifications
You must be signed in to change notification settings - Fork 0
Configuration Examples
Real-world configuration examples for common deployment scenarios.
This page provides complete, copy-paste-ready configuration examples for FileHunter. Each example targets a specific deployment scenario with a full TOML configuration and an explanation of the key design decisions. For a complete reference of all fields and their defaults, see the Configuration Reference.
The simplest possible FileHunter setup: a single catch-all location serving all files from one directory with no extension filtering. Every field not specified falls back to its default value.
[server]
bind = "0.0.0.0:8080" # listen on all interfaces, port 8080
[[locations]]
prefix = "/" # catch-all: matches every request path
[[locations.paths]]
root = "/data/files" # serve files from this directoryKey points:
- Only
bindand one location with one path are required to start FileHunter. -
modedefaults tosequential,max_file_sizedefaults to10MB, and all other server settings use their defaults. - No
extensionsfilter means every file type in/data/filesis eligible. - A request like
GET /reports/q4.pdfwill look for/data/files/reports/q4.pdf.
Separate URL prefixes for images, documents, and videos, each with appropriate extension filters to prevent serving the wrong content type from a shared storage backend.
[server]
bind = "0.0.0.0:8080"
max_file_size = "50MB" # global limit covers most content
# Image assets
[[locations]]
prefix = "/images"
mode = "sequential"
[[locations.paths]]
root = "/data/images"
extensions = ["jpg", "jpeg", "png", "gif", "webp", "svg"]
# Office and text documents
[[locations]]
prefix = "/docs"
mode = "sequential"
[[locations.paths]]
root = "/data/documents"
extensions = ["pdf", "docx", "xlsx", "pptx", "txt", "csv"]
# Video files
[[locations]]
prefix = "/videos"
mode = "sequential"
[[locations.paths]]
root = "/data/videos"
extensions = ["mp4", "mkv", "avi", "mov", "webm"]Key points:
- Each prefix routes to its own directory and only allows matching file types.
- Extension filters prevent accidental exposure of unrelated files (e.g., a
.envfile in the documents directory). - Extensions are specified without a leading dot:
"jpg"not".jpg". - Longest-prefix matching means
/images/photo.pnghits the/imageslocation, not a/catch-all.
When files are spread across multiple NFS mount points with unpredictable latency, concurrent mode probes all roots in parallel and returns the first successful result.
[server]
bind = "0.0.0.0:8080"
connection_timeout = 600 # longer timeout for slow NFS operations
[[locations]]
prefix = "/shared"
mode = "concurrent" # parallel I/O across all NFS mounts
[[locations.paths]]
root = "/mnt/nfs-server-a/files"
[[locations.paths]]
root = "/mnt/nfs-server-b/files"
[[locations.paths]]
root = "/mnt/nfs-server-c/files"Key points:
-
concurrentmode spawns parallel probes to all three NFS mounts simultaneously. - The first root to respond with a valid file wins; remaining probes are cancelled.
- This eliminates head-of-line blocking when one NFS server is slow or temporarily unreachable.
- Increasing
connection_timeoutto 600 seconds accommodates slow network storage without dropping clients.
Use latest_modified mode to always serve the newest version of a file when the same filename exists across production, staging, and archive directories.
[server]
bind = "0.0.0.0:8080"
[[locations]]
prefix = "/assets"
mode = "latest_modified" # always serve the newest version
[[locations.paths]]
root = "/data/production"
[[locations.paths]]
root = "/data/staging"
[[locations.paths]]
root = "/data/archive"Key points:
-
latest_modifiedmode checks ALL roots and compares each file's modification timestamp (mtime). - The file with the most recent
mtimeis returned, regardless of which root it lives in. - Ideal for staged deployments: push a new version to
/data/stagingand it is served immediately without touching production. - Unlike
concurrentmode, this always scans every root before responding (it needs to compare all candidates).
Apply different file size limits to different content types. Thumbnails get a strict cap, full-size images get more room, and videos are allowed to be very large.
[server]
bind = "0.0.0.0:8080"
max_file_size = "10MB" # global default
# Thumbnails: strict size limit
[[locations]]
prefix = "/thumbnails"
max_file_size = "1MB" # override: reject anything over 1MB
[[locations.paths]]
root = "/data/thumbnails"
extensions = ["jpg", "png", "webp"]
# Full-size images: generous limit
[[locations]]
prefix = "/images"
max_file_size = "20MB" # override: allow up to 20MB
[[locations.paths]]
root = "/data/images"
extensions = ["jpg", "jpeg", "png", "gif", "webp", "svg", "bmp"]
# Video files: large limit
[[locations]]
prefix = "/videos"
max_file_size = "500MB" # override: large video files
[[locations.paths]]
root = "/data/videos"
extensions = ["mp4", "mkv", "webm", "avi"]Key points:
- The per-location
max_file_sizeoverrides the global[server]default for that specific prefix. - Files exceeding the limit are silently skipped during search (as if they do not exist).
- Set
max_file_size = 0on a location to disable the size limit entirely for that prefix. - Size values accept human-readable strings like
"1MB","500MB","2GB", or raw integers in bytes like1048576.
Multiple paths under a single location can each carry their own extension filter. This lets you combine different content sources behind one URL prefix while controlling which file types are served from each directory.
[server]
bind = "0.0.0.0:8080"
[[locations]]
prefix = "/media"
mode = "sequential"
# First path: only web-safe image formats
[[locations.paths]]
root = "/data/web-images"
extensions = ["jpg", "png", "webp", "svg"]
# Second path: also allow legacy formats
[[locations.paths]]
root = "/data/legacy-images"
extensions = ["bmp", "tiff", "gif"]
# Third path: video content
[[locations.paths]]
root = "/data/video-clips"
extensions = ["mp4", "webm"]
# A separate location with no extension filter
[[locations]]
prefix = "/raw"
# No extensions specified — serve any file type
[[locations.paths]]
root = "/data/raw-assets"Key points:
- Extension filters are per-path, not per-location. Each
[[locations.paths]]entry can restrict its own allowed types. - Omitting the
extensionsfield entirely means all file types in that root are eligible. - In
sequentialmode, paths are probed in config order:/data/web-imagesfirst, then/data/legacy-images, then/data/video-clips. - This lets you prioritize certain directories or formats over others simply by ordering them in the config.
Enable CORS to allow a frontend application on a different domain to fetch files directly from FileHunter. This example restricts access to two specific origins and enables credentials for authenticated requests.
[server]
bind = "0.0.0.0:8080"
[server.cors]
enabled = true
allow_origins = ["https://app.example.com", "https://admin.example.com"]
allow_methods = ["GET", "HEAD", "OPTIONS"]
allow_headers = ["Authorization", "Content-Type"]
expose_headers = ["Content-Length", "Content-Type"]
max_age = 3600 # cache preflight for 1 hour
allow_credentials = true # allow cookies / Authorization headers
[[locations]]
prefix = "/"
[[locations.paths]]
root = "/data/files"Key points:
-
enabled = trueactivates CORS header injection on all responses. Without this, no CORS headers are added regardless of other settings. - Listing specific origins instead of
"*"is required whenallow_credentials = true(per the CORS specification). -
allow_headerscan be set to["*"]to accept any request header, or restricted to specific headers like["Authorization"]for tighter control. -
max_age = 3600tells browsers to cache the preflight response for 1 hour, reducing the number of OPTIONS requests. - Preflight (OPTIONS) requests are handled automatically by the CORS layer.
Protect a public-facing FileHunter instance from abuse by limiting each client IP to a sustained rate of 20 requests per second with a burst allowance of 50.
[server]
bind = "0.0.0.0:8080"
[server.rate_limit]
enabled = true
requests_per_second = 20 # sustained rate per IP
burst_size = 50 # allow short bursts
cleanup_interval = 300 # clean up expired entries every 5 minutes
[[locations]]
prefix = "/"
[[locations.paths]]
root = "/data/public"Key points:
-
enabled = trueactivates per-IP rate limiting. Without this, all requests are allowed without throttling. -
requests_per_secondsets the long-term sustained rate.burst_sizeallows temporary spikes above this rate (e.g., a browser opening multiple image requests simultaneously). - When a client exceeds the limit, they receive a
429 Too Many Requestsresponse with aRetry-Afterheader indicating how long to wait. -
cleanup_intervalcontrols how often the background task removes expired entries. Lower values free memory faster but consume slightly more CPU. - Rate limiting is checked before all other request processing (method checks, file search), so throttled requests have minimal server impact.
A comprehensive configuration with every field explicitly set and annotated. Suitable as a starting template for production deployments.
[server]
bind = "0.0.0.0:8080" # listen address
keepalive = true # HTTP/1.1 keep-alive
connection_timeout = 300 # seconds, 0 = unlimited
max_header_size = "16KB" # request line + headers limit
max_headers = 100 # max number of request headers
max_body_size = "1MB" # Content-Length limit (413 if exceeded)
http2_max_streams = 256 # HTTP/2 concurrent streams per connection
max_file_size = "50MB" # global file size limit (0 = unlimited)
stream_buffer_size = "128KB" # response streaming chunk size
# CORS — allow CDN consumers
[server.cors]
enabled = true
allow_origins = ["*"] # public CDN, allow any origin
allow_methods = ["GET", "HEAD"]
max_age = 86400
# Rate limiting — protect against abuse
[server.rate_limit]
enabled = true
requests_per_second = 50
burst_size = 100
cleanup_interval = 300
# Compression — compress text-based content for public clients
[server.compression]
enabled = true
algorithms = ["gzip", "br"]
min_size = "1KB"
# High-priority image serving
[[locations]]
prefix = "/cdn/images"
mode = "sequential" # deterministic, config order priority
max_file_size = "20MB" # override: images up to 20MB
[[locations.paths]]
root = "/data/primary/images"
extensions = ["jpg", "jpeg", "png", "gif", "webp", "svg", "avif"]
[[locations.paths]]
root = "/data/backup/images"
extensions = ["jpg", "jpeg", "png", "gif", "webp", "svg", "avif"]
# Document serving from multiple sources
[[locations]]
prefix = "/cdn/docs"
mode = "concurrent" # parallel search across storage
max_file_size = "100MB" # allow large PDFs
[[locations.paths]]
root = "/data/primary/documents"
extensions = ["pdf", "docx", "xlsx", "pptx"]
[[locations.paths]]
root = "/data/secondary/documents"
extensions = ["pdf", "docx", "xlsx", "pptx"]
# Video with latest-modified for staged deployments
[[locations]]
prefix = "/cdn/videos"
mode = "latest_modified" # always serve newest version
max_file_size = "2GB" # large video files
[[locations.paths]]
root = "/data/production/videos"
extensions = ["mp4", "mkv", "webm"]
[[locations.paths]]
root = "/data/staging/videos"
extensions = ["mp4", "mkv", "webm"]
# Catch-all for everything else
[[locations]]
prefix = "/"
mode = "sequential"
[[locations.paths]]
root = "/data/general"Key points:
- All
[server]fields are shown explicitly so nothing is left to implicit defaults. - Three different search modes are used across locations, each chosen for its use case:
sequentialfor deterministic priority,concurrentfor parallel multi-source search, andlatest_modifiedfor versioned content. - Per-location
max_file_sizeoverrides the global50MBdefault where appropriate. - The
"/"catch-all at the end handles any request that does not match a more specific prefix. Longest-prefix matching ensures/cdn/images/photo.jpgalways hits/cdn/images, never/. -
stream_buffer_size = "128KB"increases the default streaming chunk size from 64KB for better throughput on large files.
A complete Docker deployment with a TOML configuration and matching docker-compose.yml. Container paths in the TOML must match the volume mount targets.
config.toml:
[server]
bind = "0.0.0.0:8080" # bind inside the container
max_file_size = "50MB"
stream_buffer_size = "128KB"
[[locations]]
prefix = "/images"
mode = "sequential"
[[locations.paths]]
root = "/data/images" # must match volume mount target
extensions = ["jpg", "jpeg", "png", "gif", "webp", "svg"]
[[locations]]
prefix = "/docs"
mode = "concurrent"
[[locations.paths]]
root = "/data/documents" # must match volume mount target
extensions = ["pdf", "docx", "xlsx", "txt"]
[[locations]]
prefix = "/"
[[locations.paths]]
root = "/data/general" # must match volume mount targetdocker-compose.yml:
services:
filehunter:
image: filehunter:latest
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:8080"
volumes:
- ./config.toml:/etc/filehunter/config.toml:ro
- /data/images:/data/images:ro
- /data/documents:/data/documents:ro
- /data/general:/data/general:ro
restart: unless-stoppedKey points:
- The config file is mounted read-only (
:ro) at/etc/filehunter/config.toml, the default config path inside the container. - Each data directory on the host is mapped into the container at the same path referenced in the TOML
rootfields. - All volumes use
:ro(read-only) since FileHunter only reads files, never writes. -
restart: unless-stoppedensures the service recovers from crashes or host reboots. - To use a pre-built image, replace the
buildsection with justimage: your-registry/filehunter:tag.
Enable response compression for a standalone public-facing deployment where FileHunter serves text-based files (logs, CSVs, JSON, XML) directly to clients over the internet without a reverse proxy.
[server]
bind = "0.0.0.0:8080"
[server.compression]
enabled = true
algorithms = ["gzip", "br", "zstd"] # broad algorithm support
min_size = "1KB" # skip tiny files
[[locations]]
prefix = "/data"
mode = "sequential"
[[locations.paths]]
root = "/data/exports"
extensions = ["csv", "json", "xml", "txt", "log"]Key points:
- Compression is disabled by default — you must explicitly set
enabled = trueto activate it. - Best suited for text-based content served directly to public clients. Images, videos, and archives are already compressed and will be automatically skipped.
-
min_size = "1KB"avoids the overhead of compressing very small files where the savings are negligible. - If FileHunter runs behind a reverse proxy (Nginx, Caddy, etc.) that already handles compression, keep compression disabled to avoid double-encoding and unnecessary CPU usage.
Protect an internal file server with HTTP Basic Authentication. Health check endpoints remain accessible without credentials for orchestration tools like Kubernetes.
[server]
bind = "0.0.0.0:8080"
[server.basic_auth]
enabled = true
username = "admin"
password = "s3cr3t"
realm = "internal-files" # shown in browser login dialog
[[locations]]
prefix = "/"
[[locations.paths]]
root = "/data/internal"Key points:
- Basic Auth is disabled by default — you must explicitly set
enabled = trueto activate it. - Health check endpoints (
/healthand/ready) are exempt from authentication, ensuring Kubernetes probes and load balancer health checks continue to work. - Password comparison uses constant-time XOR-fold to prevent timing side-channel attacks.
- The
realmfield controls the text shown in the browser's login dialog. Defaults to"filehunter"if omitted. - Both
usernameandpasswordmust be non-empty when enabled — this is validated at startup. - Authentication is checked before rate limiting, so unauthenticated requests do not consume rate limit quota.
- For production use, consider combining Basic Auth with TLS (e.g., via a reverse proxy) to protect credentials in transit.
MIT License · GitHub · Built with Rust 🦀
FileHunter Wiki
🇬🇧 English
- Home
- Architecture
- Configuration Reference
- Configuration Examples
- Search Modes
- Routing
- Deployment
- Security
- Testing
🇨🇳 中文