chaos-proxy-go is a Go port of fetch-kit/chaos-proxy: a proxy server for injecting configurable network chaos (latency, failures, connection drops, rate-limiting, etc.) into any HTTP or HTTPS traffic. Use it via CLI or programmatically to apply ordered middleware (global and per-route) and forward requests to your target server, preserving method, path, headers, query, and body.
-
Download from GitHub Releases or build from source:
go build -o chaos-proxy-go . -
Create a minimal
chaos.yaml:target: "http://localhost:4000" port: 5000 global: - failRandomly: rate: 0.1 status: 503
-
Run the proxy:
./chaos-proxy-go --config chaos.yaml --verbose
All traffic to http://localhost:5000 is now forwarded to http://localhost:4000 with 10% random 503 failures injected.
- Middleware reference — all 11 built-in primitives with config tables
- Observability — OpenTelemetry tracing, span attributes, connecting to a collector
- Hot reload — runtime config reload, endpoint spec, edge cases
Ready-made scenarios in the presets/ folder:
| Preset | Simulates |
|---|---|
flaky-backend.yaml |
Unstable upstream: latency jitter, 5% 503s, 2% connection drops |
mobile-3g.yaml |
Mobile 3G: 100–300ms latency, 50 KB/s bandwidth, 1% drops |
burst-errors.yaml |
Error bursts: every 5th fails with 500, plus 10% random 503s |
timeout-storm.yaml |
Timeout storm: 1–8s delays, 10% drops, 15% instant 504s |
Run a preset directly:
./chaos-proxy-go --config presets/flaky-backend.yaml- Simple configuration via a single
chaos.yamlfile - Programmatic API and CLI usage
- Built-in middleware primitives: latency, latencyRange, fail, failRandomly, failNth, dropConnection, rateLimit, cors, throttle, headerTransform, bodyTransformJSON
- Extensible registry for custom middleware
- Supports both request and response interception/modification
- Method+path route support (e.g.,
GET /api/users) - Robust short-circuiting: middlewares halt further processing when sending a response or dropping a connection
- Runtime config reload via
POST /reloadwithout process restart
Download the latest release from GitHub Releases or build from source:
go build -o chaos-proxy-go .Requires Go 1.21 or later.
./chaos-proxy-go --config chaos.yaml [--verbose]--config <path>: YAML config file (default./chaos.yaml)--verbose: print loaded config, middleware setup, and per-request logs
import (
"log"
"chaos-proxy-go/internal/config"
"chaos-proxy-go/internal/proxy"
)
cfg, err := config.Load("chaos.yaml")
if err != nil {
log.Fatalf("failed to load config: %v", err)
}
server, err := proxy.New(cfg, false)
if err != nil {
log.Fatalf("failed to create server: %v", err)
}
if err := server.Start(); err != nil {
log.Fatalf("server error: %v", err)
}Chaos Proxy supports full runtime reloads without process restart.
- Endpoint:
POST /reload - Content-Type:
application/json - Payload: full config snapshot (same shape as
chaos.yaml, but JSON) - Behavior: build-then-swap — all-or-nothing, the active state is never partially updated
- Body size limit: 1 MB
curl -X POST http://localhost:5000/reload \
-H "Content-Type: application/json" \
-d '{
"target": "http://localhost:4000",
"port": 5000,
"global": [
{ "latency": { "ms": 120 } },
{ "failRandomly": { "rate": 0.05, "status": 503 } }
],
"routes": {
"GET /users/:id": [
{ "failNth": { "n": 3, "status": 500 } }
]
}
}'{
"ok": true,
"version": 2,
"reload_ms": 3
}| Status | Reason |
|---|---|
400 |
Invalid or unparseable config (active state is unchanged) |
409 |
Reload already in progress |
415 |
Wrong Content-Type (must be application/json) |
{
"ok": false,
"error": "target is required",
"version": 1,
"reload_ms": 0
}proxy.New(...) returns a *Server with a ReloadConfig method:
result := server.ReloadConfig(newCfg)
if !result.OK {
log.Printf("reload failed: %s", result.Error)
} else {
log.Printf("reloaded to version %d in %dms", result.Version, result.ReloadMs)
}- In-flight requests are deterministic: they run on the snapshot captured at the moment the request arrived, immune to concurrent reloads.
- New requests after a successful swap immediately use the new snapshot.
- All-or-nothing: if parse, validate, or middleware-build fails, the active state is unchanged.
- Middleware state resets on reload (e.g., rate-limit and failNth counters start fresh).
- Concurrent reloads are rejected with
409; the second caller must retry.
target: "http://localhost:4000" # required
port: 5000 # default: 5000
# Optional: OpenTelemetry tracing
otel:
serviceName: "my-service" # required if otel is set
endpoint: "http://localhost:4318" # required if otel is set
flushIntervalMs: 5000 # default
maxBatchSize: 100 # default
maxQueueSize: 1000 # default
headers: # optional OTLP request headers
x-api-key: "secret"
# Global middleware — applied to every proxied request
global:
- latencyRange:
minMs: 20
maxMs: 100
- failRandomly:
rate: 0.05
status: 503
# Route-specific middleware — "METHOD /path" format
routes:
GET /api/users:
- latency:
ms: 500
POST /api/orders:
- failNth:
n: 3
status: 500See docs/middlewares.md for the full middleware reference and docs/observability.md for otel options.
latency(ms)— delay every requestlatencyRange(minMs, maxMs, seed?)— random delay (deterministic whenseedis set)fail({ status, body })— always failfailRandomly({ rate, status, body, seed? })— fail with probability (deterministic whenseedis set)failNth({ n, status, body })— fail every nth requestdropConnection({ prob, seed? })— randomly drop connection (probdefaults to1.0if omitted; deterministic whenseedis set)rateLimit({ limit, windowMs, key })— rate limiting (by header key if configured, otherwise by client remote address in ip:port format)cors({ origin, methods, headers })— enable and configure CORS headersthrottle({ rate, chunkSize, burst })— throttles bandwidth per request (rateis bytes/second)headerTransform({ request: { set, delete }, response: { set, delete } })— mutate request/response headersbodyTransformJSON({ request: { set, delete }, response: { set, delete } })— mutate JSON request/response bodies
Register custom middleware in Go. See the internal/middleware package for examples.
- Proxy forwards all headers; be careful with sensitive tokens.
- Intended for local/dev/test only.
- HTTPS pass-through requires TLS termination; not supported out-of-the-box.
- Not intended for stress testing; connection limits apply.
- Middleware execution order is nondeterministic when multiple middlewares are in the same YAML map element. For example:
For deterministic order, use separate map elements:
global: - latency: { ms: 100 } fail: { status: 500 } # Order vs latency is not guaranteed
global: - latency: { ms: 100 } - fail: { status: 500 } # Always runs after latency
MIT
Go port of fetch-kit/chaos-proxy.
This is a Go port of fetch-kit/chaos-proxy.