RESTGate is a small, configuration-driven Layer-7 API gateway / reverse proxy written in Go. You describe your backend microservices and their REST routes in a single YAML file, and RESTGate routes incoming HTTP traffic to the right service - ideal for fronting RESTful APIs inside a Kubernetes cluster.
- Config-driven routing - declare services and routes in one YAML file; no code changes to add a route.
- WebSocket & streaming - proxies HTTP protocol upgrades (WebSocket), Server-Sent Events, and chunked/streaming responses out of the box.
- Built on the standard library - uses Go's battle-tested
net/http/httputilreverse proxy with a tuned, connection-pooling transport. One proxy is built per service at startup, not per request. - Production-ready operations
- Graceful shutdown on
SIGINT/SIGTERM(drains in-flight requests). - Sensible HTTP server timeouts (Slowloris protection via
read_header_timeout). - Separate admin server for
/healthz,/readyz,/metricsand/version, so operational endpoints never collide with proxied routes.
- Graceful shutdown on
- Observability - structured logging (text or JSON) and Prometheus metrics out of the box.
- Safe by default - strict config validation at startup, panic-recovery middleware, distroless non-root container image.
- Cloud-native - drops into Kubernetes with the manifests in
deploy/k8s/.
client -> :7000 proxy -> middleware -> router -> reverse proxy -> backend service
:9000 admin: /healthz /readyz /metrics /version
A request is matched to a route by method and path, passes through the
recover/log/metrics middleware, and is proxied to that service's service_url.
Health, readiness and metrics are served separately on the admin port.
RESTGate is configured by a single YAML file. Resolve order for its location:
-config <path>command-line flagRESTGATE_CONFIG_PATHenvironment variableRESTATE_CONFIG_PATHenvironment variable (deprecated legacy spelling, still honored with a warning)
See config/restgate.example.yaml for a complete example.
| Key | Type | Default | Description |
|---|---|---|---|
port |
int | 7000 |
Port the proxy (data-plane) listens on. |
admin_port |
int | 9000 |
Port for health/readiness/metrics/version. Must differ from port. |
log_level |
string | info |
trace, debug, info, warn, or error. |
log_format |
string | text |
text or json. |
read_header_timeout |
duration | 10s |
Max time to read request headers (Slowloris protection). |
read_timeout |
duration | (off) | Max time to read the whole request. Off by default to allow large uploads. |
write_timeout |
duration | (off) | Max time to write the response. Off by default to allow streaming. |
idle_timeout |
duration | 120s |
Keep-alive idle timeout. |
shutdown_timeout |
duration | 15s |
Grace period for draining on shutdown. |
response_header_timeout |
duration | (off) | Max wait for a backend's response headers. |
observed_services[] |
list | - | Backend services and their routes (below). |
Each entry in observed_services:
| Key | Description |
|---|---|
name |
A label for the service (documentation only). |
service_url |
Backend base URL, e.g. http://account-service:8000 (K8s DNS). Must be http/https with a host. |
routes[] |
Routes proxied to this service. |
Each route:
| Key | Description |
|---|---|
name |
Unique route name (used as the metrics label). Required. |
methods |
Comma-separated HTTP methods, e.g. "GET,OPTIONS". |
pattern |
URL path pattern, e.g. /account/{id}. Must start with /. |
description |
Optional human-readable description. |
Invalid configuration (bad port, duplicate route name, unparseable service_url,
unknown YAML key, β¦) is rejected at startup with a clear error.
RESTGate transparently proxies HTTP protocol upgrades (WebSocket), Server-Sent
Events, and chunked/streaming responses - the connection is hijacked on a 101 Switching Protocols and bytes are copied bidirectionally.
To expose a WebSocket endpoint, declare its route with the GET method (a
WebSocket handshake is an HTTP GET with an Upgrade header):
- name: realtime-service
service_url: "http://realtime-service:8080"
routes:
- name: realtime_socket
methods: "GET"
pattern: "/realtime/ws"Caveat: Do not set
read_timeoutorwrite_timeoutif you proxy WebSockets or other long-lived streams - those timeouts would close the connection mid-stream. They are disabled by default for this reason.idle_timeoutandread_header_timeoutdo not affect an established upgrade. WebSocket backends should be reachable over HTTP/1.1 (internalhttp://services are).
Served on admin_port (default 9000):
| Endpoint | Purpose |
|---|---|
GET /healthz |
Liveness - always 200 while the process runs. |
GET /readyz |
Readiness - 200 once listeners are bound, 503 otherwise. |
GET /metrics |
Prometheus metrics (restgate_requests_total, restgate_request_duration_seconds, β¦). |
GET /version |
Build version, commit and time as JSON. |
# Build a static binary into ./bin (version info baked in via ldflags)
make build
# Run against the example config
./bin/restgate -config config/restgate.example.yaml
# ...or
RESTGATE_CONFIG_PATH=config/restgate.example.yaml ./bin/restgateUseful make targets: build, run, test, test-race, cover, vet,
fmt, lint, docker. Run make help for the full list.
# Build the image (multi-stage, distroless, non-root)
make docker
# ...or directly:
docker build -t restgate:latest .
# Run, mounting your config to the default in-container path
docker run --rm \
-v "$(pwd)/config/restgate.example.yaml:/etc/restgate/restgate.yaml:ro" \
-p 7000:7000 -p 9000:9000 \
restgate:latestThe image defaults RESTGATE_CONFIG_PATH to /etc/restgate/restgate.yaml.
Ready-to-edit manifests live in deploy/k8s/:
kubectl apply -f deploy/k8s/configmap.yaml # the RESTGate config
kubectl apply -f deploy/k8s/deployment.yaml # Deployment w/ liveness & readiness probes
kubectl apply -f deploy/k8s/service.yaml # ClusterIP service (http + admin ports)
kubectl apply -f deploy/k8s/ingress.yaml # Ingress (set your ingressClassName)The Deployment runs as a non-root user with a read-only root filesystem, wires the
liveness/readiness probes to the admin port, and exposes Prometheus scrape
annotations. Edit the image: field to point at your registry.
Requires Go 1.25+ and golangci-lint v2 (for the make lint target).
go test -race -cover ./... # tests with the race detector
go vet ./... # vet
make lint # golangci-lint
make build # static binary into ./binRun make help for all available targets.
RESTGate's small, conventional codebase makes it easy to modify and extend - add logging, auth, rate limiting, or routing features as middleware. Fork it, open a PR, and our team will review.
π₯ Every contributor is honorably mentioned in CONTRIBUTING.md
and immortalized in the git history.
Questions or problems? Please open a GitHub issue - we'll address it as soon as we can.