Home Docs Configuration

Configuration

Soli Proxy uses two files: proxy.conf for routing rules and config.toml for server settings. Both support hot reloading without downtime.

1 Routing Rules — proxy.conf

Each line maps a source pattern to one or more backend targets using the -> arrow syntax. Rules are evaluated top-to-bottom; the first match wins.

Domain-Only Rules

Match requests by their Host header. Any path on that domain is forwarded to the target.

proxy.conf
# All traffic to example.com goes to backend on port 8080
example.com -> http://backend:8080

# Multiple domains, each with its own backend
api.example.com   -> http://api-server:3000
admin.example.com -> http://admin-panel:4000

Domain + Path Prefix

Combine a domain with a path prefix to route specific URL paths on a domain to a different backend.

proxy.conf
# Only /api/* paths on app.example.com go to the API service
app.example.com/api/* -> http://api:8082

# Everything else on app.example.com goes to the frontend
app.example.com -> http://frontend:3000

Path-Only Rules (Any Domain)

Match by URL path regardless of the host. Paths ending in /* match any subpath (prefix match). Paths without a wildcard require an exact match.

proxy.conf
# Prefix match: /api/ and all subpaths
/api/* -> http://api:8090

# Exact match: only /health (not /health/check)
/health -> http://monitoring:9090

# WebSocket route
/ws -> ws://realtime:8888

Regex Rules

Prefix the source with ~ to use a regular expression. The regex is matched against the full request path.

proxy.conf
# Match versioned API paths: /v1/..., /v2/..., etc.
~^/v[0-9]+/.*$ -> http://versioned:8091

# Match UUID-based resource paths
~^/resources/[0-9a-f]{8}-[0-9a-f]{4}-.*$ -> http://resources:8092

HTML URL Rewriting

When using path prefix rules (like /api/* -> http://api:8090), the proxy automatically rewrites URLs in HTML responses to include the prefix. This ensures assets loaded by the browser point to the correct path.

proxy.conf
# Route /api/* to backend (prefix stripped from request)
/api/* -> http://api:8090

With the rule above, when the backend returns HTML like:

<link rel="stylesheet" href="/style.css">
<script src="/app.js"></script>
<form action="/submit">

The proxy rewrites it to:

<link rel="stylesheet" href="/api/style.css">
<script src="/api/app.js"></script>
<form action="/api/submit">

The rewriting applies to href, src, and action attributes in HTML responses. It also handles gzip/deflate compression transparently.

Multi-Backend (Load Balancing)

Specify multiple backends separated by commas. Use backslash \ at the end of a line to continue on the next line. Traffic is distributed across all targets.

proxy.conf
# Two backends on a single line
/app/* -> http://b1:8080, http://b2:8080

# Line continuation with backslash for readability
/api/* -> http://backend1:8080, \
          http://backend2:8080, \
          http://backend3:8080

Per-Route Lua Scripts

Append @script:filename.lua to attach Lua scripts to specific routes. Multiple scripts are comma-separated.

proxy.conf
# Single script on a route
/admin/* -> http://admin:4000 @script:auth.lua

# Multiple scripts on a route
/api/* -> http://api:3000 @script:auth.lua,rate_limit.lua

# Works with multi-backend and line continuation
/api/* -> http://a:8080, \
          http://b:8080 @script:auth.lua

Global Scripts

The [global] directive applies Lua scripts to every request, regardless of the matched route.

proxy.conf
# These scripts run on every request
[global] @script:cors.lua,logging.lua

# Routes follow as normal
/api/* -> http://api:3000 @script:auth.lua
default -> http://localhost:8080

Default Fallback

The default rule catches any request that did not match a previous rule. Place it at the end of your config.

proxy.conf
# Catch-all fallback (must be last)
default -> http://localhost:8080

Complete Example

proxy.conf
# Global middleware
[global] @script:cors.lua,logging.lua

# Domain-based routing
api.example.com    -> http://api:3000 @script:auth.lua
admin.example.com  -> http://admin:4000 @script:auth.lua
cdn.example.com    -> http://static:8080

# Domain + path prefix
app.example.com/api/* -> http://api:3000
app.example.com       -> http://frontend:8080

# Path-only with load balancing
/api/* -> http://backend1:8080, \
          http://backend2:8080, \
          http://backend3:8080

# Regex for versioned endpoints
~^/v[0-9]+/.*$ -> http://versioned:8091

# WebSocket
/ws -> ws://realtime:8888

# Catch-all fallback
default -> http://localhost:8080

Matching Order

Rules are evaluated top-to-bottom. The first rule that matches the incoming request wins. Place more specific rules (domain + path, regex) before broader ones (domain-only, default). The default rule should always be last.

2 Server Settings — config.toml

The config.toml file controls server behavior, TLS, admin API, scripting, and more. All sections are optional with sensible defaults.

[server]

Core server binding and threading options.

config.toml
[server]
bind = "0.0.0.0:80"            # HTTP listen address
https_port = 443               # HTTPS listen port
worker_threads = "auto"        # "auto" = num CPUs, or a number
Key Type Default Description
bindstring"0.0.0.0:80"Address and port for HTTP
https_portinteger443Port for HTTPS/TLS listener
worker_threadsstring"auto"Tokio worker thread count

[tls]

TLS mode and certificate storage.

config.toml
[tls]
mode = "auto"                  # "auto" = self-signed dev certs
                                # "letsencrypt" = ACME production certs
cache_dir = "./certs"          # Directory to store certificates
Key Type Default Description
modestring"auto""auto" for self-signed, "letsencrypt" for ACME
cache_dirstring"./certs"Certificate storage directory

[letsencrypt]

ACME / Let's Encrypt settings. Only used when tls.mode = "letsencrypt".

config.toml
[letsencrypt]
staging = false               # true = use staging ACME server (for testing)
email = "[email protected]"   # Contact email for certificate notifications
terms_agreed = true          # You must agree to the ACME ToS
Key Type Default Description
stagingboolfalseUse Let's Encrypt staging for testing
emailstring--Contact email for ACME notifications
terms_agreedbool--Accept the ACME Terms of Service

[admin]

REST Admin API for runtime management. See the Admin API docs for endpoints.

config.toml
[admin]
enabled = true                # Enable the admin API
bind = "0.0.0.0:9090"         # Admin API listen address
api_key = "your-secret-key"   # Optional: require X-API-Key header
Key Type Default Description
enabledboolfalseEnable or disable the admin API
bindstring"127.0.0.1:9090"Address and port for admin API
api_keystring?noneIf set, all requests require X-API-Key header

[circuit_breaker]

Tracks backend health and stops sending traffic to failing backends. When all targets for a route are open, returns 503 Service Unavailable.

config.toml
[circuit_breaker]
failure_threshold = 5        # Consecutive failures before opening
recovery_timeout_secs = 30   # Seconds before probing an open backend
success_threshold = 2        # Successful probes needed to close
failure_status_codes = [502, 503, 504]  # HTTP codes that count as failures
Key Type Default Description
failure_thresholdinteger5Failures before breaker opens
recovery_timeout_secsinteger30Seconds to wait before half-open probe
success_thresholdinteger2Successes needed to close breaker
failure_status_codesarray[502, 503, 504]HTTP status codes treated as failures

[scripting]

Lua 5.4 scripting engine. See the Scripting docs for hook details.

config.toml
[scripting]
enabled = true                # Enable Lua scripting engine
scripts_dir = "./scripts/lua"  # Directory containing Lua scripts
hook_timeout_ms = 10         # Max execution time per hook (ms)
Key Type Default Description
enabledboolfalseEnable or disable scripting engine
scripts_dirstring"./scripts/lua"Path to Lua script files
hook_timeout_msinteger10Max execution time per hook call

[logging]

Structured logging configuration.

config.toml
[logging]
level = "info"                # trace, debug, info, warn, error
format = "json"               # "json" or "pretty"
output = "stdout"             # "stdout" or "file:/var/log/soli-proxy.log"
Key Type Default Description
levelstring"info"Log verbosity level
formatstring"json"Output format (json or pretty)
outputstring"stdout"Output target (stdout or file path)

[limits]

Connection and request limits.

config.toml
[limits]
max_connections = 10000      # Maximum concurrent connections
max_request_size = "10MB"    # Maximum request body size
keep_alive_timeout = 30     # Keep-alive timeout in seconds
request_timeout = 60        # Total request timeout in seconds
Key Type Default Description
max_connectionsinteger10000Max concurrent connections
max_request_sizestring"10MB"Max request body size
keep_alive_timeoutinteger30Keep-alive timeout (seconds)
request_timeoutinteger60Total request timeout (seconds)

[rate_limiting]

Global rate limiting with token bucket strategy.

config.toml
[rate_limiting]
enabled = true                # Enable rate limiting
strategy = "token_bucket"     # Rate limiting algorithm
requests_per_second = 1000   # Sustained request rate
burst_size = 2000            # Maximum burst above sustained rate
Key Type Default Description
enabledbooltrueEnable or disable rate limiting
strategystring"token_bucket"Rate limiting algorithm
requests_per_secondinteger1000Sustained request rate per second
burst_sizeinteger2000Max burst above sustained rate

Complete config.toml Example

config.toml
[server]
bind = "0.0.0.0:80"
https_port = 443
worker_threads = "auto"

[tls]
mode = "letsencrypt"
cache_dir = "./certs"

[letsencrypt]
staging = false
email = "[email protected]"
terms_agreed = true

[admin]
enabled = true
bind = "127.0.0.1:9090"
api_key = "your-secret-key"

[circuit_breaker]
failure_threshold = 5
recovery_timeout_secs = 30
success_threshold = 2
failure_status_codes = [502, 503, 504]

[scripting]
enabled = true
scripts_dir = "./scripts/lua"
hook_timeout_ms = 10

[logging]
level = "info"
format = "json"
output = "stdout"

[limits]
max_connections = 10000
max_request_size = "10MB"
keep_alive_timeout = 30
request_timeout = 60

[rate_limiting]
enabled = true
strategy = "token_bucket"
requests_per_second = 1000
burst_size = 2000

3 Hot Reloading

Configuration changes are applied without restarting the proxy or dropping active connections. There are three ways to trigger a reload.

SIGUSR1 Signal

Send a Unix signal to the running process for immediate reload.

kill -USR1 $(pidof soli-proxy)

Admin API

HTTP endpoint for programmatic reload from scripts or CI/CD.

POST http://localhost:9090/reload

File Watcher

Automatic detection when proxy.conf is modified on disk.

Automatic — no action needed

SIGUSR1 Signal

Send the SIGUSR1 signal to the Soli Proxy process. This is the simplest reload method for manual operations.

bash
# Find and signal the process
kill -USR1 $(pidof soli-proxy)

# Or if using systemd
systemctl kill --signal=USR1 soli-proxy

Admin API Reload

Call the Admin API reload endpoint. Requires the admin API to be enabled in config.toml.

bash
# Without API key
curl -X POST http://localhost:9090/reload

# With API key authentication
curl -X POST http://localhost:9090/reload \
  -H "X-API-Key: your-secret-key"

Automatic File Watcher

Soli Proxy watches the proxy.conf file for changes using OS-level file notifications (inotify on Linux, FSEvents on macOS). When a change is detected, configuration is reloaded automatically.

Smart Suppression

When routes are modified via the Admin API, the file watcher suppresses the next filesystem event to avoid a double reload. The Admin API writes to proxy.conf and swaps the in-memory config atomically.