Edge Proxy is a runtime-configurable reverse proxy and edge gateway written in Go. It routes HTTP traffic by host and path, tracks backend health, applies per-virtual-host rate limiting, exposes Prometheus metrics, and accepts authenticated runtime configuration updates. It is developed as an open-source learning project for exploring HTTP proxy internals and edge infrastructure.
At runtime, Edge Proxy:
- accepts HTTP requests on the public proxy port;
- selects a virtual host by the request
Hostheader; - selects optional path routes using longest-match precedence;
- can strip a matched path prefix before forwarding upstream;
- forwards only to enabled backends that are currently marked active;
- supports
least-connectionsand experimentaladaptiveload balancing; - retries retryable requests once on an alternate backend;
- runs active backend health checks and exposes backend status;
- resolves client IPs using forwarding headers only from trusted proxy CIDRs;
- applies per-virtual-host rate limiting through reusable security policies;
- publishes validated configuration snapshots atomically;
- persists runtime configuration changes to the configured JSON file;
- exposes a token-protected admin HTTP API backed by token-protected gRPC;
- exports operational metrics for Prometheus;
- includes a Docker Compose stack with two mock backends, Prometheus, and Grafana.
Requests always use one complete runtime snapshot. During a configuration update, a request either sees the previous valid snapshot or the next valid snapshot; it does not observe a partially applied update.
flowchart LR
Client["Client"] --> Proxy["Reverse proxy"]
Admin["Admin HTTP API"] --> GRPC["Authenticated gRPC control plane"]
GRPC --> Runtime["Atomic runtime state"]
Runtime --> Proxy
Proxy --> LB["Load balancer"]
LB --> A["Backend A"]
LB --> B["Backend B"]
Health["Health checker"] --> A
Health --> B
Proxy --> Metrics["Prometheus metrics"]
Metrics --> Grafana["Grafana"]
cmd/
admin-api/ HTTP API for runtime configuration
mock-backend/ Demo backend used by Docker Compose
reverse-proxy/ Main proxy process
internal/
admin/ HTTP handlers and gRPC control plane
api/ Protocol Buffers definition and generated Go code
config/ Loading, validation, persistence, and snapshots
health/ Backend health checking
lb/ Load-balancing strategies
metrics/ Runtime and Prometheus metrics
middleware/ Request middleware and client IP resolution
proxy/ Proxy runtime, orchestration, and handlers
Requirements:
- Go 1.25.2 or newer, matching
go.mod; - Docker and Docker Compose for the complete local stack.
Create a local environment file:
cp .env.example .envReplace the example secrets in .env, then start the stack:
docker compose up --buildThe stack exposes:
| Service | Address |
|---|---|
| Reverse proxy | http://localhost:8080 |
| Admin API | http://localhost:8081 |
| Prometheus | http://localhost:9090 |
| Grafana | http://localhost:3000 |
Send a request through the configured virtual host:
curl -H "Host: app.example.local" http://localhost:8080/Health checks start immediately. A backend becomes available after it reaches the configured healthy threshold.
The default configuration is stored in configs/config.json. Additional
examples are available in configs/examples/.
Proxy configuration uses concrete, non-secret values. In Docker Compose, backend URLs can use service names from the shared Docker network:
{
"id": "backend-1",
"url": "http://backend-a:3000",
"weight": 1,
"enabled": true
}Use a separate configuration file for each environment when backend addresses
or routing rules differ. When running the binary directly, CONFIG_PATH
selects the file loaded by the proxy. In a container, mount the selected file
and set CONFIG_PATH to its path. Runtime changes made through the Admin API
are persisted as concrete values to that file.
Environment variables remain available for secrets and process-level startup
settings. They are not expanded inside the JSON configuration. The local
.env file is used by Docker Compose for values such as admin tokens and the
Grafana password, .env.example documents settings intended to be customized
for the local stack.
Important fields:
| Field | Purpose |
|---|---|
server.proxy_port |
Public proxy HTTP port |
server.admin_grpc_port |
Persisted admin gRPC port setting; the listener currently uses ADMIN_GRPC_ADDR |
load_balancing.strategy |
Load-balancing strategy: least-connections or experimental adaptive |
backends |
Upstream backend definitions with stable id values |
virtual_hosts |
Host-based routing policies using backend_ids |
path_routes |
Optional path-specific backend selection |
health_check |
Active and passive checks, scheduling, thresholds, concurrency, recovery backoff, and transport limits |
timeouts |
Outbound HTTP transport timeouts |
logging |
Log level and asynchronous logging settings |
security.policies |
Reusable security policies referenced by security_policy_id |
When active health checking is enabled, every configured backend is checked
immediately at startup and then according to schedule.interval_ms with
optional schedule.jitter_ms. Checks run in a bounded worker pool. Duplicate
checks for the same backend are coalesced, and work that does not fit in the
queue is retried during the next scheduling cycle.
thresholds.healthy and thresholds.unhealthy define how many consecutive
results are required to change backend state. Recovery backoff reduces probe
frequency for unhealthy backends and is capped by recovery.backoff.max_ms.
When passive checking is enabled, upstream connection errors and HTTP 5xx responses count as failures, while responses below 500 count as successes. Passive observations use the same thresholds as active probes. If health checking is disabled, enabled backends are considered available immediately.
Runtime changes made through the admin API are persisted to the configured JSON file. Mount that file as a volume when configuration must survive container replacement.
Path routes are matched as exact paths or slash-delimited subtrees. For example,
/api matches /api and /api/users, but not /apix. When multiple routes
match, the longest route wins. strip_prefix is applied only after a route has
matched.
Authentication is enabled by default. Configure separate HTTP and gRPC tokens:
ADMIN_AUTH_ENABLED=true
ADMIN_API_TOKEN=replace-with-a-random-value
ADMIN_GRPC_TOKEN=replace-with-another-random-valueSet ADMIN_AUTH_ENABLED=false only in an isolated local environment. The gRPC
token is always required, and the unencrypted gRPC transport must remain on a
trusted private network.
Main routes:
GET|POST /api/backend
GET|PUT|DELETE /api/backend/{id}
GET|POST /api/vhost
GET|PUT|DELETE /api/vhost/{domain}
GET|PUT /api/vhost/{domain}/security
GET /api/security/policies
PUT /api/security/policies/{id}
GET|PUT /api/config/server
GET|PUT /api/config/lb
API documentation:
The browsable reference is generated from the checked-in specification and published through this repository's GitHub Pages workflow.
Example:
curl http://localhost:8081/api/backend \
-H "Authorization: Bearer $ADMIN_API_TOKEN"Prometheus scrapes the internal operational endpoint:
http://reverse-proxy:9091/metrics/prometheus
The public proxy port exposes only a minimal /health response. Detailed
operational endpoints are kept on the internal OPS_ADDR.
When another proxy is placed in front of Edge Proxy, configure
TRUSTED_PROXY_CIDRS with only its network ranges. Forwarded client IP headers
from other peers are ignored.
Run the complete quality suite:
gofmt -l .
go vet ./...
go test ./...
go test -race ./...Run the proxy outside Docker with a configuration whose backend URLs are reachable from the host:
ADMIN_GRPC_TOKEN=development-only \
CONFIG_PATH=/path/to/config.json \
go run ./cmd/reverse-proxyEdge Proxy is pre-alpha and under active development. Configuration formats and APIs may change before the first stable release.
The project originated from an engineering thesis. The current repository is a substantial refactor with a consolidated Go module, immutable configuration snapshots, atomic runtime publication, a backend state registry, and an authenticated control plane.
- Admin gRPC authentication uses bearer tokens, but the transport is plaintext. Keep it on a trusted private network.
- The public proxy listener serves plain HTTP; TLS termination is not implemented yet.
- Updating
server.proxy_portpersists the new value, but does not restart the listener.server.admin_grpc_portdoes not currently configure the live listener, which is controlled byADMIN_GRPC_ADDR. - Newly added or freshly started backends may return 503 until health checks mark them active.
- The adaptive load balancer is implemented but still experimental.
- HTTP upgrades, trailers, and complete hop-by-hop header filtering are not yet supported.
- Request size, URL length, and header limits are not yet configurable per virtual host.
- Read CONTRIBUTING.md before opening a pull request.
- Use GitHub Issues for bugs and feature proposals.
- Report vulnerabilities according to SECURITY.md, not through a public issue.
Edge Proxy is available under the MIT License.