Home Docs Security

TLS & Security

Automatic certificates, circuit breakers, and observability

1 TLS Modes

Soli Proxy supports two TLS modes. Switch between them with a single configuration line.

Auto Mode

Default

Generates a self-signed certificate for localhost on startup. Perfect for local development -- HTTPS works immediately with no external dependencies.

config.toml
# Self-signed cert for localhost
[tls]
mode = "auto"

Let's Encrypt Mode

Production

Automatic ACME HTTP-01 challenge validation for all configured domains. Certificates are issued, stored, and renewed without any manual intervention.

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

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

2 Certificate Auto-Provisioning

The entire certificate lifecycle is fully automated -- from domain discovery to renewal.

1

Domain Discovery

Domains are collected from two sources: hostnames in proxy.conf routing rules and domain fields in sites/*/app.infos files. All unique domains are queued for certificate provisioning.

2

ACME Account

An ACME account is created on first run and persisted to certs/account_credentials.json. On subsequent starts, the existing account is restored automatically.

3

HTTP-01 Challenge Validation

The proxy intercepts requests to /.well-known/acme-challenge/ and serves the ACME token response. This happens transparently before any backend routing.

4

Certificate Issuance & SNI Routing

Once validated, the certificate and private key are stored per-domain. The TLS acceptor uses SNI (Server Name Indication) to serve the correct certificate for each incoming connection.

5

Auto-Renewal

A background task checks every 12 hours whether any certificate is within 30 days of expiry. Certificates nearing expiry are renewed automatically with zero downtime.

certs/ directory layout
certs/
  account_credentials.json   # ACME account keypair
  example.com/
    cert.pem                  # Full certificate chain
    key.pem                   # Private key
  api.example.com/
    cert.pem
    key.pem
  myapp.example.com/
    cert.pem
    key.pem

3 Circuit Breaker

Per-backend circuit breakers track health and automatically stop routing traffic to failing backends.

Closed

Healthy

Traffic flows normally. Failures are counted. When consecutive failures reach failure_threshold, the breaker opens.

Open

Unhealthy

All requests to this backend are rejected immediately (503). After recovery_timeout_secs, the breaker moves to half-open.

Half-Open

Probing

A single probe request is allowed through. If success_threshold consecutive successes occur, the breaker closes. Any failure re-opens it.

State Transitions

Closed
failures >= threshold
Open
timeout elapsed
Half-Open
successes >= threshold
Closed
config.toml
# Circuit Breaker Configuration
# Tracks backend health and stops sending traffic to failing backends.
# When all targets for a route are open, returns 503 Service Unavailable.
[circuit_breaker]
failure_threshold = 5               # consecutive failures before opening
recovery_timeout_secs = 30          # seconds before probing an open backend
success_threshold = 2               # successful probes to close breaker
failure_status_codes = [502, 503, 504]  # HTTP codes that count as failures

Multi-Target Load Balancing

When a route has multiple targets, Soli Proxy supports three load balancing strategies. Use @lb:round-robin, @lb:weighted, or @lb:failover to specify the strategy. Default is round-robin.

proxy.conf
# Round-robin load balancing (default)
api.example.com -> http://backend-1:3000, http://backend-2:3000

# Weighted load balancing
example.com/api/* -> http://api-primary:8080, http://api-secondary:8080  # @lb:weighted

# Failover: use second target only if first fails
example.com/backup/* -> http://primary:8080, http://backup:8080  # @lb:failover

Admin API Endpoints

GET
/api/v1/circuit-breaker

Returns the current state of every tracked backend: state (closed/open/half_open), consecutive failures, and consecutive successes.

POST
/api/v1/circuit-breaker/reset

Resets all circuit breakers to the closed (healthy) state. Useful after deploying a fix to a failing backend.

GET /api/v1/circuit-breaker response
{
  "http://backend-1:3000": {
    "state": "closed",
    "consecutive_failures": 0,
    "consecutive_successes": 12
  },
  "http://backend-2:3000": {
    "state": "open",
    "consecutive_failures": 5,
    "consecutive_successes": 0
  }
}

4 Prometheus Metrics

Soli Proxy exposes a built-in /metrics endpoint in Prometheus text format, ready for scraping by Prometheus, Grafana Agent, or any compatible collector.

config.toml
[metrics]
enabled = true
endpoint = "/metrics"

Sample Output

curl http://localhost/metrics
# HELP proxy_requests_total Total number of HTTP requests
# TYPE proxy_requests_total counter
proxy_requests_total 48291

# HELP proxy_requests_in_flight Number of requests currently being processed
# TYPE proxy_requests_in_flight gauge
proxy_requests_in_flight 12

# HELP proxy_bytes_received Total bytes received from clients
# TYPE proxy_bytes_received counter
proxy_bytes_received 2847103

# HELP proxy_bytes_sent Total bytes sent to clients
# TYPE proxy_bytes_sent counter
proxy_bytes_sent 91740258

# HELP proxy_response_time_seconds Average response time in seconds
# TYPE proxy_response_time_seconds gauge
proxy_response_time_seconds 0.000284

# HELP proxy_tls_connections_total Total number of TLS connections
# TYPE proxy_tls_connections_total counter
proxy_tls_connections_total 15820

# HELP proxy_errors_total Total number of proxy errors
# TYPE proxy_errors_total counter
proxy_errors_total 3

# HELP proxy_response_status_codes_total HTTP response status codes
# TYPE proxy_response_status_codes_total counter
proxy_response_status_codes_total{code="200"} 45102
proxy_response_status_codes_total{code="301"} 2040
proxy_response_status_codes_total{code="404"} 1146
proxy_response_status_codes_total{code="503"} 3

Metrics Reference

Metric Type Description
proxy_requests_total counter Total number of HTTP requests processed
proxy_requests_in_flight gauge Number of requests currently being processed
proxy_bytes_received counter Total bytes received from clients
proxy_bytes_sent counter Total bytes sent to clients
proxy_response_time_seconds gauge Average response time in seconds
proxy_tls_connections_total counter Total number of TLS connections established
proxy_errors_total counter Total number of proxy errors encountered
proxy_response_status_codes_total counter HTTP response status codes with code label

# Worker Privilege Dropping

By default, app workers inherit the same privileges as the Soli Proxy process. If you run the proxy as root (to bind ports 80/443), all workers also run as root. This is a security risk — a compromised app gives attackers full system access.

Configuring Per-App Users

Use the user and group fields in app.infos to run each app as a different user:

app.infos
# Run app workers as a specific user
name = "myapp"
domain = "myapp.example.com"
start_script = "npm start"
user = "nobody"
group = "nogroup"

Never run app workers as root in production

Always configure a dedicated non-root user for each app. This limits the blast radius if an app is compromised.