Configuration¶
prox uses JSON5 for configuration — a superset of JSON with comments, trailing commas, and unquoted keys.
Structure¶
Every config has three sections:
┌──────────────────────────────────────────────────────┐
│ config.json5 │
├──────────────┬──────────────┬────────────────────────┤
│ services │ actions │ resources │
│ (WHAT) │ (HOW) │ (WITH WHAT) │
│ │ │ │
│ listen addr │ type: proxy │ inline text │
│ tls on/off │ type: static│ │
│ routes[] │ type: serve │ │
│ └ match │ timeout │ │
│ └ action ──│──► ref ──────│──► body_ref → resource │
└──────────────┴──────────────┴────────────────────────┘
Key concept: everything is reference-based. Routes point to actions by name, actions point to resources by name. But you can also inline them directly when a definition isn't reused.
Services¶
A service is a listener with routing rules. Services can be defined inline or loaded from external files.
Inline¶
{
services: {
my_site: {
listen: ":8080", // required
tls: true, // optional, default: false
tls_cert: "/path/cert", // required if tls: true
tls_key: "/path/key", // required if tls: true
config: {}, // optional, per-service tuning
routes: [...] // required, at least one
},
},
}
Service config¶
The optional config block tunes HTTP server timeouts and proxy transport behavior for a specific service. All values fall back to built-in defaults when omitted.
Durations accept strings ("5s", "1m30s", "5m") or numbers (interpreted as seconds).
| Field | Default | Description |
|---|---|---|
read_timeout |
10s |
Maximum time to read the full request (headers + body) |
write_timeout |
30s |
Maximum time to write the full response |
idle_timeout |
120s |
Keep-alive idle timeout before closing the connection |
response_header_timeout |
30s |
Maximum time to wait for upstream response headers |
flush_interval |
0 |
How often to flush buffered proxy response data. -1 = flush immediately |
dial_timeout |
action | TCP dial timeout (defaults to the action's timeout) |
keep_alive |
30s |
TCP keep-alive interval |
max_idle_conns |
100 |
Maximum idle connections in the connection pool |
max_idle_conns_per_host |
10 |
Maximum idle connections per upstream host |
tls_handshake_timeout |
10s |
TLS handshake deadline for HTTPS upstreams |
h2_read_idle_timeout |
30s |
HTTP/2: send ping after this idle period |
h2_ping_timeout |
15s |
HTTP/2: deadline for ping response |
max_connections |
0 |
Maximum concurrent connections. 0 = unlimited |
Tip
For streaming protocols (SSE, long-lived HTTP, long-polling), set flush_interval to -1 and increase read_timeout, write_timeout, and response_header_timeout to accommodate long-lived connections.
// Streaming-friendly gateway — long timeouts, immediate flushing.
{
services: {
gateway: {
listen: ":443",
tls: true,
tls_cert: "/etc/prox/certs/",
config: {
read_timeout: "5m",
write_timeout: "5m",
response_header_timeout: "5m",
flush_interval: -1,
},
routes: [...]
},
},
}
File reference¶
A string value loads the service from an external JSON5 file:
Relative paths are resolved from the directory of the parent config file.
Directory reference¶
A string pointing to a directory loads all .json5 files from it. Each file becomes a service named after its filename (without extension):
{
services: {
_microservices: "./services/",
// services/web.json5 → service "web"
// services/api.json5 → service "api"
},
}
Non-.json5 files and subdirectories are ignored.
Directory mode (CLI)¶
You can also pass a directory directly to -config:
Every .json5 file in the directory is treated as a service fragment. No root config file is needed.
Service Fragments¶
A service fragment is the file format for external service definitions. It's a service definition with optional local actions and resources:
// web.json5
{
listen: ":8080",
routes: [
{ match: { path: "/health" }, action: "health" },
{ match: { path: "/*" }, action: "frontend" },
],
// Local actions — merged into the global pool.
actions: {
frontend: { type: "serve", root: "./public" },
},
// Local resources — merged into the global pool.
resources: {
banner: { text: "Welcome!" },
},
}
Merge rules:
- Actions and resources from fragments are merged into the global pool alongside definitions from the root config.
- Duplicate names across any files are a validation error — rename to avoid collisions.
- Global actions from the root config are accessible by all services (e.g. a shared
healthaction). - Fragments cannot reference other files — only one level of nesting.
Validation¶
Validate before deploying:
The validator checks:
- All action references resolve
- All resource references resolve
- Required fields are present
- HTTP methods are valid
- Path patterns are well-formed
- Domain patterns are well-formed (at most one
*per segment, at least 2 segments,**only at end) - At least one of
pathordomainin each route (or omitmatchfor catch-all) - TLS cert/key are provided when TLS is enabled
passroutes require adomain(SNI matching) and cannot usepathormethodspassactions require anupstream- Balancer type is valid (
roundrobin,random, orleastconn) - Balancer targets are non-empty and unique (empty allowed with plugins)
- Balancer is only used with
proxyorpassactions - Action upstream contains
{target}when a balancer is used - Plugin paths are non-empty
- No duplicate action/resource names across files
- No circular file references
- Reports all issues at once