Traffic Plane Configuration
Gordon models network listeners as a traffic graph: entrypoints receive packets or connections, routers select a service, and services resolve to backends.
Smart TCP Edge Entrypoint
Public application traffic is normally exposed with a smart_tcp entrypoint. The conventional route-capable entrypoint name is edge, but Gordon does not require that name or assign a built-in public port. When exactly one route-capable smart_tcp or tls_mux entrypoint exists, app HTTP hosts use it even if it has a custom name. Choose the address that matches your deployment, firewall, and container mapping:
[entrypoints.edge]
address = ":443"
protocol = "smart_tcp"
trusted_cidrs = []
Do not treat entrypoints.edge.address as an HTTP port or an HTTPS port. It is one TCP socket that sniffs each new connection and dispatches the original byte stream.
Supported entrypoint protocols are smart_tcp, tls_mux, tcp, and udp. smart_tcp is the primary public edge model; tls_mux can also serve app HTTP hosts through TLS fallback, tcp and udp are for explicit L4 services, and UDP remains separate from the TCP entrypoint.
Smart TCP Dispatch Order
For each accepted connection on a smart_tcp entrypoint Gordon:
- Applies entrypoint-wide
trusted_cidrs to the peer socket IP.
- Rejects PROXY protocol v1 and v2 prefixes. PROXY headers are not trusted or parsed.
- Peeks a minimal prefix and replays the bytes to the selected handler/backend.
- Dispatches cleartext HTTP/1.x and h2c prior-knowledge to Gordon's HTTP handler.
- Dispatches TLS ClientHello traffic by SNI:
- matching TLS passthrough router -> raw TCP backend without TLS termination;
- no passthrough match -> normal Gordon HTTPS fallback.
- Dispatches unknown non-HTTP/non-TLS bytes only to an explicit raw fallback router, after raw-fallback source policy allows it.
- Refuses the connection when no deterministic handler or allowed fallback exists.
Malformed HTTP-looking or TLS-looking traffic is rejected instead of bypassing to raw fallback.
Standalone and Network Services
Standalone services are Gordon-managed containers that L4 routers can target with service:<service>:<port-name>. Define them under [[services]] when Gordon should own the container lifecycle.
[[services]]
name = "rust"
image = "registry.example.com:5000/rust:latest"
enabled = true
[[services.ports]]
name = "game"
container = 28015
protocol = "udp"
publish = "127.0.0.1:38015"
[entrypoints.rust]
address = "0.0.0.0:28015"
protocol = "udp"
[[traffic.udp.routers]]
name = "rust-game"
entrypoint = "rust"
service = "service:rust:game"
Network services describe manually managed non-HTTP backends that L4 routers can target.
[[network_services]]
name = "postgres"
[[network_services.ports]]
name = "db"
container = 5432
protocol = "tcp"
Service references use:
route:<domain> for app HTTP hosts from ACTIVE state (merged with installation external routes)
external_route:<domain> for configured external HTTP routes
network_service:<service>:<port-name> for manually managed TCP, UDP, and TLS passthrough backends
service:<service>:<port-name> for Gordon-managed standalone service backends
static:<name> is reserved and currently unsupported.
TLS Passthrough on Smart TCP
TLS passthrough routers run on smart_tcp or tls_mux entrypoints and route by ClientHello SNI without terminating TLS.
[entrypoints.edge]
address = ":443"
protocol = "smart_tcp"
[[traffic.tls.routers]]
name = "raw-tls"
entrypoint = "edge"
sni = "raw.example.com"
service = "network_service:raw:tls"
Exact SNI matches win over wildcard matches. Ambiguous wildcard overlaps and HTTP-host/TLS-passthrough conflicts on the same smart TCP entrypoint are rejected at validation time. App HTTPS hosts that do not match a passthrough SNI use Gordon's normal HTTPS fallback and certificate selection.
Raw TCP Fallback
Raw fallback is disabled by default. Unknown non-HTTP/non-TLS bytes reach a TCP backend only when the smart TCP entrypoint explicitly names one TCP router as raw_fallback and the source policy allows the peer socket IP.
[entrypoints.edge]
address = ":443"
protocol = "smart_tcp"
raw_fallback = "ssh-fallback"
raw_fallback_trusted_cidrs = ["100.64.0.0/10"]
[[traffic.tcp.routers]]
name = "ssh-fallback"
entrypoint = "edge"
service = "network_service:ssh:ssh"
Use raw_fallback_trusted_cidrs for private raw fallback exposure. To expose raw fallback publicly, acknowledge it explicitly:
[entrypoints.edge]
address = ":443"
protocol = "smart_tcp"
raw_fallback = "public-raw"
allow_public_raw_fallback = true
trusted_cidrs and raw_fallback_trusted_cidrs both use the peer socket IP. They do not use X-Forwarded-For.
HTTP Timeouts
The HTTP servers behind smart_tcp and tls_mux entrypoints serve apps and the admin API. They do not use a fixed read or write timeout, so a download or upload that keeps moving data is never cut for taking too long. A connection is cut only after transfer_idle_timeout passes with no data in the direction being waited on.
[traffic.http]
read_header_timeout = "5s"
idle_timeout = "30s"
transfer_idle_timeout = "2m"
| Option |
Env |
Default |
Description |
traffic.http.read_header_timeout |
GORDON_TRAFFIC_HTTP_READ_HEADER_TIMEOUT |
"5s" |
Maximum time to read request headers |
traffic.http.idle_timeout |
GORDON_TRAFFIC_HTTP_IDLE_TIMEOUT |
"30s" |
Maximum keep-alive idle time between requests |
traffic.http.transfer_idle_timeout |
GORDON_TRAFFIC_HTTP_TRANSFER_IDLE_TIMEOUT |
"2m" |
Inactivity timeout for request bodies and responses |
Values are Go duration strings; malformed, zero, or negative values are rejected when the configuration loads. Servers pick up changes when the traffic graph is next applied (reload); connections already in flight keep their previous timeout.
Inactivity timeout
Each request has an inactivity timer, armed when the request starts. Progress resets it: a response chunk accepted by the client connection (8 KiB granularity), a flush, or request body bytes read. If transfer_idle_timeout passes without progress, Gordon cancels the request context, which aborts the upstream request of a reverse-proxied app, and closes the client connection. When the handler returns, the final response flush and the drain of an unread request body are bounded by the same duration. Consequences:
- a response that streams data at least once per
transfer_idle_timeout can last arbitrarily long, and a client that keeps reading is never cut (it must accept 8 KiB per transfer_idle_timeout, about 68 B/s at the 2 minute default);
- an upload whose body arrives at least once per
transfer_idle_timeout can last arbitrarily long;
- a backend that stays silent longer than
transfer_idle_timeout before or between response chunks has its connection cut, so set it above the slowest expected backend response time;
- the timer also fires when a handler or backend is silent and no write is pending;
- WebSocket and other upgraded connections are not subject to it, and HTTP/2 streams are handled the same way as HTTP/1.
transfer_idle_timeout does not apply to [traffic.tcp] or [traffic.udp] routers; those use their own idle_timeout (see below).
TCP Routers
Plain TCP entrypoints support one TCP router per entrypoint. TCP routers on a smart_tcp entrypoint are valid only when referenced by that entrypoint's raw_fallback.
[entrypoints.postgres]
address = "0.0.0.0:5432"
protocol = "tcp"
trusted_cidrs = ["100.64.0.0/10"]
[traffic.tcp]
dial_timeout = "10s"
idle_timeout = "5m"
drain_timeout = "30s"
max_connections = 1024
[[traffic.tcp.routers]]
name = "postgres"
entrypoint = "postgres"
service = "network_service:postgres:db"
If max_connections is omitted or set to 0, Gordon applies the safe runtime default of 1024 active connections per entrypoint.
UDP Routers
UDP is not sniffed or unified into smart TCP. Use a separate UDP entrypoint:
[entrypoints.game]
address = "0.0.0.0:7777"
protocol = "udp"
[traffic.udp]
idle_timeout = "30s"
drain_timeout = "30s"
max_sessions = 4096
[[traffic.udp.routers]]
name = "game"
entrypoint = "game"
service = "network_service:game:game"
UDP sessions are keyed by client address and expire after idle_timeout. If max_sessions is omitted or set to 0, Gordon applies the safe runtime default of 4096 active sessions per entrypoint.
source
1# Traffic Plane Configuration
2
3Gordon models network listeners as a traffic graph: entrypoints receive packets or connections, routers select a service, and services resolve to backends.
4
5## Smart TCP Edge Entrypoint
6
7Public application traffic is normally exposed with a `smart_tcp` entrypoint. The conventional route-capable entrypoint name is `edge`, but Gordon does not require that name or assign a built-in public port. When exactly one route-capable `smart_tcp` or `tls_mux` entrypoint exists, app HTTP hosts use it even if it has a custom name. Choose the address that matches your deployment, firewall, and container mapping:
8
9```toml
10[entrypoints.edge]
11address = ":443"
12protocol = "smart_tcp"
13trusted_cidrs = []
14```
15
16Do not treat `entrypoints.edge.address` as an HTTP port or an HTTPS port. It is one TCP socket that sniffs each new connection and dispatches the original byte stream.
17
18Supported entrypoint protocols are `smart_tcp`, `tls_mux`, `tcp`, and `udp`. `smart_tcp` is the primary public edge model; `tls_mux` can also serve app HTTP hosts through TLS fallback, `tcp` and `udp` are for explicit L4 services, and UDP remains separate from the TCP entrypoint.
19
20## Smart TCP Dispatch Order
21
22For each accepted connection on a `smart_tcp` entrypoint Gordon:
23
241. Applies entrypoint-wide `trusted_cidrs` to the peer socket IP.
252. Rejects PROXY protocol v1 and v2 prefixes. PROXY headers are not trusted or parsed.
263. Peeks a minimal prefix and replays the bytes to the selected handler/backend.
274. Dispatches cleartext HTTP/1.x and h2c prior-knowledge to Gordon's HTTP handler.
285. Dispatches TLS ClientHello traffic by SNI:
29 - matching TLS passthrough router -> raw TCP backend without TLS termination;
30 - no passthrough match -> normal Gordon HTTPS fallback.
316. Dispatches unknown non-HTTP/non-TLS bytes only to an explicit raw fallback router, after raw-fallback source policy allows it.
327. Refuses the connection when no deterministic handler or allowed fallback exists.
33
34Malformed HTTP-looking or TLS-looking traffic is rejected instead of bypassing to raw fallback.
35
36## Standalone and Network Services
37
38Standalone services are Gordon-managed containers that L4 routers can target with `service:<service>:<port-name>`. Define them under `[[services]]` when Gordon should own the container lifecycle.
39
40```toml
41[[services]]
42name = "rust"
43image = "registry.example.com:5000/rust:latest"
44enabled = true
45
46[[services.ports]]
47name = "game"
48container = 28015
49protocol = "udp"
50publish = "127.0.0.1:38015"
51
52[entrypoints.rust]
53address = "0.0.0.0:28015"
54protocol = "udp"
55
56[[traffic.udp.routers]]
57name = "rust-game"
58entrypoint = "rust"
59service = "service:rust:game"
60```
61
62Network services describe manually managed non-HTTP backends that L4 routers can target.
63
64```toml
65[[network_services]]
66name = "postgres"
67
68[[network_services.ports]]
69name = "db"
70container = 5432
71protocol = "tcp"
72```
73
74Service references use:
75
76- `route:<domain>` for app HTTP hosts from ACTIVE state (merged with installation external routes)
77- `external_route:<domain>` for configured external HTTP routes
78- `network_service:<service>:<port-name>` for manually managed TCP, UDP, and TLS passthrough backends
79- `service:<service>:<port-name>` for Gordon-managed standalone service backends
80
81`static:<name>` is reserved and currently unsupported.
82
83## TLS Passthrough on Smart TCP
84
85TLS passthrough routers run on `smart_tcp` or `tls_mux` entrypoints and route by ClientHello SNI without terminating TLS.
86
87```toml
88[entrypoints.edge]
89address = ":443"
90protocol = "smart_tcp"
91
92[[traffic.tls.routers]]
93name = "raw-tls"
94entrypoint = "edge"
95sni = "raw.example.com"
96service = "network_service:raw:tls"
97```
98
99Exact SNI matches win over wildcard matches. Ambiguous wildcard overlaps and HTTP-host/TLS-passthrough conflicts on the same smart TCP entrypoint are rejected at validation time. App HTTPS hosts that do not match a passthrough SNI use Gordon's normal HTTPS fallback and certificate selection.
100
101## Raw TCP Fallback
102
103Raw fallback is disabled by default. Unknown non-HTTP/non-TLS bytes reach a TCP backend only when the smart TCP entrypoint explicitly names one TCP router as `raw_fallback` and the source policy allows the peer socket IP.
104
105```toml
106[entrypoints.edge]
107address = ":443"
108protocol = "smart_tcp"
109raw_fallback = "ssh-fallback"
110raw_fallback_trusted_cidrs = ["100.64.0.0/10"]
111
112[[traffic.tcp.routers]]
113name = "ssh-fallback"
114entrypoint = "edge"
115service = "network_service:ssh:ssh"
116```
117
118Use `raw_fallback_trusted_cidrs` for private raw fallback exposure. To expose raw fallback publicly, acknowledge it explicitly:
119
120```toml
121[entrypoints.edge]
122address = ":443"
123protocol = "smart_tcp"
124raw_fallback = "public-raw"
125allow_public_raw_fallback = true
126```
127
128`trusted_cidrs` and `raw_fallback_trusted_cidrs` both use the peer socket IP. They do not use `X-Forwarded-For`.
129
130## HTTP Timeouts
131
132The HTTP servers behind `smart_tcp` and `tls_mux` entrypoints serve apps and the admin API. They do not use a fixed read or write timeout, so a download or upload that keeps moving data is never cut for taking too long. A connection is cut only after `transfer_idle_timeout` passes with no data in the direction being waited on.
133
134```toml
135[traffic.http]
136read_header_timeout = "5s"
137idle_timeout = "30s"
138transfer_idle_timeout = "2m"
139```
140
141| Option | Env | Default | Description |
142|--------|-----|---------|-------------|
143| `traffic.http.read_header_timeout` | `GORDON_TRAFFIC_HTTP_READ_HEADER_TIMEOUT` | `"5s"` | Maximum time to read request headers |
144| `traffic.http.idle_timeout` | `GORDON_TRAFFIC_HTTP_IDLE_TIMEOUT` | `"30s"` | Maximum keep-alive idle time between requests |
145| `traffic.http.transfer_idle_timeout` | `GORDON_TRAFFIC_HTTP_TRANSFER_IDLE_TIMEOUT` | `"2m"` | Inactivity timeout for request bodies and responses |
146
147Values are Go duration strings; malformed, zero, or negative values are rejected when the configuration loads. Servers pick up changes when the traffic graph is next applied (reload); connections already in flight keep their previous timeout.
148
149### Inactivity timeout
150
151Each request has an inactivity timer, armed when the request starts. Progress resets it: a response chunk accepted by the client connection (8 KiB granularity), a flush, or request body bytes read. If `transfer_idle_timeout` passes without progress, Gordon cancels the request context, which aborts the upstream request of a reverse-proxied app, and closes the client connection. When the handler returns, the final response flush and the drain of an unread request body are bounded by the same duration. Consequences:
152
153- a response that streams data at least once per `transfer_idle_timeout` can last arbitrarily long, and a client that keeps reading is never cut (it must accept 8 KiB per `transfer_idle_timeout`, about 68 B/s at the 2 minute default);
154- an upload whose body arrives at least once per `transfer_idle_timeout` can last arbitrarily long;
155- a backend that stays silent longer than `transfer_idle_timeout` before or between response chunks has its connection cut, so set it above the slowest expected backend response time;
156- the timer also fires when a handler or backend is silent and no write is pending;
157- WebSocket and other upgraded connections are not subject to it, and HTTP/2 streams are handled the same way as HTTP/1.
158
159`transfer_idle_timeout` does not apply to `[traffic.tcp]` or `[traffic.udp]` routers; those use their own `idle_timeout` (see below).
160
161## TCP Routers
162
163Plain TCP entrypoints support one TCP router per entrypoint. TCP routers on a `smart_tcp` entrypoint are valid only when referenced by that entrypoint's `raw_fallback`.
164
165```toml
166[entrypoints.postgres]
167address = "0.0.0.0:5432"
168protocol = "tcp"
169trusted_cidrs = ["100.64.0.0/10"]
170
171[traffic.tcp]
172dial_timeout = "10s"
173idle_timeout = "5m"
174drain_timeout = "30s"
175max_connections = 1024
176
177[[traffic.tcp.routers]]
178name = "postgres"
179entrypoint = "postgres"
180service = "network_service:postgres:db"
181```
182
183If `max_connections` is omitted or set to `0`, Gordon applies the safe runtime default of `1024` active connections per entrypoint.
184
185## UDP Routers
186
187UDP is not sniffed or unified into smart TCP. Use a separate UDP entrypoint:
188
189```toml
190[entrypoints.game]
191address = "0.0.0.0:7777"
192protocol = "udp"
193
194[traffic.udp]
195idle_timeout = "30s"
196drain_timeout = "30s"
197max_sessions = 4096
198
199[[traffic.udp.routers]]
200name = "game"
201entrypoint = "game"
202service = "network_service:game:game"
203```
204
205UDP sessions are keyed by client address and expire after `idle_timeout`. If `max_sessions` is omitted or set to `0`, Gordon applies the safe runtime default of `4096` active sessions per entrypoint.
206
207## Related
208
209- [Server Settings](./server.md)
210- [App Manifest](./apps.md)
211- [Standalone Services](./services.md)
212- [CLI traffic status](../cli/daemon.md#gordon-daemon-traffic)