A simple tool to serve static files from a directory
cargo install --git https://github.com/Lurk/serve serveserve [OPTIONS] [COMMAND]
-c, --config <CONFIG> Path to the configuration file.
Command line arguments override the configuration file.
If configuration file does not exist, it will be created with the current
command line arguments.
Supported format is TOML.
--path <PATH> Path to the directory to serve. Defaults to the current directory
-p, --port <PORT> Port to listen on [default: 3000]
-a, --addr <ADDR> Address to listen on [default: 127.0.0.1]
--disable-compression Compression layer is enabled by default
--not-found <NOT_FOUND> Path to 404 page. By default, 404 is empty
--ok Override with 200 OK. Useful for SPA. Requires --not-found
--proxy <PROXY> Proxy route in the format /path=http://host:port.
Can be specified multiple times. HTTPS upstreams are not supported.
-v, --verbose... Increase logging verbosity
-q, --quiet... Decrease logging verbosity
--log-path <LOG_PATH> Path to the directory where logs will be stored.
If not specified, logs will be printed to stdout.
If specified, logs will be written to the file: log_path/serve.YYYY-MM-DD.log
and rotated daily.
If the directory does not exist, it will be created.
--log-max-files <LOG_MAX_FILES> Maximum number of log files to keep [default: 7]
-h, --help Print help
-V, --version Print version
tls Adds TLS support
init Create a default configuration file
install Install serve as a system service
uninstall Remove the serve system service
validate Validate serve configuration
restart Restart the system service (e.g. after binary update)
reload Reload the service configuration
status Show service status, version, and config path
help Print this message or the help of the given subcommand(s)
Adds TLS support
Usage: serve [OPTIONS] [PATH] tls --cert <CERT> --key <KEY> --redirect-http
Options:
-c, --cert <CERT> path to the certificate file
-k, --key <KEY> path to the private key file
--redirect-http Redirect HTTP to HTTPS. Works only if 443 port is used
Reverse proxy requests matching a path prefix to an upstream HTTP server.
serve --proxy /api=http://localhost:8080
Multiple proxies can be specified:
serve --proxy /api=http://localhost:8080 --proxy /ws=http://localhost:9090
By default, the matching prefix is stripped before forwarding (e.g., /api/users is forwarded as /users).
To disable prefix stripping, use the config file with strip_prefix = false.
The proxy sets x-forwarded-for and x-forwarded-proto headers on forwarded requests.
HTTPS upstreams are not supported.
serve ships with an opt-in HTTP analytics subsystem that records request
counts and bytes served, broken down by URL path and HTTP status class. A
small password-gated HTML dashboard lives at /__stats__.
Stats are off by default. To enable, add a [stats] table to your
serve.toml:
[stats]
db_path = "/var/lib/serve/stats.db" # SQLite file; parent dir must be writable
session_ttl_days = 30 # default
# secure_cookies = true # force the Secure attribute on auth cookies.
# Omit to follow the TLS subcommand; set to
# true when TLS terminates upstream (proxy).
# url_prefix = "/admin/stats" # default: "/__stats__". Must start with /,
# must not end with /, and must not contain
# "//" or "..".On the very first start with [stats] configured, serve prints a setup
token to stderr only — the token is intentionally never written through
tracing so --log-path won't persist it to a log file on disk:
stats setup token: 8f4c…
Visit https://your-host/__stats__/ in a browser. You will be redirected to
/__stats__/setup, where you enter the token plus a password of your choice.
The password is hashed with argon2id and stored in the SQLite file; the token
is consumed and cannot be reused.
After setup, log in at /__stats__/login to see:
- Time-series of requests/bytes broken down by 2xx/3xx/4xx/5xx
- Top 30 paths (toggle by requests or bytes)
- Windows: 1 day, 7 days, 30 days, 12 months
The stats dashboard can also show a per-country breakdown of requests and
bytes. This is off by default and requires a GeoIP country database in
MaxMind's .mmdb format. With no database configured, the country panel simply
does not appear and nothing else changes.
Privacy: client IP addresses are resolved to a 2-letter country code in memory, and only the code is stored. Raw IP addresses are never written to the stats database or the logs.
Any MaxMind-format country database works. Two free options:
- MaxMind GeoLite2 (free, requires an account): sign up at
https://www.maxmind.com/en/geolite2/signup, then download
GeoLite2-Country.mmdb. - DB-IP IP-to-Country Lite (free, no account, CC-BY): download the
.mmdbfrom https://db-ip.com/db/download/ip-to-country-lite.
Add the path to the [stats] table in serve.toml:
[stats]
db_path = "/var/lib/serve/stats.db"
# Path to the GeoIP country database. Its presence enables the country panel.
geoip_db_path = "/var/lib/serve/GeoLite2-Country.mmdb"
# Set true ONLY when serve runs behind exactly one trusted proxy/CDN that sets
# X-Forwarded-For. serve then geolocates the right-most entry — the address that
# proxy observed, which a client cannot spoof. When serve is directly exposed,
# leave this false; otherwise clients can forge their country with the header.
# With more than one proxy hop in front, the right-most entry is an inner proxy
# rather than the client, so leave this false in that case too.
trust_forwarded_for = falseIf the configured file is missing or unreadable, serve logs a warning and
keeps running with country stats disabled — it will not fail to start.
The database is read once at startup. To refresh it, replace the .mmdb file
and restart serve. Country data drifts slowly, so an occasional update is
enough.
Minute buckets are kept for 25 hours, hourly buckets for 32 days, daily buckets for 400 days. Older rows are pruned automatically.
Stop serve, delete the SQLite file at db_path, and restart. A new setup
token will be printed.
| Symptom | Likely cause |
|---|---|
/__stats__ returns 404 |
[stats] not in config; section missing. |
| Setup form keeps rejecting token | Token only printed on first start with empty DB. Delete DB to regenerate. |
| Dashboard shows zero traffic | Writer flushes every 10s; very recent requests may not yet be visible. |
serve refuses to start with "stats db_path parent missing" |
Create the parent directory or fix the path. |
# Configuration for Serve (https://github.com/Lurk/serve)
path = "/var/www"
port = 3000
addr = "127.0.0.1"
disable_compression = false
ok = false
log_level = "trace" # off, error, warn, info, debug, trace
log_path = "/var/log/serve"
log_max_files = 7
[[proxy]]
path = "/api"
upstream = "http://localhost:8080"
strip_prefix = true
[subcommand.Tls]
cert = "/var/certs/localhost.crt"
key = "/var/certs/localhost.key"
redirect_http = trueServe can install itself as a system service on Linux (systemd) and macOS (launchd).
serve initCreates a default config at the platform-specific path (/etc/serve/serve.toml on Linux, /Library/Application Support/serve/serve.toml on macOS). To specify a custom path:
serve init --path /path/to/serve.tomlsudo serve install --config /etc/serve/serve.tomlThis validates the config, copies the binary to /usr/local/bin/serve, creates log and data directories, and registers the system service. On Linux it also creates a dedicated serve user.
serve validateAuto-detects the config path from the installed service. To validate a specific file:
serve validate --config /path/to/serve.tomlsudo serve restartsudo serve reloadValidates the config before restarting. Aborts if the config is invalid.
serve statusShows running status, installed version, and config path.
sudo serve uninstallRemoves the binary, service definition, and service user. Config, log, and data directories are preserved.
If binding to ports 443/80, ensure they are open in both the OS firewall and any cloud firewall (e.g., Hetzner Cloud Firewall).
openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 \
-nodes -keyout localhost.key -out localhost.crt -subj "/CN=localhost" \
-addext "subjectAltName=IP:127.0.0.1"serve MSRV is 1.89