Configuration (V3)

Quick start

Create odd-box.toml (or Config.toml) and run:

./odd-box              # loads odd-box.toml or Config.toml in the cwd
./odd-box "/path/to/config.toml"   # explicit path

Most settings can be reloaded at runtime through the admin API.


Placeholder variables

VariableResolves toDefault when unsetWhat it’s for
$root_dirValue of root_dircurrent working directoryUse in any path or argument to stay portable.
$cfg_dirDirectory of the loaded config fileHandy for relative paths that follow the config.
$portThe port chosen for the current hosted_processnext free ≥ port_range_startLets you inject the runtime port into args/env without hard‑coding.

Tip (VS Code): Install Even Better TOML and keep the #:schema … tag for instant validation & IntelliSense.


1 — Global settings

What each field does

FieldMeaningDefault
versionMust be "V3" so odd‑box knows which schema to parse.
root_dirBase for $root_dir; relative paths are resolved from here.current working dir
ipAddress the HTTP/HTTPS listeners bind to.127.0.0.1
http_port / tls_portPublic ports for HTTP and HTTPS listeners.8080 / 4343
alpnOffer HTTP/2 over TLS via ALPN; disable only for exotic setups.true
port_range_startFirst port odd‑box tries for hosted_process auto‑ports.4200
default_log_formatLine layout for process logs (standard or dotnet).standard
log_levelOdd‑box’s own log verbosity.Info
auto_startWhether to start every hosted process at launch unless they override.true
use_loopback_ip_for_procsAlways proxy to 127.0.0.1 instead of the incoming host name (avoids IPv6/SNI weirdness).true
env_varsKey–value pairs injected into every hosted process.[]
lets_encrypt_account_emailEnables Let’s Encrypt support; this email is sent to ACME.unset
odd_box_url / odd_box_passwordCustom hostname + password for the admin UI/API; if unset, UI binds to localhost and is unsecured.unset

Example block

#:schema https://raw.githubusercontent.com/OlofBlomqvist/odd-box/main/odd-box-schema-v3.1.json
version                   = "V3"
root_dir                  = "/srv/odd-box"
ip                        = "0.0.0.0"
http_port                 = 8080
tls_port                  = 4343
alpn                      = true
port_range_start          = 4200
default_log_format        = "standard"
log_level                 = "Info"
auto_start                = true
use_loopback_ip_for_procs = true
env_vars = [
  { key = "GLOBAL_KEY", value = "value" }
]
lets_encrypt_account_email = "[email protected]"
odd_box_url                = "admin.example.com"
odd_box_password           = "s3cr3t"

2 — Reverse‑proxy back‑ends ([[remote_target]])

Field meanings

FieldWhat it controlsDefault
host_nameIncoming host (SNI / Host header) odd‑box listens for. Required.
capture_subdomainsIf true, *.host_name is also routed here.false
forward_subdomainsIf true, the matching subdomain is kept when rewriting Host header (foo.api.example.comfoo.backend).false
terminate_tlsTerminate HTTPS at odd‑box, forward HTTP to the back‑end.false
terminate_httpForce layer‑7 proxy even for plain HTTP (can enable URL rewriting).false
enable_lets_encryptIssue real certs for host_name via Let’s Encrypt. Requires lets_encrypt_account_email.false
keep_original_host_headerForwards inbound Host header unchanged instead of using the back‑end’s address.false
backendsArray of one or more servers. Each needs address + port.

Back‑end object keys:

KeyMeaningDefault
addressIP / FQDN of the target server.
portTCP port on the target.
httpsIf true, use TLS to the back‑end.false
hintsList of H1H2H2CH2CPKH3 to steer protocol negotiation.[]

Example

[[remote_target]]
host_name = "api.example.com"
capture_subdomains        = false
forward_subdomains        = true
terminate_tls             = true
enable_lets_encrypt       = true
keep_original_host_header = true

backends = [
  { address = "10.0.0.5", port = 443, https = true, hints = ["H2","H1"] },
  { address = "10.0.0.6", port = 8443, https = true }
]

3 — Hosted processes ([[hosted_process]])

Field meanings

FieldWhat it controlsDefault
host_nameIncoming host odd‑box proxies to this process. Required.
dirWorking directory for the process.current working dir
binExecutable or script to run. Required.
argsCommand‑line arguments array.[]
env_varsExtra env vars (merged with global env_vars).[]
log_formatOverrides default_log_format.inherited
log_levelOverrides global log_level.inherited
auto_startStart with odd‑box? Overrides global auto_start.inherited (true)
exclude_from_start_allIf true, start_all CLI/API skips this process.false
portFixed port; if unset odd‑box auto‑assigns.auto‑assign
httpsIf true, the process itself speaks HTTPS.false
capture_subdomainsHandle *.host_name.false
terminate_httpTerminates HTTP connections rather than directly forwarding the data stream.false
terminate_tlsTerminates TLS connections rather than directly forwarding the data stream.false
forward_subdomainsPreserve subdomain when rewriting Host.false
enable_lets_encryptIssue certs for this process’s host.false
hintsProtocol hints exactly like in backends.[]

Example

[[hosted_process]]
host_name              = "app.local"
dir                    = "$root_dir/apps/myapp"
bin                    = "./start-server"
args                   = ["--config", "$cfg_dir/app.toml"]
env_vars               = [
  { key = "APP_ENV", value = "production" }
]
log_format             = "dotnet"
log_level              = "Debug"
port                   = 3000
https                  = true
auto_start             = true
exclude_from_start_all = false
capture_subdomains     = false
forward_subdomains     = false
terminate_tls          = false
terminate_http         = false
enable_lets_encrypt    = false
hints                  = ["H1","H2"]

4 — Static file servers ([[dir_server]])

Field meanings

FieldWhat it controlsDefault
host_nameHostname odd‑box listens for. Required.
dirDirectory on disk to serve. Required.
capture_subdomainsServe *.host_name.false
enable_directory_browsingList files if no index.html/index.md.false
render_markdownConvert .md ➜ HTML automatically.false
redirect_to_httpsRespond with 301 to HTTPS listener.false
enable_lets_encryptIssue certs for this site.false
cache_control_max_age_in_secondsSets the cache-control header max-age (public, max-age=, immutable)no cache-control header

Example

[[dir_server]]
host_name                 = "static.example.com"
dir                       = "/var/www/public"
capture_subdomains        = true
enable_directory_browsing = true
render_markdown           = true
redirect_to_https         = true
enable_lets_encrypt       = true
cache_control_max_age_in_seconds = 60