For the complete documentation index, see llms.txt. Markdown versions of all docs pages are available by appending .md to any docs URL.
Egress proxy
Run agentgateway as an HTTP CONNECT proxy to control which destinations your clients can reach.
Agentgateway can act as an HTTP CONNECT proxy for outbound traffic. Clients point their proxy settings at agentgateway, and agentgateway decides which destinations they are allowed to reach.
About the egress proxy
A client selects the proxy through the standard environment variable that most tools read.
export HTTPS_PROXY=http://127.0.0.1:3000The client then sends CONNECT <host>:443 to agentgateway instead of connecting to the destination directly. What agentgateway does with that request depends on which of the two CONNECT handling modes you configure.
| Area | Tunnel mode | Route mode |
|---|---|---|
| Configuration | tunnelProtocol: connect on a bind | frontendPolicies.connect.mode: route |
| How a destination is matched | TLS SNI, through tcpRoutes hostnames | The CONNECT authority, through route hostnames |
| HTTP policies apply to the CONNECT request | No | Yes |
| Client authentication | Not available | Available, through basicAuth |
| A blocked destination fails with | A TLS error, after the tunnel is established | A 404 response to the CONNECT request |
| Can terminate TLS for selected hostnames | Yes | No |
Neither mode is a replacement for the other. Tunnel mode gives you SNI-based control and the option to terminate TLS, and route mode gives you the HTTP policy engine, including client authentication.
Note
Policies that you attach to a listener on a tunnelProtocol: connect bind are not applied to the CONNECT request, because tunnel mode handles the request before route matching. To authenticate clients, use route mode.
Before you begin
Install theagentgateway binary.Allowlist destinations without decrypting traffic
Use this pattern when you need to restrict outbound HTTPS by hostname and the traffic must stay encrypted end to end. Agentgateway reads the TLS SNI hostname for routing and forwards the encrypted connection without decrypting it.
Create a configuration file that accepts CONNECT requests on port 3000 and allowlists destinations by hostname.
cat <<EOF > egress.yaml # yaml-language-server: \$schema=https://agentgateway.dev/schema/config binds: - port: 3000 tunnelProtocol: connect listeners: [] gateways: secure: port: 443 listeners: - name: public-egress hostname: "*" protocol: TLS tcpRoutes: - name: public-egress-allowlist gateways: secure/public-egress hostnames: - pypi.org - files.pythonhosted.org backends: - dynamic: {} EOFReview the following table to understand this configuration.
Setting Description binds[].tunnelProtocolSet to connectso that the bind accepts HTTP CONNECT requests. The other values aredirect, which is the default,proxy,hboneWaypoint, andhboneGateway.binds[].listenersEmpty, because the tunneled traffic is handled by the gatewaysentry rather than by a listener on this bind.gateways.secure.listeners[].protocolSet to TLSso that agentgateway inspects the SNI hostname without terminating the connection.tcpRoutes[].hostnamesThe destinations that clients are allowed to reach. A destination that matches no entry has no route. backends[].dynamicSends the connection to the hostname from SNI, on the original destination port. Start agentgateway.
agentgateway -f egress.yamlIn another terminal, verify that an allowed destination works.
curl --proxy http://127.0.0.1:3000 --head https://pypi.org/Example output:
HTTP/1.1 200 OKVerify that a destination that is not in the allowlist is refused.
curl --proxy http://127.0.0.1:3000 --head https://example.com/Example output:
curl: (35) LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to example.com:443Important
The CONNECT tunnel is established before the destination is checked, so the client sees a TLS error rather than a refusal from the proxy. To refuse a blocked destination at the proxy instead, use route mode, described in Require client authentication.
Package managers often download from several hostnames. Include artifact and redirect destinations, such as files.pythonhosted.org, not only the primary site.
Send a permitted hostname to a different destination
To redirect an allowed hostname, set a TCP dynamic target expression. The expression must return a host:port string.
tcpRoutes:
- hostnames:
- pypi.org
backends:
- dynamic:
target: 'destination.hostname == "pypi.org" ? "mirror.example.com:8443" : destination.hostname + ":" + string(destination.port)'TCP target expressions can use destination.hostname, destination.address, destination.port, and source.*. This form applies to tcpRoutes only. A dynamic backend is not accepted as the destination for a policy call.
Require client authentication
Use this pattern when only known clients may use the proxy. Route mode sends the CONNECT request through normal route matching, so HTTP policies such as basicAuth apply to it.
Create a user database in htpasswd format.
printf 'egressuser:%s\n' "$(openssl passwd -apr1 'secretpw')" > htpasswd.txtCreate a configuration file that enables route mode and requires credentials.
cat <<EOF > egress-auth.yaml # yaml-language-server: \$schema=https://agentgateway.dev/schema/config frontendPolicies: connect: mode: route binds: - port: 3000 listeners: - name: connect protocol: HTTP policies: basicAuth: htpasswd: file: htpasswd.txt mode: strict realm: agentgateway-egress authorizationLocation: header: name: proxy-authorization prefix: "Basic " routes: - name: egress-allowlist hostnames: - pypi.org - files.pythonhosted.org backends: - dynamic: {} EOFReview the following table to understand this configuration.
Setting Description frontendPolicies.connect.modeSet to routeso that CONNECT requests go through route matching. The other values aretunnel, which behaves like tunnel mode, anddeny, which refuses CONNECT requests.basicAuth.htpasswdThe user database, either inline or loaded from a file. basicAuth.modeSet to strictto require valid credentials. The default isoptional, which validates credentials when a client sends them but allows requests that omit them.basicAuth.realmThe realm that agentgateway returns in the challenge. basicAuth.authorizationLocationSet to the proxy-authorizationheader. Reading the credential from this header is what makes agentgateway answer with407andProxy-Authenticateinstead of401andWWW-Authenticate.routes[].hostnamesThe destinations that clients are allowed to reach, matched against the CONNECT authority. Start agentgateway.
agentgateway -f egress-auth.yamlVerify that a request without credentials is challenged. The
--verboseflag is required, because the proxy response is not the response thatcurlreports.curl --verbose --proxy http://127.0.0.1:3000 --head https://pypi.org/Example output:
* CONNECT tunnel: HTTP/1.1 negotiated < HTTP/1.1 407 Proxy Authentication Required < proxy-authenticate: Basic realm="agentgateway-egress" * CONNECT tunnel failed, response 407Verify that a request with credentials reaches an allowed destination.
curl --proxy http://127.0.0.1:3000 --proxy-user egressuser:secretpw --head https://pypi.org/Example output:
HTTP/1.1 200 OKVerify that a destination that is not in the allowlist is refused, even for an authenticated client. Agentgateway returns
404to the CONNECT request, because no route matches the destination.curl --verbose --proxy http://127.0.0.1:3000 --proxy-user egressuser:secretpw --head https://example.com/Example output:
* CONNECT tunnel: HTTP/1.1 negotiated < HTTP/1.1 404 Not Found * CONNECT tunnel failed, response 404
Tip
A proxy response is not the response that curl reports. When the proxy refuses a request, curl writes 000 for the HTTP status and exits with code 56, so the 407 and the 404 are visible only with --verbose.
Terminate TLS for selected hostnames
Use this pattern when most destinations must stay encrypted end to end, but agentgateway must serve selected hostnames itself. This pattern uses tunnel mode, and adds HTTPS listeners for the hostnames that agentgateway terminates.
Generate a certificate authority and a certificate for the hostnames that agentgateway serves.
openssl req -x509 -newkey rsa:2048 -nodes -keyout ca-key.pem -out ca-cert.pem -days 30 \ -subj "/CN=agentgateway egress CA" openssl req -newkey rsa:2048 -nodes -keyout key.pem -out csr.pem -subj "/CN=egress" printf "subjectAltName=DNS:llm.example.com,DNS:static.example.com\n" > san.ext openssl x509 -req -in csr.pem -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial \ -out cert.pem -days 30 -extfile san.extWarning
This certificate authority is for local testing. Do not distribute it outside your test environment. Generate and protect a certificate authority that is appropriate for your environment.
Create a configuration file that terminates TLS for two hostnames and passes every other allowed hostname through.
cat <<EOF > egress-conditional.yaml # yaml-language-server: \$schema=https://agentgateway.dev/schema/config binds: - port: 3000 tunnelProtocol: connect listeners: [] gateways: secure: port: 443 listeners: - name: llm hostname: llm.example.com protocol: HTTPS tls: cert: cert.pem key: key.pem - name: direct-response hostname: static.example.com protocol: HTTPS tls: cert: cert.pem key: key.pem - name: public-egress hostname: "*" protocol: TLS tcpRoutes: - name: public-egress-allowlist gateways: secure/public-egress hostnames: - pypi.org - files.pythonhosted.org backends: - dynamic: {} routes: - name: static-response gateways: secure/direct-response policies: directResponse: status: 200 body: hello from agentgateway llm: gateways: secure/llm models: - name: smart provider: openAI params: model: gpt-5.5 EOFThe listener with the
*hostname matches last, so the two named hostnames are terminated and every other allowed hostname passes through encrypted.Start agentgateway. The LLM routes need a provider key to be present, but listing models is handled locally and does not call the provider.
OPENAI_API_KEY=dummy agentgateway -f egress-conditional.yamlVerify that an allowlisted public destination still passes through unchanged.
curl --proxy http://127.0.0.1:3000 --head https://pypi.org/Example output:
HTTP/1.1 200 OKVerify the hostname that returns a configured response.
curl --proxy http://127.0.0.1:3000 --cacert ca-cert.pem https://static.example.com/Example output:
hello from agentgatewayVerify that the LLM hostname serves the configured model catalog.
curl --proxy http://127.0.0.1:3000 --cacert ca-cert.pem https://llm.example.com/v1/modelsExample output:
{"data":[{"id":"smart","object":"model","created":1787773708,"owned_by":"openai"}],"object":"list"}
Inspect all HTTPS traffic
Use this pattern when agentgateway must apply HTTP policies to every outbound request. Agentgateway issues a certificate for each requested hostname from a certificate authority that you supply, then opens a separate TLS connection to the destination.
Create a configuration file that terminates every HTTPS stream with a dynamic certificate.
cat <<EOF > egress-inspect.yaml # yaml-language-server: \$schema=https://agentgateway.dev/schema/config binds: - port: 3000 tunnelProtocol: connect listeners: [] - port: 443 mode: internal listeners: - protocol: HTTPS tls: mode: dynamicCa cert: ca-cert.pem key: ca-key.pem routes: - backends: - dynamic: {} policies: backendTLS: {} policies: transformations: request: set: x-agentgateway-req-message: "'Hello from agentgateway!'" response: set: x-agentgateway-resp-message: "'Hello from agentgateway!'" EOFReview the following table to understand this configuration.
Setting Description binds[].modeSet to internalso that the bind routes traffic without opening a listener socket on port 443. The default isstandard, which binds the port.tls.modeSet to dynamicCaso thatcertandkeyare treated as a certificate authority that issues a leaf certificate for each requested hostname. The default isstatic, which treats them as the leaf certificate.backendTLSAdds TLS back to the outgoing request, because agentgateway terminated the client connection. transformationsIncluded to show that HTTP policies now apply. Replace this policy with the policies that you need. Start agentgateway.
agentgateway -f egress-inspect.yamlSend a request to an endpoint that echoes request headers, and trust the certificate authority.
curl --proxy http://127.0.0.1:3000 --cacert ca-cert.pem --include https://httpbingo.org/headersThe response headers include the header that the response policy adds:
x-agentgateway-resp-message: Hello from agentgateway!The response body shows the header that the request policy added to the upstream request:
"X-Agentgateway-Req-Message": [ "Hello from agentgateway!" ]
Warning
This configuration has no hostname allowlist, so it permits every HTTPS destination. To restrict destinations as well, add route hostnames entries, or combine this pattern with one of the allowlist patterns on this page.
Choose a pattern
- To restrict outbound traffic by hostname and keep it encrypted, use Allowlist destinations without decrypting traffic.
- To restrict who may use the proxy, use Require client authentication. This pattern also refuses a blocked destination at the proxy rather than through a TLS error.
- To serve selected hostnames from agentgateway while everything else passes through, use Terminate TLS for selected hostnames.
- To apply HTTP policies to every outbound request, use Inspect all HTTPS traffic.
Run one configuration at a time. Each configuration on this page listens for proxy requests on port 3000.