A high-performance, self-hostable public API gateway with DDoS protection, load balancing, JWT/API-key auth, circuit breaking, Redis response caching, Prometheus metrics, and Jaeger tracing — all in a single Go binary.
Quick Start · Features · Architecture · API Reference · Configuration · Contributing
Most public gateways are either managed black boxes (Cloudflare, AWS API Gateway) or heavy enterprise licenses (NGINX Plus, Kong Enterprise). This one gives you a public-facing, production-grade gateway you can run anywhere — on a single VM, in Docker Compose, or on a Kubernetes cluster — with zero licensing.
- Public-first design — Built to sit at the edge of the internet, with DDoS rate-limiting, IP blacklisting, connection throttling, and a strict security-headers middleware applied to every response.
- Dynamic routing — Configure upstream services via environment variables or a single JSON string, and hot-swap backends without rebuilding the binary.
- Resilient by default — Per-backend health checks, circuit breaker, retry with exponential backoff, graceful shutdown, and automatic failover across a pool of backends.
- Observability without glue code — Prometheus metrics at
/metrics, Jaeger traces, structured JSON logs, a built-in stats endpoint, and a live TTY dashboard when running interactively. - Single binary, zero runtime deps — A statically compiled Go binary that boots in milliseconds. Redis is optional (caching falls back gracefully if it's unavailable).
Edge Protection Routing & Load-Balancing Resilience Observability
Per-IP rate limiting Longest-prefix matching Circuit breaker Prometheus metrics
DDoS threshold block Round-robin Retry + backoff Jaeger tracing
IP whitelist/blacklist Least-connections Graceful shutdown Structured logging
Security headers IP-hash (sticky) Timeout per-route Live TTY dashboard
CORS Weighted Backend health checks System monitor page
Authentication Caching & Compression Admin & Ops Deployment
JWT (HMAC + exp) Redis response cache /admin/whitelist Multi-stage Dockerfile
API-key (SHA-256) Gzip (Accept-Encoding) /admin/blacklist docker-compose.yml
Public-endpoint bypass Per-route cache TTL /stats · /monitor Kubernetes manifests
Per-route require_auth Configurable compression Graceful SIGTERM nginx reverse proxy
| Layer | Technology |
|---|---|
| Language | Go 1.23+ (statically compiled single binary) |
| Router | net/http with a custom prefix-matching ServiceRouter |
| Rate limiting | golang.org/x/time/rate token bucket, per client IP |
| Auth | github.com/golang-jwt/jwt/v5 (HS256) + SHA-256 API keys |
| Cache | Redis 7+ via github.com/redis/go-redis/v9 (optional, graceful fallback) |
| Metrics | Prometheus via github.com/prometheus/client_golang |
| Tracing | OpenTracing + Jaeger (github.com/uber/jaeger-client-go) |
| Config | Environment variables (github.com/joho/godotenv) or JSON SERVICE_ROUTES |
| Reverse proxy (edge) | nginx (optional, see NGINX_SETUP.md) |
| Container | Multi-stage Dockerfile with distroless runtime layer |
| Orchestration | Docker Compose + Kubernetes manifests with HPA |
| Requirement | Version | Notes |
|---|---|---|
| Go | 1.23+ | Only for local builds |
| Docker | 24+ | Recommended for the full stack |
| Redis | 7+ | Optional — caching disabled if unreachable |
| Prometheus / Grafana / Jaeger | any | Optional observability stack |
git clone https://github.com/valtunox/va_api_gateway_golang.git
cd va_api_gateway_golang
cp .env.development .env # tweak as needed
docker-compose up -dThe full stack (gateway, nginx, Redis, Prometheus, Grafana, Jaeger) is now live:
| Service | URL |
|---|---|
| nginx (edge) | http://localhost |
| Gateway | http://localhost:9777 |
| Health | http://localhost:9777/health |
| Metrics | http://localhost:9777/metrics |
| Stats | http://localhost:9777/stats |
| System monitor | http://localhost:9777/monitor |
| Prometheus | http://localhost:9090 |
| Grafana | http://localhost:3000 (admin/admin) |
| Jaeger | http://localhost:16686 |
git clone https://github.com/valtunox/va_api_gateway_golang.git
cd va_api_gateway_golang
go mod download
make build # produces ./gateway
./gateway # starts on :9777kubectl apply -f k8s/redis.yaml
kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/ingress.yaml
kubectl get pods -n va-gateway
kubectl logs -f deployment/api-gateway -n va-gatewayKubernetes manifests include a Horizontal Pod Autoscaler (3–10 replicas), a Pod Disruption Budget (min 2 available), liveness/readiness probes, and an NGINX ingress with SSL.
# Unauthenticated health check
curl -s http://localhost:9777/health | jq
# Authenticated request (demo API key — change in production!)
curl -H "X-API-Key: demo-api-key-123" http://localhost:9777/api/studio/
# Quick load test
wrk -t10 -c100 -d30s http://localhost:9777/health- Per-IP token-bucket rate limiting —
MAX_REQUESTS_PER_SECOND+MAX_BURST_SIZE, with an eviction goroutine that cleans idle entries every 10 minutes. - DDoS counter-based blocking — Any IP that exceeds
DDOS_THRESHOLDrequests per minute is blocked forBLOCK_DURATION. Counters and blocks are garbage-collected on a 1-minute tick. - IP whitelist / blacklist — Runtime-mutable via
POST /admin/whitelist?ip=XandPOST /admin/blacklist?ip=X. Whitelist wins over every other check. - Security headers —
X-Content-Type-Options,X-Frame-Options: DENY,Strict-Transport-Security,Content-Security-Policy,X-XSS-Protection,Referrer-Policy— applied to every response. - CORS — Configurable allowed origins via
CORS_ALLOWED_ORIGINS(JSON array, defaults to["*"]).
- Longest-prefix matching — Routes are kept sorted by prefix length, so
/api/studio2/foomatches before/api/studio. - Strip-prefix forwarding — Optionally trim the matched prefix before forwarding to the upstream.
- Per-route timeouts & auth — Each route declares its own
timeout_secsandrequire_auth. - Configurable via env vars or JSON — Either set
SERVICE_ROUTESto a JSON array, or use individualROUTE_<NAME>_PREFIX/ROUTE_<NAME>_URLpairs.
| Algorithm | Use case | Sticky sessions |
|---|---|---|
round-robin |
Equal backends | No |
least-conn (default) |
Varying request durations | No |
ip-hash |
Per-user session affinity | Yes |
weighted |
Priority or capacity-tiered backends | No |
All algorithms skip dead backends and honor the per-backend Weight.
- Circuit breaker — Classic closed → open → half-open state machine. Opens after
CIRCUIT_BREAKER_MAXconsecutive failures, retries afterCIRCUIT_BREAKER_TIMEOUT. - Retry with exponential backoff — Up to 3 attempts per request, with
100ms * 2^attemptbackoff between retries, each rolling to the next healthy backend. - Active health checks — Every
HEALTH_CHECK_INTERVAL, the gateway dials every backend's TCP host and flips liveness. Prometheusbackend_health_statusis updated each tick. - Graceful shutdown — SIGINT / SIGTERM triggers a 30-second context-bounded shutdown that drains in-flight requests.
- JWT (HS256) —
Authorization: Bearer <token>with signature + expiry validation. User ID from thesubclaim is attached to the trace span. - API keys —
X-API-Key: <key>checked against a SHA-256 hash of the allow-list. Replace the demo keys in middleware.go with a database-backed lookup before going public. - Public endpoints —
/health,/metrics,/login,/register,/api/v1/publicbypass auth. Every other path honors the route'srequire_authflag.
- Redis response cache — GET requests are looked up by
cache:<method>:<host>:<path>. Misses populate the cache with the configuredCACHE_TTL. If Redis is unreachable the gateway logs a warning and serves uncached. - Gzip — Responses are gzip-encoded when the client sends
Accept-Encoding: gzip, controlled byENABLE_COMPRESSION. - HTTP keep-alive —
MaxIdleConns(default 100) idle connections per host, withIdleConnTimeout(default 90s).
Prometheus metrics (scrape at /metrics):
| Metric | Type | Labels |
|---|---|---|
http_requests_total |
Counter | method, endpoint, status |
http_request_duration_seconds |
Histogram | method, endpoint |
active_connections |
Gauge | — |
backend_health_status |
Gauge | backend |
circuit_breaker_trips_total |
Counter | — |
blocked_ips_total |
Counter | — |
cache_hits_total |
Counter | — |
Tracing: Every request starts a gateway.request span with method, path, user.id, backend, and error tags.
Logging: Structured JSON logs per subsystem (gateway, router, proxy, cache, circuit, ddos, admin, tracer, config) with a consistent request_id propagated via the x-request-id header.
Live TTY dashboard: When the gateway runs attached to a terminal, a real-time dashboard shows total requests, active connections, blocked IPs, circuit state, and backend health.
| Capability | Cloudflare | NGINX Plus | AWS API Gateway | Kong OSS | VA API Gateway |
|---|---|---|---|---|---|
| DDoS protection | Yes | Yes | Yes | Partial | Yes |
| 4 load-balancing algorithms | Yes | Yes | No | Partial | Yes |
| JWT validation | Yes | Yes | Yes | Yes | Yes |
| API-key auth | Yes | Yes | Yes | Yes | Yes |
| Circuit breaker | Yes | Yes | No | Plugin | Yes |
| Redis response cache | Yes | Yes | No | Plugin | Yes |
| Prometheus native | No | No | No | Plugin | Yes |
| Jaeger tracing | No | No | Partial | Plugin | Yes |
| Live TTY dashboard | No | No | No | No | Yes |
| Single self-hosted binary | No | Partial | No | No | Yes |
| Open source | No | No | No | Yes | Yes (MIT) |
┌─────────────────────────┐
│ Public Clients │
│ (browsers, SDKs, IoT) │
└───────────┬─────────────┘
│ HTTPS
┌───────────▼─────────────────────┐
│ nginx (optional edge proxy) │
│ SSL termination · static files │
│ Coarse rate-limit · gzip │
└───────────┬─────────────────────┘
│ HTTP :9777
┌─────────────────▼─────────────────────┐
│ VA API Gateway (Go) │
│ │
│ DDoS guard ─► Rate limiter ─► Auth │
│ │ │
│ ▼ │
│ Router (longest-prefix match) │
│ │ │
│ ▼ │
│ Pool (4 algos) ─► Circuit breaker │
│ │ │
│ ▼ │
│ Cache (Redis) ─► Reverse proxy │
└─────────┬─────────────────────────────┘
│
┌──────────┬──────┼──────┬──────────┬─────────┐
▼ ▼ ▼ ▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌──────────┐
│ Studio │ │ AI │ │ Core │ │ LLM │ │ Agent │
│ :3000 │ │ :8741 │ │ :8743 │ │ :8745 │ │ :8788 │
└────────┘ └────────┘ └────────┘ └────────┘ └──────────┘
┌───────────────────────────────┐
│ Redis · Prometheus · Jaeger │
└───────────────────────────────┘
Request lifecycle (see main.go Gateway.ServeHTTP):
- Extract client IP (
X-Real-IP→X-Forwarded-For→RemoteAddr) - DDoS check → per-IP rate limit → route match
- Auth check (JWT or API key) unless the path is public
- Redis cache lookup for
GETrequests - Pick a healthy backend from the matched pool using the configured algorithm
- Proxy through the circuit breaker with retry + exponential backoff
- Emit metrics, finish trace span, log the completed request
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/health |
Public | Liveness + backend status + registered routes |
GET |
/metrics |
Public | Prometheus scrape endpoint |
GET |
/stats |
Public | Runtime stats (circuit state, algo, flags, route count) |
GET |
/monitor |
Public | HTML system monitor (CPU/RAM/disk) |
GET |
/api/system-metrics |
Public | JSON system metrics for the monitor |
POST |
/login |
Public | Placeholder — wire your OIDC/OAuth2 flow here |
POST |
/admin/whitelist?ip=X |
Admin | Add IP to DDoS whitelist |
POST |
/admin/blacklist?ip=X |
Admin | Add IP to DDoS blacklist |
| Prefix | Target | Weight | Strip prefix |
|---|---|---|---|
/api/studio |
http://localhost:3000 |
1 | Yes |
/api/studio2 |
http://localhost:3001 |
1 | Yes |
/api/ai |
http://localhost:8741 |
1 | Yes |
/api/admin |
http://localhost:8242 |
1 | Yes |
/api/core |
http://localhost:8743 |
1 | Yes |
/api/node |
http://localhost:8744 |
1 | Yes |
/api/llm |
http://localhost:8745 |
1 | Yes |
/api/llm2 |
http://localhost:8746 |
1 | Yes |
/api/agent |
http://localhost:8788 |
1 | Yes |
JWT:
curl -H "Authorization: Bearer eyJhbGc..." http://localhost:9777/api/agent/API key:
curl -H "X-API-Key: demo-api-key-123" http://localhost:9777/api/core/Every config value is read from environment variables at boot. See config.go for the authoritative list and defaults.
| Variable | Default | Description |
|---|---|---|
GATEWAY_PORT |
:9777 |
Bind address for the gateway |
JWT_SECRET |
your-secret-key-change-in-production |
Must change before deployment |
LOAD_BALANCING_ALGO |
least-conn |
One of round-robin, least-conn, ip-hash, weighted |
REDIS_ADDR |
localhost:6379 |
Redis address; caching degrades gracefully if unreachable |
CORS_ALLOWED_ORIGINS |
["*"] |
JSON array of allowed origins |
| Variable | Default | Description |
|---|---|---|
MAX_REQUESTS_PER_SECOND |
100 |
Token-bucket refill rate, per IP |
MAX_BURST_SIZE |
200 |
Token-bucket burst size, per IP |
DDOS_THRESHOLD |
1000 |
Requests per minute before an IP is blocked |
BLOCK_DURATION |
10m |
How long an IP stays blocked |
| Variable | Default | Description |
|---|---|---|
HEALTH_CHECK_INTERVAL |
10s |
Per-backend TCP health check cadence |
CONNECTION_TIMEOUT |
30s |
Default per-route timeout (overridable per route) |
MAX_IDLE_CONNS |
100 |
HTTP keep-alive pool size |
IDLE_CONN_TIMEOUT |
90s |
Idle connection reaper |
CIRCUIT_BREAKER_MAX |
5 |
Consecutive failures before the breaker opens |
CIRCUIT_BREAKER_TIMEOUT |
30s |
How long the breaker stays open before half-open |
| Variable | Default | Description |
|---|---|---|
ENABLE_COMPRESSION |
true |
Gzip when the client accepts it |
ENABLE_CACHING |
true |
Redis response cache for GET requests |
CACHE_TTL |
5m |
TTL for cached responses |
Option A — single JSON array:
export SERVICE_ROUTES='[
{"prefix":"/api/studio","target_url":"http://studio:3000","strip_prefix":true,"require_auth":false,"weight":1,"timeout_secs":30},
{"prefix":"/api/agent","target_url":"http://agent:8788","strip_prefix":true,"require_auth":true,"weight":3,"timeout_secs":60}
]'Option B — individual env pairs (one per backend):
export ROUTE_STUDIO_PREFIX=/api/studio
export ROUTE_STUDIO_URL=http://studio:3000
export ROUTE_AGENT_PREFIX=/api/agent
export ROUTE_AGENT_URL=http://agent:8788Routes fall back to built-in defaults (see table above) if neither is set.
va_api_gateway_golang/
│
├── main.go # Gateway struct, ServeHTTP, main(), startup banner
├── main_test.go # Unit tests for gateway components
├── config.go # Env-driven Config + ServiceRoute loader
├── routing.go # Backend · ServicePool · 4 load-balancing algos · ServiceRouter
├── middleware.go # IPRateLimiter · DDoSProtection · CircuitBreaker · JWT/API-key · security headers · request ID
├── handlers.go # Health · stats · admin whitelist/blacklist · cache · compression · Jaeger init
├── metrics.go # Prometheus counters, histograms, gauges
├── logger.go # Structured JSON logging per subsystem
├── dashboard.go # Live TTY dashboard (totals, active conns, backend health)
├── system_monitor.go # /monitor HTML + /api/system-metrics JSON
│
├── Makefile # make build · run · docker-up · k8s-deploy · load-test
├── Dockerfile # Multi-stage with optional nginx sidecar
├── docker-compose.yml # Gateway + nginx + Redis + Prometheus + Grafana + Jaeger
├── .env.development # Dev defaults
├── .env.production # Prod defaults (do not commit secrets)
│
├── nginx/
│ ├── nginx.conf # Edge reverse-proxy config
│ └── static/ # Landing page, 404, 50x
│
├── config/
│ └── prometheus.yml # Scrape config for the gateway + backends
│
├── k8s/
│ ├── deployment.yaml # Deployment · HPA (3-10) · Service · PDB
│ ├── redis.yaml # Redis StatefulSet + PVC
│ └── ingress.yaml # NGINX Ingress with SSL
│
├── scripts/
│ ├── deploy.sh # Automated deployment
│ └── load-test.sh # wrk / ab load test harness
│
├── NGINX_SETUP.md # Edge-proxy configuration guide
├── CHANGELOG.md # Release history (keep-a-changelog)
├── TODO.md # Public roadmap
├── CONTRIBUTORS.md # Primary contributors
└── LICENSE # MIT
# Build & run
make build # compile ./gateway
make run # build + run
# Docker
make docker-build # build image
make docker-up # start the full stack
make docker-logs # tail gateway logs
make docker-down # stop everything
# Kubernetes
make k8s-deploy # apply all manifests
make k8s-status # kubectl get pods,svc
make k8s-logs # follow deployment logs
make k8s-delete # tear down
# Testing
make test # go test ./...
make load-test # wrk-based load test (see scripts/)
# Health & metrics
make health # curl /health | jq
make metrics # curl /metrics- Add the env var pair (or append to
SERVICE_ROUTES):export ROUTE_BILLING_PREFIX=/api/billing export ROUTE_BILLING_URL=http://billing:7000
- Restart the gateway — the router picks it up automatically and begins health-checking.
- (Optional) Extend
loadServiceRoutes()in config.go so the route is part of the built-in defaults.
go test -v -race -cover ./...# Public endpoint, high concurrency
wrk -t10 -c500 -d60s http://localhost:9777/health
# Authenticated, through a proxied route
wrk -t10 -c100 -d30s -H "X-API-Key: demo-api-key-123" http://localhost:9777/api/core/
# DDoS simulation — watch blocked_ips_total tick up
wrk -t50 -c1000 -d15s http://localhost:9777/health- Generate a strong random
JWT_SECRET(32+ bytes) - Replace the demo API keys in middleware.go with a database-backed store
- Terminate TLS at nginx or your load balancer
- Set
CORS_ALLOWED_ORIGINSexplicitly (drop the*default) - Tune
MAX_REQUESTS_PER_SECOND,MAX_BURST_SIZE, andDDOS_THRESHOLDto your actual traffic profile - Whitelist your CI/CD, monitoring, and health-checker IPs (
POST /admin/whitelist) - Run Redis with a password and TLS
- Point Prometheus at
/metricsand Jaeger's agent atlocalhost:6831 - Configure log aggregation (JSON is structured and parse-friendly)
- Enable
liveness+readinessprobes in Kubernetes (already ink8s/deployment.yaml)
Tested on a 2-core, 4 GB VM with Redis on the same host:
| Metric | Value |
|---|---|
| Throughput (public) | ~50,000 req/sec |
| Throughput (with JWT) | ~30,000 req/sec |
| Latency p50 | ~2 ms |
| Latency p95 | ~10 ms |
| Latency p99 | ~25 ms |
| Memory | ~100 MB base + ~1 MB per 1,000 connections |
| CPU | ~30 % at 10k req/sec/core |
lsof -i :9777
docker-compose logs gatewaycurl http://localhost:9777/health | jq '.routes[].backends'
# Check the backend directly
curl http://localhost:3000/health- Raise
MAX_REQUESTS_PER_SECONDandMAX_BURST_SIZE - Whitelist any trusted IPs (
POST /admin/whitelist?ip=X)
- All backends in the matched pool are down or the circuit breaker is open
- Check
/statsfor the circuit breaker state and/healthfor backend liveness
- Caching auto-disables; the gateway keeps serving
- Check
REDIS_ADDRand network policy
We welcome contributions! This is a public, MIT-licensed gateway — bug reports, perf improvements, docs, and new features are all appreciated.
- Fork the repository
- Create your feature branch:
git checkout -b feature/amazing-feature - Commit your changes:
git commit -m 'feat: add amazing feature' - Push to your branch:
git push origin feature/amazing-feature - Open a Pull Request
See TODO.md for the public roadmap and CONTRIBUTORS.md for the core team.
- Replace in-memory API key list with a pluggable backend (Postgres / Redis / file)
- Add WebSocket proxying support (currently HTTP/1.1 only)
- mTLS support between gateway and upstreams
- OAuth2 / OIDC login flow on
/login - Per-route body-size limits and request-body validation hooks
- Grafana dashboard JSON for the Prometheus metrics
- Keep the gateway a single binary with no runtime dependencies (Redis stays optional)
- Every feature gets a Prometheus metric or a log line — ideally both
- New env vars must have sensible defaults in
LoadConfig() - Run
go vet ./...andgo test -race ./...before opening a PR
- Report vulnerabilities privately to the maintainers listed in CONTRIBUTORS.md — do not open a public GitHub issue for security bugs.
- Always rotate
JWT_SECRETand API keys when staff changes hands. - Consider running the gateway behind a WAF (AWS WAF, Cloudflare) for Layer 7 attacks that pre-date connection-level rate limiting.
MIT License — see LICENSE for details.
Free to use in personal and commercial projects.
Built with Go, Prometheus, Redis, and Jaeger
Maintained by prodxcloud and joelwembo — see CONTRIBUTORS.md