Quick Start
Install A3S Gateway (v1.0.11) and run a minimal reverse proxy to an LLM/HTTP backend
Quick Start
This guide gets A3S Gateway v1.0.11 running as a reverse proxy in front of an LLM/HTTP backend. Everything here is copy-pasteable and runs as written.
The gateway is a single statically-linked binary (a3s-gateway). Configuration
is a Traefik-style entrypoint → router → middleware → service model written
in ACL. Config files must use the .acl extension — HCL/TOML/YAML/JSON
files are rejected at load.
Install
Pick one. Homebrew and Cargo install the native a3s-gateway binary; Docker
runs the published image from GitHub Container Registry.
Homebrew (macOS)
brew install a3s-lab/tap/a3s-gatewayCargo
cargo install a3s-gatewayBuilding from source requires Rust 1.88 or newer (the current MSRV).
Docker (ghcr.io)
The published Linux images build with the kube and redis cargo features
enabled (Kubernetes provider + distributed Redis rate limiting). The default
cargo install / brew builds enable no extra features.
docker run -d \
-v $(pwd)/gateway.acl:/etc/a3s-gateway/gateway.acl \
-p 8080:8080 \
ghcr.io/a3s-lab/gateway:latest \
--config /etc/a3s-gateway/gateway.aclMinimal Configuration
Create a gateway.acl with one entrypoint, one router, and one service that
proxies to an LLM/HTTP backend.
# gateway.acl — proxy /v1 traffic to a local LLM backend
entrypoints "web" {
address = "0.0.0.0:8080"
}
routers "llm" {
rule = "PathPrefix(`/v1`)"
service = "llm-backend"
entrypoints = ["web"]
}
services "llm-backend" {
load_balancer {
strategy = "round-robin"
request_timeout = "60s"
servers = [
{ url = "http://127.0.0.1:8001" },
]
}
}How this maps to the schema:
entrypoints— a named network listener.address(host:port) is required;protocoldefaults tohttp(alsotcp/udp). Listening on0.0.0.0:8080matches the published Docker image, which exposes8080.routers—ruleandserviceare required. The rule uses backtick arguments and the&&combinator (Host,Path,PathPrefix,Method,Headers).PathPrefixis a rawstarts_with, soPathPrefix(/v1)matches/v1,/v1/chat/completions, and/v1xyz. An emptyentrypointslist (or omitting it) binds the router to all entrypoints.services— wraps aload_balancer.request_timeoutdefaults to"30s"; LLM responses can be slow, so this example raises it to"60s". Durations acceptms/s/msuffixes (a bare number means seconds); empty,0s, or non-numeric values are rejected at load. A service must have at least oneserver(or a revision) or validation fails.
The router
serviceandentrypoints, and each middleware name, are cross-validated at load — a reference to a name that does not exist is a hard config error.
Start the Gateway
# Start with the ACL config (defaults to ./gateway.acl if --config is omitted)
a3s-gateway --config gateway.acl
# Start with debug logging
a3s-gateway --config gateway.acl --log-level debug
# Validate the configuration without starting
a3s-gateway validate --config gateway.aclInspect what was parsed without binding any sockets:
a3s-gateway config --config gateway.acl summary
a3s-gateway config --config gateway.acl entrypoints
a3s-gateway config --config gateway.acl routes
a3s-gateway config --config gateway.acl services
a3s-gateway config --config gateway.acl middlewares
a3s-gateway config --config gateway.acl jsonVerify
Send a request through the gateway. The router matches PathPrefix(/v1) and
forwards to llm-backend, which proxies to http://127.0.0.1:8001:
curl -i http://localhost:8080/v1/modelsIf the backend is reachable you get its response. The gateway always adds
X-Forwarded-For, X-Forwarded-Host, X-Forwarded-Proto, and
X-Forwarded-Port to the upstream request (this is unconditional — there are no
config keys for forwarded headers).
A POST works the same way — paths and bodies stream straight through:
curl -i http://localhost:8080/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model":"my-model","messages":[{"role":"user","content":"ping"}]}'If no backend is healthy you get 502; an upstream that exceeds
request_timeout returns 504.
Secrets via env()
Any string-valued ACL attribute can read from the environment with env("VAR").
A missing variable is a config error. This is the only function call supported in
ACL values.
services "llm-backend" {
load_balancer {
servers = [
{ url = env("LLM_UPSTREAM_URL") },
]
}
}export LLM_UPSTREAM_URL="http://127.0.0.1:8001"
a3s-gateway --config gateway.aclNext Steps
- Routing —
Host/Path/PathPrefix/Method/Headersmatchers, priority, and most-specific-wins ordering: see Routing. - Middleware —
rate-limit,jwt,api-key,cors,circuit-breaker, and more, attached viamiddlewares = [...]: see Middleware. - Services — load-balancing strategies, active/passive health checks, sticky sessions, failover, autoscaling, and revision traffic splitting: see Services.
- Deployment — Helm, Docker, and systemd: see Deployment.
- Observability & management API — Prometheus metrics and the dedicated management listener: see Observability.