Repository navigation
Deploy Reverse Proxy
This page tells you how to put the Streamable HTTP server of the SDK behind a reverse proxy, such as nginx or Caddy. The proxy terminates TLS and forwards each request to the SDK on the loopback interface. Three settings are important: the Host and Origin checks, the buffering of server-sent events and the time limits. The examples are a start for your own configuration. They are not a complete configuration.
Applies to: MCP 2026-07-28, Zig 0.16.0, zig-sdk 0.1.0. The cancellation at a disconnect, the default request timeout, the rate limits, DPoP and the WebSocket transport: zig-sdk 0.2.0.
The SDK server binds to 127.0.0.1 and serves plain HTTP. The proxy accepts HTTPS on the public address and forwards the requests to the SDK. Only the proxy can connect to the SDK port.
The SDK can also serve HTTPS itself with the tls option. Then you do not need a proxy. See TLS and Certificates.
The SDK server compares the Host header and the Origin header with two lists before it reads the body1. These checks prevent DNS rebinding attacks. A request that fails a check gets 403.
-
allowed_hosts: when this list is empty, the server accepts the loopback names and the bound address. When the list has names, the server accepts only these names. The port does not count. -
allowed_origins: when this list is empty, the server accepts loopback origins only. A request without anOriginheader passes this check.
The proxy changes what the SDK sees:
| Proxy |
Host at the SDK |
What to configure |
|---|---|---|
| nginx with its defaults | The address in proxy_pass, such as 127.0.0.1:3000
|
Nothing for Host, when allowed_hosts is empty. |
nginx with proxy_set_header Host $host
|
The public name | Add the public name to allowed_hosts. |
| Caddy with its defaults | The public name | Add the public name to allowed_hosts. |
Both proxies forward the Origin header of a browser without a change. When browser clients use the server, add their exact origins to allowed_origins, such as https://app.example.com. Do not add a wildcard.
var http_server: mcp.transport.http.Server = .init(io, gpa, &server, .{
.address = "127.0.0.1",
.port = 3000,
.allowed_hosts = &.{"mcp.example.com"},
.allowed_origins = &.{"https://app.example.com"},
});A proxy that keeps a response in its buffer holds the events of a stream. The client then gets progress notifications late, and a listen stream seems to stop. The SDK sends two headers on each SSE response to prevent this:
-
x-accel-buffering: notells nginx not to keep this response in its buffer. -
cache-control: no-cache, no-transformtells caches and proxies not to store or change the stream.
Caddy sends each part of a text/event-stream response at once. For nginx, we recommend that you also set proxy_buffering off on the MCP location.
A listen stream can stay open for hours. When the keepalive option is on, the SDK sends an SSE comment every limits.http.sse_keepalive (15 s) on each listen stream. Set the read timeout of the proxy above this interval.
A tool call without notifications sends no bytes until its response. The SDK client cancels a request without a timeout after limits.request_timeout (60 s). A timeout can be up to limits.max_total_timeout (600 s). Set the read timeout of the proxy above the longest time that you permit for a tool.
When the proxy closes the connection to the SDK, for example at its read timeout, the SDK cancels the request.
The SDK closes a keep-alive connection without a new request after limits.http.idle_timeout (60 s). Set the keep-alive time of the proxy for its connections to the SDK below this value. Else the proxy can send a request on a connection that the SDK closes at the same time.
- The body limit of the SDK is
limits.http.max_body_bytes(4 MiB). The default body limit of nginx is 1 MiB. Setclient_max_body_size 4m. - The proxy must forward the
Authorization,DPoP,MCP-Protocol-Version,Mcp-Method,Mcp-NameandMcp-Param-*headers without a change. Both proxies forward them by default. - With the
authoption, the SDK serves the protected resource metadata at/.well-known/oauth-protected-resource/mcp2. The proxy must forward this path too. Theresourceandresource_metadata_urlfields ofResourceServermust use the public URL, not the loopback URL. WithResourceServer.dpop, thehtuclaim of each DPoP proof3 must be equal toresource. - The SDK does not read
X-Forwarded-FororForwarded. Log the client address in the proxy. - The rate limits use the IP address of the caller when the request has no principal. Behind a proxy, all these callers have the address of the proxy and share one bucket. See Limits and Security.
- The gRPC transport needs a proxy that forwards HTTP/2 from end to end, such as nginx with
grpc_pass. See Transport gRPC. - The WebSocket transport needs a proxy that forwards the upgrade request. With nginx, set
proxy_http_version 1.1and forward theUpgradeandConnectionheaders. See Transport WebSocket.
server {
listen 443 ssl;
http2 on;
server_name mcp.example.com;
ssl_certificate /etc/ssl/mcp.example.com.crt;
ssl_certificate_key /etc/ssl/mcp.example.com.key;
ssl_protocols TLSv1.3;
location = /mcp {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_http_version 1.1;
proxy_buffering off;
proxy_read_timeout 3600s;
client_max_body_size 4m;
}
location = /.well-known/oauth-protected-resource/mcp {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
}
}The example sends the public name in Host. Thus add mcp.example.com to allowed_hosts, as in the Zig example above.
mcp.example.com {
reverse_proxy /mcp 127.0.0.1:3000
reverse_proxy /.well-known/oauth-protected-resource/mcp 127.0.0.1:3000
}Caddy gets a certificate for the name and terminates TLS. Its default read timeout has no limit. With Caddy, add mcp.example.com to allowed_hosts.
-
MCP-HTTP-2026-07-28: Streamable HTTP transport, with the security warning about the
Originheader. - RFC9728: OAuth 2.0 Protected Resource Metadata.
- RFC9449: OAuth 2.0 DPoP.
zig-sdk uses the Apache License 2.0. The wiki uses the project profile of ASD-STE100 Simplified Technical English. The Bibliography lists the sources.
zig-sdk
- Home
- Getting Started
- Concepts
- Server Guide
- Client Guide
- Transports
- Transport stdio
- Transport Streamable HTTP
- Transport gRPC
- Transport Unix Socket
- Transport WebSocket
- TLS and Certificates
- Deploy Reverse Proxy
- Authorization
- Authorization Server
- JSON Schema Support
- Resource Templates
- Tasks Extension
- Skills Extension
- Apps Extension
- Conformance
- Examples
- API Reference
- Interoperability
- Threat Model
- Limits and Security
- Deprecated Features
- Roadmap
- Release Notes
- Versioning
- Contributing
- FAQ
Style and words