detached f02707a

docs(traffic): document smart tcp entrypoints

commit
f02707aa808a4dc26cb94aae78fd2de00fb22eb6
author
brice · · 2026-06-30 13:28 UTC
parent
6dc6603
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
16 files · +328 -157
M README.md +2 -2
@@ -127,7 +127,7 @@ See the [Deploy Action README](.github/actions/deploy/README.md) for multi-platf
127127 ## Features
128128
129129 - Private Docker registry on your VPS
130-- Domain-to-container routing via HTTP reverse proxy
130+- Domain-to-container routing through a smart TCP edge reverse proxy
131131 - Automatic deployment on image push
132132 - Auto-routing from image labels
133133 - Remote CLI management
@@ -138,7 +138,7 @@ See the [Deploy Action README](.github/actions/deploy/README.md) for multi-platf
138138 - Single binary, ~15MB RAM
139139
140140 > [!NOTE]
141-> Gordon can terminate TLS directly via its internal CA (default `server.tls_port = 8443`), static certificates, or public ACME certificates. Cloudflare and upstream reverse proxies are optional deployment choices, not requirements.
141+> Gordon exposes public traffic through entrypoints such as `[entrypoints.edge]` with `protocol = "smart_tcp"`. It can terminate TLS via static certificates, public ACME certificates, or its internal CA. Cloudflare and upstream reverse proxies are optional deployment choices, not requirements.
142142
143143 ## Documentation
144144
@@ -127,7 +127,7 @@ See the [Deploy Action README](.github/actions/deploy/README.md) for multi-platf
127 ## Features127 ## Features
128 128
129 - Private Docker registry on your VPS129 - Private Docker registry on your VPS
130-- Domain-to-container routing via HTTP reverse proxy130+- Domain-to-container routing through a smart TCP edge reverse proxy
131 - Automatic deployment on image push131 - Automatic deployment on image push
132 - Auto-routing from image labels132 - Auto-routing from image labels
133 - Remote CLI management133 - Remote CLI management
@@ -138,7 +138,7 @@ See the [Deploy Action README](.github/actions/deploy/README.md) for multi-platf
138 - Single binary, ~15MB RAM138 - Single binary, ~15MB RAM
139 139
140 > [!NOTE]140 > [!NOTE]
141-> Gordon can terminate TLS directly via its internal CA (default `server.tls_port = 8443`), static certificates, or public ACME certificates. Cloudflare and upstream reverse proxies are optional deployment choices, not requirements.141+> Gordon exposes public traffic through entrypoints such as `[entrypoints.edge]` with `protocol = "smart_tcp"`. It can terminate TLS via static certificates, public ACME certificates, or its internal CA. Cloudflare and upstream reverse proxies are optional deployment choices, not requirements.
142 142
143 ## Documentation143 ## Documentation
144 144
M docs/cli/ca.md +2 -2
@@ -12,7 +12,7 @@ Manage Gordon's internal Certificate Authority.
1212 | `info` | Show CA status (root CN, fingerprint, intermediate expiry) |
1313 | `install` | Install/uninstall the root CA in system trust stores |
1414
15-All subcommands require `server.tls_port` to be non-zero in the Gordon configuration.
15+All subcommands require Gordon to have TLS-capable HTTPS fallback configured on an entrypoint such as `entrypoints.edge` with `protocol = "smart_tcp"`.
1616
1717 ---
1818
@@ -120,5 +120,5 @@ Visit `http://<gordon-host>/.well-known/gordon/ca` from the device browser. The
120120
121121 ## Related
122122
123-- [Server Configuration](../config/server.md) — `tls_port`, `force_https_redirect`, and internal CA settings
123+- [Server Configuration](../config/server.md) — `force_https_redirect`, smart TCP edge, and internal CA settings
124124 - [CLI Commands](./index.md)
@@ -12,7 +12,7 @@ Manage Gordon's internal Certificate Authority.
12 | `info` | Show CA status (root CN, fingerprint, intermediate expiry) |12 | `info` | Show CA status (root CN, fingerprint, intermediate expiry) |
13 | `install` | Install/uninstall the root CA in system trust stores |13 | `install` | Install/uninstall the root CA in system trust stores |
14 14
15-All subcommands require `server.tls_port` to be non-zero in the Gordon configuration.15+All subcommands require Gordon to have TLS-capable HTTPS fallback configured on an entrypoint such as `entrypoints.edge` with `protocol = "smart_tcp"`.
16 16
17 ---17 ---
18 18
@@ -120,5 +120,5 @@ Visit `http://<gordon-host>/.well-known/gordon/ca` from the device browser. The
120 120
121 ## Related121 ## Related
122 122
123-- [Server Configuration](../config/server.md) — `tls_port`, `force_https_redirect`, and internal CA settings123+- [Server Configuration](../config/server.md) — `force_https_redirect`, smart TCP edge, and internal CA settings
124 - [CLI Commands](./index.md)124 - [CLI Commands](./index.md)
M docs/cli/remotes.md +1 -1
@@ -350,7 +350,7 @@ A common Tailscale setup is to point your domain's DNS at the machine's Tailscal
350350 - `insecure_tls = true` on the remote — skips certificate verification entirely
351351 - `sudo gordon ca install` — installs the root CA into system, Firefox, and Java trust stores
352352 - `gordon ca export --out gordon-ca.crt` — exports the root CA PEM for manual installation
353-- Visit `https://<gordon-host>:<tls_port>/ca` in a browser — onboarding page with downloads for macOS, Linux, Windows, iOS, and Android
353+- Visit `https://<gordon-host>/ca` (or your mapped edge address) in a browser — onboarding page with downloads for macOS, Linux, Windows, iOS, and Android
354354
355355 If you have your own certificate for the domain (e.g. from a corporate CA), you can provide it via `tls_cert_file`/`tls_key_file` — see [Server Configuration](../config/server.md#custom-certificates). The static cert is served for SNI-matching domains; everything else falls through to the internal CA.
356356
@@ -350,7 +350,7 @@ A common Tailscale setup is to point your domain's DNS at the machine's Tailscal
350 - `insecure_tls = true` on the remote — skips certificate verification entirely350 - `insecure_tls = true` on the remote — skips certificate verification entirely
351 - `sudo gordon ca install` — installs the root CA into system, Firefox, and Java trust stores351 - `sudo gordon ca install` — installs the root CA into system, Firefox, and Java trust stores
352 - `gordon ca export --out gordon-ca.crt` — exports the root CA PEM for manual installation352 - `gordon ca export --out gordon-ca.crt` — exports the root CA PEM for manual installation
353-- Visit `https://<gordon-host>:<tls_port>/ca` in a browser — onboarding page with downloads for macOS, Linux, Windows, iOS, and Android353+- Visit `https://<gordon-host>/ca` (or your mapped edge address) in a browser — onboarding page with downloads for macOS, Linux, Windows, iOS, and Android
354 354
355 If you have your own certificate for the domain (e.g. from a corporate CA), you can provide it via `tls_cert_file`/`tls_key_file` — see [Server Configuration](../config/server.md#custom-certificates). The static cert is served for SNI-matching domains; everything else falls through to the internal CA.355 If you have your own certificate for the domain (e.g. from a corporate CA), you can provide it via `tls_cert_file`/`tls_key_file` — see [Server Configuration](../config/server.md#custom-certificates). The static cert is served for SNI-matching domains; everything else falls through to the internal CA.
356 356
M docs/cli/tls.md +8 -0
@@ -2,6 +2,14 @@
22
33 Inspect public TLS/ACME certificate status.
44
5+Gordon serves normal HTTPS fallback on TLS-capable entrypoints such as `entrypoints.edge` with `protocol = "smart_tcp"`. Certificate priority is static certificates first, then public ACME certificates, then Gordon's internal CA.
6+
7+ACME challenge notes:
8+
9+- DNS-01 (`cloudflare-dns-01`) does not require a special external port 80 edge.
10+- HTTP-01 requires an HTTP-capable smart TCP entrypoint reachable on external port 80 for every hostname being validated.
11+- TLS-ALPN-01 is not supported.
12+
513 Remote targeting uses client config or an active remote by default.
614 Use `--remote` and `--token` to override. See [CLI Overview](./index.md).
715
@@ -2,6 +2,14 @@
2 2
3 Inspect public TLS/ACME certificate status.3 Inspect public TLS/ACME certificate status.
4 4
5+Gordon serves normal HTTPS fallback on TLS-capable entrypoints such as `entrypoints.edge` with `protocol = "smart_tcp"`. Certificate priority is static certificates first, then public ACME certificates, then Gordon's internal CA.
6+
7+ACME challenge notes:
8+
9+- DNS-01 (`cloudflare-dns-01`) does not require a special external port 80 edge.
10+- HTTP-01 requires an HTTP-capable smart TCP entrypoint reachable on external port 80 for every hostname being validated.
11+- TLS-ALPN-01 is not supported.
12+
5 Remote targeting uses client config or an active remote by default.13 Remote targeting uses client config or an active remote by default.
6 Use `--remote` and `--token` to override. See [CLI Overview](./index.md).14 Use `--remote` and `--token` to override. See [CLI Overview](./index.md).
7 15
M docs/cli/traffic.md +30 -2
@@ -39,7 +39,21 @@ gordon traffic status --remote https://gordon.example.com --json
3939 "total_refused": 0,
4040 "total_errors": 0,
4141 "bytes_in": 4096,
42- "bytes_out": 8192
42+ "bytes_out": 8192,
43+ "smart_tcp": {
44+ "http_accepted": 0,
45+ "h2c_accepted": 0,
46+ "https_fallback_accepted": 0,
47+ "tls_passthrough_accepted": 0,
48+ "raw_fallback_accepted": 0,
49+ "entrypoint_cidr_refused": 0,
50+ "raw_fallback_cidr_refused": 0,
51+ "proxy_refused": 0,
52+ "unknown_no_fallback_refused": 0,
53+ "malformed_rejected": 0,
54+ "sniff_timeout": 0,
55+ "client_hello_too_large": 0
56+ }
4357 }
4458 ],
4559 "routers": [],
@@ -51,7 +65,21 @@ gordon traffic status --remote https://gordon.example.com --json
5165 "total_refused": 0,
5266 "total_errors": 0,
5367 "bytes_in": 4096,
54- "bytes_out": 8192
68+ "bytes_out": 8192,
69+ "smart_tcp": {
70+ "http_accepted": 0,
71+ "h2c_accepted": 0,
72+ "https_fallback_accepted": 0,
73+ "tls_passthrough_accepted": 0,
74+ "raw_fallback_accepted": 0,
75+ "entrypoint_cidr_refused": 0,
76+ "raw_fallback_cidr_refused": 0,
77+ "proxy_refused": 0,
78+ "unknown_no_fallback_refused": 0,
79+ "malformed_rejected": 0,
80+ "sniff_timeout": 0,
81+ "client_hello_too_large": 0
82+ }
5583 }
5684 }
5785 ```
@@ -39,7 +39,21 @@ gordon traffic status --remote https://gordon.example.com --json
39 "total_refused": 0,39 "total_refused": 0,
40 "total_errors": 0,40 "total_errors": 0,
41 "bytes_in": 4096,41 "bytes_in": 4096,
42- "bytes_out": 819242+ "bytes_out": 8192,
43+ "smart_tcp": {
44+ "http_accepted": 0,
45+ "h2c_accepted": 0,
46+ "https_fallback_accepted": 0,
47+ "tls_passthrough_accepted": 0,
48+ "raw_fallback_accepted": 0,
49+ "entrypoint_cidr_refused": 0,
50+ "raw_fallback_cidr_refused": 0,
51+ "proxy_refused": 0,
52+ "unknown_no_fallback_refused": 0,
53+ "malformed_rejected": 0,
54+ "sniff_timeout": 0,
55+ "client_hello_too_large": 0
56+ }
43 }57 }
44 ],58 ],
45 "routers": [],59 "routers": [],
@@ -51,7 +65,21 @@ gordon traffic status --remote https://gordon.example.com --json
51 "total_refused": 0,65 "total_refused": 0,
52 "total_errors": 0,66 "total_errors": 0,
53 "bytes_in": 4096,67 "bytes_in": 4096,
54- "bytes_out": 819268+ "bytes_out": 8192,
69+ "smart_tcp": {
70+ "http_accepted": 0,
71+ "h2c_accepted": 0,
72+ "https_fallback_accepted": 0,
73+ "tls_passthrough_accepted": 0,
74+ "raw_fallback_accepted": 0,
75+ "entrypoint_cidr_refused": 0,
76+ "raw_fallback_cidr_refused": 0,
77+ "proxy_refused": 0,
78+ "unknown_no_fallback_refused": 0,
79+ "malformed_rejected": 0,
80+ "sniff_timeout": 0,
81+ "client_hello_too_large": 0
82+ }
55 }83 }
56 }84 }
57 ```85 ```
M docs/config/index.md +9 -5
@@ -13,11 +13,13 @@ Gordon uses a single TOML configuration file located at `~/.config/gordon/gordon
1313
1414 ```toml
1515 [server]
16-port = 8088
17-tls_port = 8443
1816 registry_port = 5000
1917 gordon_domain = "gordon.mydomain.com"
2018
19+[entrypoints.edge]
20+address = ":443"
21+protocol = "smart_tcp"
22+
2123 [routes]
2224 "app.mydomain.com" = "myapp:latest"
2325 ```
@@ -35,11 +37,14 @@ For a complete list of all configuration options with their default values, see
3537 ```toml
3638 # Server settings
3739 [server]
38-port = 8088 # HTTP proxy port (default: 8088)
3940 registry_port = 5000 # Registry port (default: 5000)
4041 gordon_domain = "gordon.mydomain.com" # Required: Gordon domain (registry + API)
4142 # data_dir = "~/.gordon" # Default for user installations
4243
44+[entrypoints.edge]
45+address = ":443" # Deployment-selected public TCP socket
46+protocol = "smart_tcp"
47+
4348 # Authentication (includes secrets backend)
4449 [auth]
4550 enabled = true # Enable registry authentication (default: true)
@@ -183,7 +188,6 @@ keep_last = 3
183188
184189 | Setting | Default |
185190 |---------|---------|
186-| `server.port` | `8088` |
187191 | `server.registry_port` | `5000` |
188192 | `server.data_dir` | `~/.gordon` |
189193 | `server.max_blob_chunk_size` | `"95MB"` |
@@ -258,7 +262,7 @@ gordon reload
258262
259263 | Setting |
260264 |---------|
261-| `server.port` |
265+| `entrypoints.edge.address` |
262266 | `server.data_dir` |
263267 | `server.max_blob_chunk_size` |
264268 | `server.max_blob_size` |
@@ -13,11 +13,13 @@ Gordon uses a single TOML configuration file located at `~/.config/gordon/gordon
13 13
14 ```toml14 ```toml
15 [server]15 [server]
16-port = 8088
17-tls_port = 8443
18 registry_port = 500016 registry_port = 5000
19 gordon_domain = "gordon.mydomain.com"17 gordon_domain = "gordon.mydomain.com"
20 18
19+[entrypoints.edge]
20+address = ":443"
21+protocol = "smart_tcp"
22+
21 [routes]23 [routes]
22 "app.mydomain.com" = "myapp:latest"24 "app.mydomain.com" = "myapp:latest"
23 ```25 ```
@@ -35,11 +37,14 @@ For a complete list of all configuration options with their default values, see
35 ```toml37 ```toml
36 # Server settings38 # Server settings
37 [server]39 [server]
38-port = 8088 # HTTP proxy port (default: 8088)
39 registry_port = 5000 # Registry port (default: 5000)40 registry_port = 5000 # Registry port (default: 5000)
40 gordon_domain = "gordon.mydomain.com" # Required: Gordon domain (registry + API)41 gordon_domain = "gordon.mydomain.com" # Required: Gordon domain (registry + API)
41 # data_dir = "~/.gordon" # Default for user installations42 # data_dir = "~/.gordon" # Default for user installations
42 43
44+[entrypoints.edge]
45+address = ":443" # Deployment-selected public TCP socket
46+protocol = "smart_tcp"
47+
43 # Authentication (includes secrets backend)48 # Authentication (includes secrets backend)
44 [auth]49 [auth]
45 enabled = true # Enable registry authentication (default: true)50 enabled = true # Enable registry authentication (default: true)
@@ -183,7 +188,6 @@ keep_last = 3
183 188
184 | Setting | Default |189 | Setting | Default |
185 |---------|---------|190 |---------|---------|
186-| `server.port` | `8088` |
187 | `server.registry_port` | `5000` |191 | `server.registry_port` | `5000` |
188 | `server.data_dir` | `~/.gordon` |192 | `server.data_dir` | `~/.gordon` |
189 | `server.max_blob_chunk_size` | `"95MB"` |193 | `server.max_blob_chunk_size` | `"95MB"` |
@@ -258,7 +262,7 @@ gordon reload
258 262
259 | Setting |263 | Setting |
260 |---------|264 |---------|
261-| `server.port` |265+| `entrypoints.edge.address` |
262 | `server.data_dir` |266 | `server.data_dir` |
263 | `server.max_blob_chunk_size` |267 | `server.max_blob_chunk_size` |
264 | `server.max_blob_size` |268 | `server.max_blob_size` |
M docs/config/rate-limiting.md +1 -1
@@ -150,7 +150,7 @@ A burst of `100` with `per_ip_rps = 50` means a client can send 100 requests ins
150150
151151 **Critical for correct IP detection in production.**
152152
153-Gordon is a reverse proxy that routes requests to your containers. In production you can either terminate TLS directly in Gordon (via its internal CA on `server.tls_port`) or place it behind a TLS-terminating proxy like Cloudflare, nginx, or a load balancer.
153+Gordon is a reverse proxy that routes requests to your containers. In production you can either terminate TLS directly in Gordon (on a smart TCP edge entrypoint via its internal CA) or place it behind a TLS-terminating proxy like Cloudflare, nginx, or a load balancer.
154154
155155 ```
156156 Internet → [Cloudflare/nginx] → Gordon → Containers
@@ -150,7 +150,7 @@ A burst of `100` with `per_ip_rps = 50` means a client can send 100 requests ins
150 150
151 **Critical for correct IP detection in production.**151 **Critical for correct IP detection in production.**
152 152
153-Gordon is a reverse proxy that routes requests to your containers. In production you can either terminate TLS directly in Gordon (via its internal CA on `server.tls_port`) or place it behind a TLS-terminating proxy like Cloudflare, nginx, or a load balancer.153+Gordon is a reverse proxy that routes requests to your containers. In production you can either terminate TLS directly in Gordon (on a smart TCP edge entrypoint via its internal CA) or place it behind a TLS-terminating proxy like Cloudflare, nginx, or a load balancer.
154 154
155 ```155 ```
156 Internet → [Cloudflare/nginx] → Gordon → Containers156 Internet → [Cloudflare/nginx] → Gordon → Containers
M docs/config/reference.md +21 -8
@@ -9,10 +9,8 @@ Complete configuration reference with all options and their default values.
99 # SERVER
1010 # =============================================================================
1111 [server]
12-port = 8088 # HTTP proxy port
1312 registry_port = 5000 # Container registry port
14-tls_port = 8443 # HTTPS proxy port (0 = disabled, no internal CA)
15-tls_cert_file = "" # PEM cert path (optional, for static TLS alongside internal CA)
13+tls_cert_file = "" # PEM cert path (optional, for static TLS fallback)
1614 tls_key_file = "" # PEM key path (optional, must be set with tls_cert_file)
1715 force_https_redirect = false # Redirect all HTTP traffic to HTTPS (for direct-access setups)
1816 gordon_domain = "" # Required: Gordon domain (registry + API)
@@ -20,9 +18,20 @@ data_dir = "~/.gordon" # Data directory (varies by install
2018 max_blob_chunk_size = "95MB" # Max size per registry blob upload chunk
2119 max_blob_size = "1GB" # Max cumulative size per registry blob/layer upload
2220 registry_allowed_ips = [] # IPs or CIDR ranges allowed to access the registry (empty = allow all)
23-proxy_allowed_ips = [] # IPs or CIDR ranges allowed to reach the proxy (empty = allow all, e.g. Cloudflare IPs)
21+proxy_allowed_ips = [] # IPs or CIDR ranges allowed to reach HTTP proxy paths (empty = allow all, e.g. Cloudflare IPs)
2422 registry_listen_address = "" # Bind address for registry (empty = all interfaces, "127.0.0.1" = loopback only)
2523
24+# =============================================================================
25+# ENTRYPOINTS
26+# =============================================================================
27+[entrypoints.edge]
28+address = ":443" # Deployment-selected public TCP socket
29+protocol = "smart_tcp" # Sniff HTTP, h2c, TLS, passthrough, or explicit raw fallback
30+trusted_cidrs = [] # Peer socket IP allowlist for all traffic on this entrypoint
31+# raw_fallback = "ssh-fallback" # Optional TCP router for unknown non-HTTP/non-TLS bytes
32+# raw_fallback_trusted_cidrs = ["100.64.0.0/10"]
33+# allow_public_raw_fallback = false
34+
2635 # =============================================================================
2736 # DNS
2837 # =============================================================================
@@ -45,7 +54,7 @@ access_token_ttl = "15m" # Ephemeral access token lifetime (
4554 # PUBLIC TLS / ACME
4655 # =============================================================================
4756 [tls.acme]
48-enabled = false # Enable public ACME certificates (requires server.tls_port > 0)
57+enabled = false # Enable public ACME certificates (requires HTTPS fallback on a TLS-capable entrypoint)
4958 email = "" # ACME account email when enabled
5059 challenge = "auto" # "auto", "http-01", or "cloudflare-dns-01"
5160 obtain_batch_size = 1 # New certificate orders per reconcile run
@@ -210,9 +219,7 @@ keep_last = 3 # Keep N newest tags per repository
210219
211220 | Setting | Default | Description |
212221 |---------|---------|-------------|
213-| `server.port` | `8088` | HTTP proxy port |
214222 | `server.registry_port` | `5000` | Container registry port |
215-| `server.tls_port` | `8443` | HTTPS listener port (0 = disabled) |
216223 | `server.tls_cert_file` | `""` | PEM cert path for static TLS (optional) |
217224 | `server.tls_key_file` | `""` | PEM key path for static TLS (optional) |
218225 | `server.force_https_redirect` | `false` | Redirect all HTTP to HTTPS (for direct-access setups) |
@@ -223,10 +230,16 @@ keep_last = 3 # Keep N newest tags per repository
223230 | `server.registry_allowed_ips` | `[]` | IPs or CIDR ranges allowed to access the registry (empty = allow all) |
224231 | `server.proxy_allowed_ips` | `[]` | IPs or CIDR ranges allowed to reach the proxy (empty = allow all) |
225232 | `server.registry_listen_address` | `""` | Bind address for registry (empty = all interfaces) |
233+| `entrypoints.<name>.address` | none | Deployment-selected listen address; `edge` is conventional but has no built-in port |
234+| `entrypoints.<name>.protocol` | none | Entrypoint protocol: `smart_tcp`, `http`, `tls_mux`, `tcp`, or `udp` |
235+| `entrypoints.<name>.trusted_cidrs` | `[]` | Peer socket IP allowlist for all traffic on the entrypoint |
236+| `entrypoints.<name>.raw_fallback` | `""` | TCP router used by smart TCP for unknown non-HTTP/non-TLS bytes |
237+| `entrypoints.<name>.raw_fallback_trusted_cidrs` | `[]` | Peer socket IP allowlist for smart TCP raw fallback |
238+| `entrypoints.<name>.allow_public_raw_fallback` | `false` | Explicit acknowledgement for public raw fallback exposure |
226239 | `dns.resolvers` | `["1.1.1.1:53", "8.8.8.8:53"]` | Recursive resolvers used for public DNS visibility checks, including ACME DNS-01 propagation |
227240 | `dns.propagation_timeout` | `"5m"` | Maximum time to wait for DNS-01 TXT records to become visible through configured recursive resolvers |
228241 | `dns.polling_interval` | `"5s"` | Interval between DNS-01 propagation checks |
229-| `tls.acme.enabled` | `false` | Enable public ACME certificates (requires `server.tls_port > 0`) |
242+| `tls.acme.enabled` | `false` | Enable public ACME certificates (requires HTTPS fallback on a TLS-capable entrypoint) |
230243 | `tls.acme.email` | `""` | ACME account email when enabled |
231244 | `tls.acme.challenge` | `"auto"` | ACME challenge mode: `auto`, `http-01`, or `cloudflare-dns-01` |
232245 | `tls.acme.obtain_batch_size` | `1` | Maximum new ACME certificate orders per reconcile run |
@@ -9,10 +9,8 @@ Complete configuration reference with all options and their default values.
9 # SERVER9 # SERVER
10 # =============================================================================10 # =============================================================================
11 [server]11 [server]
12-port = 8088 # HTTP proxy port
13 registry_port = 5000 # Container registry port12 registry_port = 5000 # Container registry port
14-tls_port = 8443 # HTTPS proxy port (0 = disabled, no internal CA)13+tls_cert_file = "" # PEM cert path (optional, for static TLS fallback)
15-tls_cert_file = "" # PEM cert path (optional, for static TLS alongside internal CA)
16 tls_key_file = "" # PEM key path (optional, must be set with tls_cert_file)14 tls_key_file = "" # PEM key path (optional, must be set with tls_cert_file)
17 force_https_redirect = false # Redirect all HTTP traffic to HTTPS (for direct-access setups)15 force_https_redirect = false # Redirect all HTTP traffic to HTTPS (for direct-access setups)
18 gordon_domain = "" # Required: Gordon domain (registry + API)16 gordon_domain = "" # Required: Gordon domain (registry + API)
@@ -20,9 +18,20 @@ data_dir = "~/.gordon" # Data directory (varies by install
20 max_blob_chunk_size = "95MB" # Max size per registry blob upload chunk18 max_blob_chunk_size = "95MB" # Max size per registry blob upload chunk
21 max_blob_size = "1GB" # Max cumulative size per registry blob/layer upload19 max_blob_size = "1GB" # Max cumulative size per registry blob/layer upload
22 registry_allowed_ips = [] # IPs or CIDR ranges allowed to access the registry (empty = allow all)20 registry_allowed_ips = [] # IPs or CIDR ranges allowed to access the registry (empty = allow all)
23-proxy_allowed_ips = [] # IPs or CIDR ranges allowed to reach the proxy (empty = allow all, e.g. Cloudflare IPs)21+proxy_allowed_ips = [] # IPs or CIDR ranges allowed to reach HTTP proxy paths (empty = allow all, e.g. Cloudflare IPs)
24 registry_listen_address = "" # Bind address for registry (empty = all interfaces, "127.0.0.1" = loopback only)22 registry_listen_address = "" # Bind address for registry (empty = all interfaces, "127.0.0.1" = loopback only)
25 23
24+# =============================================================================
25+# ENTRYPOINTS
26+# =============================================================================
27+[entrypoints.edge]
28+address = ":443" # Deployment-selected public TCP socket
29+protocol = "smart_tcp" # Sniff HTTP, h2c, TLS, passthrough, or explicit raw fallback
30+trusted_cidrs = [] # Peer socket IP allowlist for all traffic on this entrypoint
31+# raw_fallback = "ssh-fallback" # Optional TCP router for unknown non-HTTP/non-TLS bytes
32+# raw_fallback_trusted_cidrs = ["100.64.0.0/10"]
33+# allow_public_raw_fallback = false
34+
26 # =============================================================================35 # =============================================================================
27 # DNS36 # DNS
28 # =============================================================================37 # =============================================================================
@@ -45,7 +54,7 @@ access_token_ttl = "15m" # Ephemeral access token lifetime (
45 # PUBLIC TLS / ACME54 # PUBLIC TLS / ACME
46 # =============================================================================55 # =============================================================================
47 [tls.acme]56 [tls.acme]
48-enabled = false # Enable public ACME certificates (requires server.tls_port > 0)57+enabled = false # Enable public ACME certificates (requires HTTPS fallback on a TLS-capable entrypoint)
49 email = "" # ACME account email when enabled58 email = "" # ACME account email when enabled
50 challenge = "auto" # "auto", "http-01", or "cloudflare-dns-01"59 challenge = "auto" # "auto", "http-01", or "cloudflare-dns-01"
51 obtain_batch_size = 1 # New certificate orders per reconcile run60 obtain_batch_size = 1 # New certificate orders per reconcile run
@@ -210,9 +219,7 @@ keep_last = 3 # Keep N newest tags per repository
210 219
211 | Setting | Default | Description |220 | Setting | Default | Description |
212 |---------|---------|-------------|221 |---------|---------|-------------|
213-| `server.port` | `8088` | HTTP proxy port |
214 | `server.registry_port` | `5000` | Container registry port |222 | `server.registry_port` | `5000` | Container registry port |
215-| `server.tls_port` | `8443` | HTTPS listener port (0 = disabled) |
216 | `server.tls_cert_file` | `""` | PEM cert path for static TLS (optional) |223 | `server.tls_cert_file` | `""` | PEM cert path for static TLS (optional) |
217 | `server.tls_key_file` | `""` | PEM key path for static TLS (optional) |224 | `server.tls_key_file` | `""` | PEM key path for static TLS (optional) |
218 | `server.force_https_redirect` | `false` | Redirect all HTTP to HTTPS (for direct-access setups) |225 | `server.force_https_redirect` | `false` | Redirect all HTTP to HTTPS (for direct-access setups) |
@@ -223,10 +230,16 @@ keep_last = 3 # Keep N newest tags per repository
223 | `server.registry_allowed_ips` | `[]` | IPs or CIDR ranges allowed to access the registry (empty = allow all) |230 | `server.registry_allowed_ips` | `[]` | IPs or CIDR ranges allowed to access the registry (empty = allow all) |
224 | `server.proxy_allowed_ips` | `[]` | IPs or CIDR ranges allowed to reach the proxy (empty = allow all) |231 | `server.proxy_allowed_ips` | `[]` | IPs or CIDR ranges allowed to reach the proxy (empty = allow all) |
225 | `server.registry_listen_address` | `""` | Bind address for registry (empty = all interfaces) |232 | `server.registry_listen_address` | `""` | Bind address for registry (empty = all interfaces) |
233+| `entrypoints.<name>.address` | none | Deployment-selected listen address; `edge` is conventional but has no built-in port |
234+| `entrypoints.<name>.protocol` | none | Entrypoint protocol: `smart_tcp`, `http`, `tls_mux`, `tcp`, or `udp` |
235+| `entrypoints.<name>.trusted_cidrs` | `[]` | Peer socket IP allowlist for all traffic on the entrypoint |
236+| `entrypoints.<name>.raw_fallback` | `""` | TCP router used by smart TCP for unknown non-HTTP/non-TLS bytes |
237+| `entrypoints.<name>.raw_fallback_trusted_cidrs` | `[]` | Peer socket IP allowlist for smart TCP raw fallback |
238+| `entrypoints.<name>.allow_public_raw_fallback` | `false` | Explicit acknowledgement for public raw fallback exposure |
226 | `dns.resolvers` | `["1.1.1.1:53", "8.8.8.8:53"]` | Recursive resolvers used for public DNS visibility checks, including ACME DNS-01 propagation |239 | `dns.resolvers` | `["1.1.1.1:53", "8.8.8.8:53"]` | Recursive resolvers used for public DNS visibility checks, including ACME DNS-01 propagation |
227 | `dns.propagation_timeout` | `"5m"` | Maximum time to wait for DNS-01 TXT records to become visible through configured recursive resolvers |240 | `dns.propagation_timeout` | `"5m"` | Maximum time to wait for DNS-01 TXT records to become visible through configured recursive resolvers |
228 | `dns.polling_interval` | `"5s"` | Interval between DNS-01 propagation checks |241 | `dns.polling_interval` | `"5s"` | Interval between DNS-01 propagation checks |
229-| `tls.acme.enabled` | `false` | Enable public ACME certificates (requires `server.tls_port > 0`) |242+| `tls.acme.enabled` | `false` | Enable public ACME certificates (requires HTTPS fallback on a TLS-capable entrypoint) |
230 | `tls.acme.email` | `""` | ACME account email when enabled |243 | `tls.acme.email` | `""` | ACME account email when enabled |
231 | `tls.acme.challenge` | `"auto"` | ACME challenge mode: `auto`, `http-01`, or `cloudflare-dns-01` |244 | `tls.acme.challenge` | `"auto"` | ACME challenge mode: `auto`, `http-01`, or `cloudflare-dns-01` |
232 | `tls.acme.obtain_batch_size` | `1` | Maximum new ACME certificate orders per reconcile run |245 | `tls.acme.obtain_batch_size` | `1` | Maximum new ACME certificate orders per reconcile run |
M docs/config/security-hardening.md +29 -0
@@ -56,6 +56,35 @@ require_digest = true
5656 - `require_digest = true` requires allowlisted external images to use `@sha256:<64 hex chars>`.
5757 - Include ports in allowlist entries when the registry uses a non-default port.
5858
59+## Smart TCP Raw Fallback
60+
61+Raw TCP fallback on a `smart_tcp` edge entrypoint is disabled by default. Unknown non-HTTP/non-TLS bytes reach a backend only when the entrypoint explicitly sets `raw_fallback` to a TCP router and the fallback source policy allows the peer.
62+
63+```toml
64+[entrypoints.edge]
65+address = ":443"
66+protocol = "smart_tcp"
67+raw_fallback = "ssh-fallback"
68+raw_fallback_trusted_cidrs = ["100.64.0.0/10"]
69+```
70+
71+Use `raw_fallback_trusted_cidrs` for private raw fallback. Public raw fallback requires an explicit acknowledgement:
72+
73+```toml
74+[entrypoints.edge]
75+address = ":443"
76+protocol = "smart_tcp"
77+raw_fallback = "public-raw"
78+allow_public_raw_fallback = true
79+```
80+
81+Security behavior:
82+
83+- PROXY protocol v1 and v2 prefixes are rejected; Gordon does not trust or parse PROXY headers on smart TCP entrypoints.
84+- `trusted_cidrs` and `raw_fallback_trusted_cidrs` use the peer socket IP, not `X-Forwarded-For`.
85+- HTTP-looking or TLS-looking malformed traffic is rejected and never bypasses to raw fallback.
86+- Entry-point-wide `trusted_cidrs` applies before sniffing to all protocols; raw fallback has its own narrower policy for private fallback exposure.
87+
5988 ## Docker network isolation
6089
6190 Per-app Docker networks are enabled by default. To block direct external egress from isolated networks, opt into Docker internal networks:
@@ -56,6 +56,35 @@ require_digest = true
56 - `require_digest = true` requires allowlisted external images to use `@sha256:<64 hex chars>`.56 - `require_digest = true` requires allowlisted external images to use `@sha256:<64 hex chars>`.
57 - Include ports in allowlist entries when the registry uses a non-default port.57 - Include ports in allowlist entries when the registry uses a non-default port.
58 58
59+## Smart TCP Raw Fallback
60+
61+Raw TCP fallback on a `smart_tcp` edge entrypoint is disabled by default. Unknown non-HTTP/non-TLS bytes reach a backend only when the entrypoint explicitly sets `raw_fallback` to a TCP router and the fallback source policy allows the peer.
62+
63+```toml
64+[entrypoints.edge]
65+address = ":443"
66+protocol = "smart_tcp"
67+raw_fallback = "ssh-fallback"
68+raw_fallback_trusted_cidrs = ["100.64.0.0/10"]
69+```
70+
71+Use `raw_fallback_trusted_cidrs` for private raw fallback. Public raw fallback requires an explicit acknowledgement:
72+
73+```toml
74+[entrypoints.edge]
75+address = ":443"
76+protocol = "smart_tcp"
77+raw_fallback = "public-raw"
78+allow_public_raw_fallback = true
79+```
80+
81+Security behavior:
82+
83+- PROXY protocol v1 and v2 prefixes are rejected; Gordon does not trust or parse PROXY headers on smart TCP entrypoints.
84+- `trusted_cidrs` and `raw_fallback_trusted_cidrs` use the peer socket IP, not `X-Forwarded-For`.
85+- HTTP-looking or TLS-looking malformed traffic is rejected and never bypasses to raw fallback.
86+- Entry-point-wide `trusted_cidrs` applies before sniffing to all protocols; raw fallback has its own narrower policy for private fallback exposure.
87+
59 ## Docker network isolation88 ## Docker network isolation
60 89
61 Per-app Docker networks are enabled by default. To block direct external egress from isolated networks, opt into Docker internal networks:90 Per-app Docker networks are enabled by default. To block direct external egress from isolated networks, opt into Docker internal networks:
M docs/config/server.md +53 -73
@@ -6,29 +6,29 @@ Core server settings for Gordon.
66
77 ```toml
88 [server]
9-port = 8088 # HTTP proxy port
109 registry_port = 5000 # Container registry port
11-tls_port = 8443 # HTTPS listener port (0 = disabled)
10+gordon_domain = "gordon.mydomain.com" # Gordon domain (required)
1211 # tls_cert_file = "" # Optional: PEM certificate for static TLS
1312 # tls_key_file = "" # Optional: PEM key for static TLS
14-# force_https_redirect = false # Redirect all HTTP traffic to HTTPS
15-gordon_domain = "gordon.mydomain.com" # Gordon domain (required)
13+# force_https_redirect = false # Redirect cleartext HTTP requests to HTTPS
1614 # legacy_registry_domains = ["registry.example.com:5000"]
1715 # data_dir = "~/.gordon" # Data storage directory (default)
1816 max_blob_chunk_size = "95MB" # Max registry blob upload chunk
1917 max_blob_size = "1GB" # Max cumulative registry blob/layer upload
18+
19+[entrypoints.edge]
20+address = ":443" # Deployment-selected public TCP socket
21+protocol = "smart_tcp"
2022 ```
2123
2224 ## Options
2325
2426 | Option | Type | Default | Description |
2527 |--------|------|---------|-------------|
26-| `port` | int | `8088` | HTTP proxy port for routing traffic to containers |
2728 | `registry_port` | int | `5000` | Docker registry port for image push/pull |
28-| `tls_port` | int | `8443` | HTTPS listener port with internal CA. Set to `0` to disable TLS entirely |
29-| `tls_cert_file` | string | `""` | Path to PEM certificate file. When set (with `tls_key_file`), this cert is served for matching SNI domains; unmatched domains fall through to the internal CA |
29+| `tls_cert_file` | string | `""` | Path to PEM certificate file. When set with `tls_key_file`, matching SNI domains use this static cert before ACME or internal CA certs |
3030 | `tls_key_file` | string | `""` | Path to PEM private key file. Must be set together with `tls_cert_file` |
31-| `force_https_redirect` | bool | `false` | Redirect all HTTP requests to the HTTPS port. For direct-access setups without a TLS-terminating proxy |
31+| `force_https_redirect` | bool | `false` | Redirect cleartext HTTP requests to HTTPS on HTTP-capable edge entrypoints |
3232 | `gordon_domain` | string | **required** | Domain for Gordon (registry + admin API) |
3333 | `registry_domain` | string | - | Deprecated migration key. Set `gordon_domain` instead. |
3434 | `legacy_registry_domains` | []string | `[]` | Additional Gordon registry hosts treated as aliases during staged migration. See [Upgrading: Staged Registry Host Rename](../upgrading.md#staged-registry-host-rename). |
@@ -37,46 +37,33 @@ max_blob_size = "1GB" # Max cumulative registry blob/layer up
3737 | `max_blob_chunk_size` | string | `"95MB"` | Maximum request body size for a single registry blob upload chunk |
3838 | `max_blob_size` | string | `"1GB"` | Maximum cumulative size for one registry blob/layer upload |
3939 | `registry_allowed_ips` | []string | `[]` | IPs or CIDR ranges allowed to access the registry (empty = allow all) |
40-| `proxy_allowed_ips` | []string | `[]` | IPs or CIDR ranges allowed to reach the proxy (empty = allow all) |
40+| `proxy_allowed_ips` | []string | `[]` | IPs or CIDR ranges allowed to reach HTTP proxy paths (empty = allow all) |
4141 | `registry_listen_address` | string | `""` | Bind address for registry (empty = all interfaces) |
4242
43-## Port Configuration
44-
45-### Proxy Port
46-
47-The `port` setting controls where Gordon listens for HTTP traffic:
43+Public application traffic is configured under `[entrypoints]`. The standard edge listener is a `smart_tcp` socket:
4844
4945 ```toml
50-[server]
51-port = 8088 # Default high port for rootless or forwarded setups
52-```
53-
54-For rootless containers, you'll typically use a high port and configure firewall port forwarding:
55-
56-```bash
57-# Forward port 80 to 8088
58-sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=8088
46+[entrypoints.edge]
47+address = ":443"
48+protocol = "smart_tcp"
5949 ```
6050
61-### Internal CA and TLS
51+`entrypoints.edge.address` is not an HTTP or HTTPS port. It is the TCP address Gordon sniffs for cleartext HTTP, h2c, TLS passthrough, normal HTTPS fallback, or explicit raw TCP fallback. Gordon does not provide a built-in default public port; choose `:443`, a high port such as `:9000`, or container/firewall mappings to match your deployment.
6252
63-Gordon includes an internal certificate authority that issues on-demand TLS certificates for proxied domains. The HTTPS listener is enabled by default on `tls_port` (8443).
53+## Internal CA and TLS
6454
65-```toml
66-[server]
67-tls_port = 8443 # Default. Set to 0 to disable TLS entirely.
68-```
55+Gordon includes an internal certificate authority that issues on-demand TLS certificates for proxied domains served through HTTPS fallback on a TLS-capable entrypoint such as `smart_tcp`.
6956
70-With TLS enabled:
57+With TLS-capable edge traffic:
7158 - Gordon runs an internal CA (root + intermediate) stored in `{data_dir}/pki/`
7259 - Leaf certificates are issued on-demand per domain (SNI-based) and cached in memory
7360 - The intermediate CA auto-renews before expiry
74-- An onboarding page at `https://<gordon-host>:<tls_port>/.well-known/gordon/` lets clients download the root CA certificate
61+- An onboarding page at `https://<gordon-host>/.well-known/gordon/` lets clients download the root CA certificate
7562 - The root CA is stable across restarts (generated once, persisted to disk)
7663
7764 #### Custom certificates
7865
79-To use your own certificate (e.g. from Tailscale, Let's Encrypt, or a corporate CA) alongside the internal CA:
66+To use your own certificate (e.g. from Tailscale, Let's Encrypt, or a corporate CA) alongside ACME/internal CA fallback:
8067
8168 ```toml
8269 [server]
@@ -84,15 +71,16 @@ tls_cert_file = "/etc/gordon/tls/cert.pem"
8471 tls_key_file = "/etc/gordon/tls/key.pem"
8572 ```
8673
87-The static certificate is served for SNI-matching domains (including wildcards). All other domains get on-demand certs from the internal CA.
74+Certificate priority for normal HTTPS fallback is: static certificates first, then public ACME certificates, then Gordon's internal CA.
8875
8976 #### Public ACME certificates
9077
91-Gordon can obtain public certificates through Let's Encrypt-compatible ACME when `[tls.acme]` is enabled. The HTTPS listener must also be enabled (`server.tls_port > 0`).
78+Gordon can obtain public certificates through Let's Encrypt-compatible ACME when `[tls.acme]` is enabled. Normal HTTPS fallback must be available on a TLS-capable entrypoint.
9279
9380 ```toml
94-[server]
95-tls_port = 8443
81+[entrypoints.edge]
82+address = ":443"
83+protocol = "smart_tcp"
9684
9785 [dns]
9886 resolvers = ["1.1.1.1:53", "8.8.8.8:53"]
@@ -107,11 +95,12 @@ obtain_batch_size = 1 # maximum new certificate orders per reconcile run
10795 ```
10896
10997 Challenge behavior:
110-- `http-01` serves `/.well-known/acme-challenge/<token>` on the HTTP proxy port.
111-- `cloudflare-dns-01` uses a Cloudflare API token from `pass`, `GORDON_CLOUDFLARE_API_TOKEN_FILE`, or `GORDON_CLOUDFLARE_API_TOKEN`.
98+- `cloudflare-dns-01` uses a Cloudflare API token from `pass`, `GORDON_CLOUDFLARE_API_TOKEN_FILE`, or `GORDON_CLOUDFLARE_API_TOKEN`; it does not need a special public port 80 edge.
99+- `http-01` serves `/.well-known/acme-challenge/<token>` through Gordon's HTTP handler and requires an HTTP-capable `smart_tcp` entrypoint reachable on external port 80 for each hostname being validated.
112100 - `auto` selects Cloudflare DNS-01 when a token is available, otherwise falls back to HTTP-01.
101+- `tls-alpn-01` is not supported.
113102
114-HTTP-01 requires public access to port 80 for each hostname being validated. If you use Cloudflare in DNS-only/gray-cloud mode, your firewall/NAT must allow direct public traffic to Gordon's HTTP proxy port. A firewall rule that only allows Cloudflare source IPs on port 80 is compatible with orange-cloud proxying, but it blocks gray-cloud HTTP-01 validation; use DNS-01 or temporarily open port 80 for direct validation.
103+HTTP-01 requires public access to external port 80 for each hostname being validated. If you use Cloudflare in DNS-only/gray-cloud mode, your firewall/NAT must allow direct public traffic to the HTTP-capable smart TCP entrypoint. A firewall rule that only allows Cloudflare source IPs on port 80 is compatible with orange-cloud proxying, but it blocks gray-cloud HTTP-01 validation; use DNS-01 or temporarily open port 80 for direct validation.
115104
116105 Gordon limits new ACME certificate orders to `obtain_batch_size` per reconcile run (default `1`) so enabling ACME on an existing multi-route server does not burst through every route and hit Let's Encrypt rate limits. Later reloads, restarts, or other explicit reconcile runs continue issuing remaining certificates.
117106
@@ -129,7 +118,7 @@ Because Gordon's ACME challenge mode is global, `cloudflare-dns-01` requires a C
129118
130119 #### Direct HTTP Onboarding
131120
132-When `tls_port` is non-zero, direct HTTP clients (those not arriving through a trusted proxy) are restricted to CA onboarding paths only:
121+When Gordon is serving TLS-capable edge traffic, direct cleartext HTTP clients (those not arriving through a trusted proxy) are restricted to CA onboarding paths only:
133122
134123 - `/.well-known/gordon/`, `/.well-known/gordon/ca`, `/.well-known/gordon/ca.crt`, `/.well-known/gordon/ca.mobileconfig` — serve the onboarding page and CA certificate downloads
135124 - `/.well-known/acme-challenge/*` — serves ACME HTTP-01 challenges when public ACME HTTP-01 is enabled, otherwise returns `404 Not Found`
@@ -141,7 +130,7 @@ On HTTPS, onboarding paths are served only on `server.gordon_domain`; app hosts
141130
142131 #### HTTP to HTTPS redirect
143132
144-When clients connect directly (no Cloudflare or reverse proxy in front), enable `force_https_redirect` to redirect all HTTP traffic to the HTTPS port. Direct HTTP onboarding paths are still served even with this flag enabled.
133+When clients connect directly (no Cloudflare or reverse proxy in front), enable `force_https_redirect` to redirect cleartext HTTP traffic to HTTPS on the same smart TCP edge.
145134
146135 ```toml
147136 [server]
@@ -150,40 +139,22 @@ force_https_redirect = true
150139
151140 When `proxy_allowed_ips` is configured, non-proxy clients are redirected automatically even without this flag — trusted proxy IPs pass through to serve Cloudflare-proxied traffic.
152141
153-#### Disabling TLS
142+#### Firewall and Container Port Mapping
154143
155-For setups where TLS is handled entirely by an external proxy (Cloudflare, nginx, etc.):
144+Choose the bind address in `[entrypoints.edge]` and map external ports to it as needed. For example, a rootless service can bind a high port while the firewall exposes 443:
156145
157146 ```toml
158-[server]
159-tls_port = 0 # No HTTPS listener, no internal CA generated
147+[entrypoints.edge]
148+address = ":9000"
149+protocol = "smart_tcp"
160150 ```
161151
162-#### Firewall port forwarding
163-
164-For rootless user services, forward 443 to the high port at the firewall:
165-
166152 ```bash
167-# Forward 443 -> 8443
168-sudo firewall-cmd --permanent --add-forward-port=port=443:proto=tcp:toport=8443
153+sudo firewall-cmd --permanent --add-forward-port=port=443:proto=tcp:toport=9000
169154 sudo firewall-cmd --reload
170155 ```
171156
172-With firewalld and Tailscale:
173-
174-```bash
175-# Verify tailscale0 is in trusted zone
176-sudo firewall-cmd --get-zone-of-interface=tailscale0
177-
178-# If needed, place tailscale0 in trusted
179-sudo firewall-cmd --permanent --zone=trusted --add-interface=tailscale0
180-sudo firewall-cmd --reload
181-
182-# Forward 443 -> 8443 permanently (IPv4 + IPv6)
183-sudo firewall-cmd --permanent --zone=trusted --add-rich-rule='rule family=ipv4 forward-port port=443 protocol=tcp to-port=8443'
184-sudo firewall-cmd --permanent --zone=trusted --add-rich-rule='rule family=ipv6 forward-port port=443 protocol=tcp to-port=8443'
185-sudo firewall-cmd --reload
186-```
157+`:443`, `:9000`, and container `-p 443:9000` mappings are deployment choices, not protocol modes.
187158
188159 ### Registry Port
189160
@@ -223,11 +194,11 @@ For a staged rename, set `gordon_domain` to the new public host and add any old
223194
224195 Without this migration, a Host/remote-target mismatch can break routing or remote CLI token exchange.
225196
226-When requests arrive on the proxy port with this domain as the Host header, Gordon routes them to the backend services (registry and admin API).
197+When requests arrive on an HTTP-capable edge entrypoint with this domain as the Host header, Gordon routes them to the backend services (registry and admin API).
227198
228199 Security note:
229200 - Direct access to `/admin/*` on `registry_port` is blocked for non-loopback clients.
230-- Admin API traffic should go through the main proxy listener (`server.port`/`server.tls_port`).
201+- Admin API traffic should go through an HTTP-capable edge entrypoint such as `entrypoints.edge` with `protocol = "smart_tcp"`.
231202
232203 > **Note:** `registry_domain` is a deprecated migration key; use `gordon_domain` for new and upgraded configs.
233204
@@ -404,7 +375,7 @@ registry_listen_address = "127.0.0.1" # Loopback only
404375
405376 ## HSTS
406377
407-HSTS headers are sent automatically on the HTTPS listener (when `r.TLS != nil`). When Gordon runs behind a TLS-terminating proxy (Cloudflare, Tailscale) and only receives plain HTTP, HSTS is not sent — the edge proxy is expected to handle it.
378+HSTS headers are sent automatically on TLS requests (when `r.TLS != nil`). When Gordon runs behind a TLS-terminating proxy (Cloudflare, Tailscale) and only receives plain HTTP, HSTS is not sent — the edge proxy is expected to handle it.
408379
409380 ## Examples
410381
@@ -412,35 +383,44 @@ HSTS headers are sent automatically on the HTTPS listener (when `r.TLS != nil`).
412383
413384 ```toml
414385 [server]
415-port = 8088
416386 registry_port = 5000
417387 gordon_domain = "gordon.local"
418388 data_dir = "./dev-data"
389+
390+[entrypoints.edge]
391+address = ":9000"
392+protocol = "smart_tcp"
419393 ```
420394
421395 ### Production Configuration
422396
423397 ```toml
424398 [server]
425-port = 8088
426399 registry_port = 5000
427400 gordon_domain = "gordon.company.com"
428401 data_dir = "/var/lib/gordon"
402+
403+[entrypoints.edge]
404+address = ":443"
405+protocol = "smart_tcp"
429406 ```
430407
431408 ### Rootless Container Setup
432409
433410 ```toml
434411 [server]
435-port = 8088 # High port for rootless
436412 registry_port = 5000
437413 gordon_domain = "gordon.mydomain.com"
414+
415+[entrypoints.edge]
416+address = ":9000" # High port for rootless
417+protocol = "smart_tcp"
438418 ```
439419
440420 With firewall port forwarding:
441421
442422 ```bash
443-sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=8088
423+sudo firewall-cmd --permanent --add-forward-port=port=443:proto=tcp:toport=9000
444424 sudo firewall-cmd --reload
445425 ```
446426
@@ -6,29 +6,29 @@ Core server settings for Gordon.
6 6
7 ```toml7 ```toml
8 [server]8 [server]
9-port = 8088 # HTTP proxy port
10 registry_port = 5000 # Container registry port9 registry_port = 5000 # Container registry port
11-tls_port = 8443 # HTTPS listener port (0 = disabled)10+gordon_domain = "gordon.mydomain.com" # Gordon domain (required)
12 # tls_cert_file = "" # Optional: PEM certificate for static TLS11 # tls_cert_file = "" # Optional: PEM certificate for static TLS
13 # tls_key_file = "" # Optional: PEM key for static TLS12 # tls_key_file = "" # Optional: PEM key for static TLS
14-# force_https_redirect = false # Redirect all HTTP traffic to HTTPS13+# force_https_redirect = false # Redirect cleartext HTTP requests to HTTPS
15-gordon_domain = "gordon.mydomain.com" # Gordon domain (required)
16 # legacy_registry_domains = ["registry.example.com:5000"]14 # legacy_registry_domains = ["registry.example.com:5000"]
17 # data_dir = "~/.gordon" # Data storage directory (default)15 # data_dir = "~/.gordon" # Data storage directory (default)
18 max_blob_chunk_size = "95MB" # Max registry blob upload chunk16 max_blob_chunk_size = "95MB" # Max registry blob upload chunk
19 max_blob_size = "1GB" # Max cumulative registry blob/layer upload17 max_blob_size = "1GB" # Max cumulative registry blob/layer upload
18+
19+[entrypoints.edge]
20+address = ":443" # Deployment-selected public TCP socket
21+protocol = "smart_tcp"
20 ```22 ```
21 23
22 ## Options24 ## Options
23 25
24 | Option | Type | Default | Description |26 | Option | Type | Default | Description |
25 |--------|------|---------|-------------|27 |--------|------|---------|-------------|
26-| `port` | int | `8088` | HTTP proxy port for routing traffic to containers |
27 | `registry_port` | int | `5000` | Docker registry port for image push/pull |28 | `registry_port` | int | `5000` | Docker registry port for image push/pull |
28-| `tls_port` | int | `8443` | HTTPS listener port with internal CA. Set to `0` to disable TLS entirely |29+| `tls_cert_file` | string | `""` | Path to PEM certificate file. When set with `tls_key_file`, matching SNI domains use this static cert before ACME or internal CA certs |
29-| `tls_cert_file` | string | `""` | Path to PEM certificate file. When set (with `tls_key_file`), this cert is served for matching SNI domains; unmatched domains fall through to the internal CA |
30 | `tls_key_file` | string | `""` | Path to PEM private key file. Must be set together with `tls_cert_file` |30 | `tls_key_file` | string | `""` | Path to PEM private key file. Must be set together with `tls_cert_file` |
31-| `force_https_redirect` | bool | `false` | Redirect all HTTP requests to the HTTPS port. For direct-access setups without a TLS-terminating proxy |31+| `force_https_redirect` | bool | `false` | Redirect cleartext HTTP requests to HTTPS on HTTP-capable edge entrypoints |
32 | `gordon_domain` | string | **required** | Domain for Gordon (registry + admin API) |32 | `gordon_domain` | string | **required** | Domain for Gordon (registry + admin API) |
33 | `registry_domain` | string | - | Deprecated migration key. Set `gordon_domain` instead. |33 | `registry_domain` | string | - | Deprecated migration key. Set `gordon_domain` instead. |
34 | `legacy_registry_domains` | []string | `[]` | Additional Gordon registry hosts treated as aliases during staged migration. See [Upgrading: Staged Registry Host Rename](../upgrading.md#staged-registry-host-rename). |34 | `legacy_registry_domains` | []string | `[]` | Additional Gordon registry hosts treated as aliases during staged migration. See [Upgrading: Staged Registry Host Rename](../upgrading.md#staged-registry-host-rename). |
@@ -37,46 +37,33 @@ max_blob_size = "1GB" # Max cumulative registry blob/layer up
37 | `max_blob_chunk_size` | string | `"95MB"` | Maximum request body size for a single registry blob upload chunk |37 | `max_blob_chunk_size` | string | `"95MB"` | Maximum request body size for a single registry blob upload chunk |
38 | `max_blob_size` | string | `"1GB"` | Maximum cumulative size for one registry blob/layer upload |38 | `max_blob_size` | string | `"1GB"` | Maximum cumulative size for one registry blob/layer upload |
39 | `registry_allowed_ips` | []string | `[]` | IPs or CIDR ranges allowed to access the registry (empty = allow all) |39 | `registry_allowed_ips` | []string | `[]` | IPs or CIDR ranges allowed to access the registry (empty = allow all) |
40-| `proxy_allowed_ips` | []string | `[]` | IPs or CIDR ranges allowed to reach the proxy (empty = allow all) |40+| `proxy_allowed_ips` | []string | `[]` | IPs or CIDR ranges allowed to reach HTTP proxy paths (empty = allow all) |
41 | `registry_listen_address` | string | `""` | Bind address for registry (empty = all interfaces) |41 | `registry_listen_address` | string | `""` | Bind address for registry (empty = all interfaces) |
42 42
43-## Port Configuration43+Public application traffic is configured under `[entrypoints]`. The standard edge listener is a `smart_tcp` socket:
44-
45-### Proxy Port
46-
47-The `port` setting controls where Gordon listens for HTTP traffic:
48 44
49 ```toml45 ```toml
50-[server]46+[entrypoints.edge]
51-port = 8088 # Default high port for rootless or forwarded setups47+address = ":443"
52-```48+protocol = "smart_tcp"
53-
54-For rootless containers, you'll typically use a high port and configure firewall port forwarding:
55-
56-```bash
57-# Forward port 80 to 8088
58-sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=8088
59 ```49 ```
60 50
61-### Internal CA and TLS51+`entrypoints.edge.address` is not an HTTP or HTTPS port. It is the TCP address Gordon sniffs for cleartext HTTP, h2c, TLS passthrough, normal HTTPS fallback, or explicit raw TCP fallback. Gordon does not provide a built-in default public port; choose `:443`, a high port such as `:9000`, or container/firewall mappings to match your deployment.
62 52
63-Gordon includes an internal certificate authority that issues on-demand TLS certificates for proxied domains. The HTTPS listener is enabled by default on `tls_port` (8443).53+## Internal CA and TLS
64 54
65-```toml55+Gordon includes an internal certificate authority that issues on-demand TLS certificates for proxied domains served through HTTPS fallback on a TLS-capable entrypoint such as `smart_tcp`.
66-[server]
67-tls_port = 8443 # Default. Set to 0 to disable TLS entirely.
68-```
69 56
70-With TLS enabled:57+With TLS-capable edge traffic:
71 - Gordon runs an internal CA (root + intermediate) stored in `{data_dir}/pki/`58 - Gordon runs an internal CA (root + intermediate) stored in `{data_dir}/pki/`
72 - Leaf certificates are issued on-demand per domain (SNI-based) and cached in memory59 - Leaf certificates are issued on-demand per domain (SNI-based) and cached in memory
73 - The intermediate CA auto-renews before expiry60 - The intermediate CA auto-renews before expiry
74-- An onboarding page at `https://<gordon-host>:<tls_port>/.well-known/gordon/` lets clients download the root CA certificate61+- An onboarding page at `https://<gordon-host>/.well-known/gordon/` lets clients download the root CA certificate
75 - The root CA is stable across restarts (generated once, persisted to disk)62 - The root CA is stable across restarts (generated once, persisted to disk)
76 63
77 #### Custom certificates64 #### Custom certificates
78 65
79-To use your own certificate (e.g. from Tailscale, Let's Encrypt, or a corporate CA) alongside the internal CA:66+To use your own certificate (e.g. from Tailscale, Let's Encrypt, or a corporate CA) alongside ACME/internal CA fallback:
80 67
81 ```toml68 ```toml
82 [server]69 [server]
@@ -84,15 +71,16 @@ tls_cert_file = "/etc/gordon/tls/cert.pem"
84 tls_key_file = "/etc/gordon/tls/key.pem"71 tls_key_file = "/etc/gordon/tls/key.pem"
85 ```72 ```
86 73
87-The static certificate is served for SNI-matching domains (including wildcards). All other domains get on-demand certs from the internal CA.74+Certificate priority for normal HTTPS fallback is: static certificates first, then public ACME certificates, then Gordon's internal CA.
88 75
89 #### Public ACME certificates76 #### Public ACME certificates
90 77
91-Gordon can obtain public certificates through Let's Encrypt-compatible ACME when `[tls.acme]` is enabled. The HTTPS listener must also be enabled (`server.tls_port > 0`).78+Gordon can obtain public certificates through Let's Encrypt-compatible ACME when `[tls.acme]` is enabled. Normal HTTPS fallback must be available on a TLS-capable entrypoint.
92 79
93 ```toml80 ```toml
94-[server]81+[entrypoints.edge]
95-tls_port = 844382+address = ":443"
83+protocol = "smart_tcp"
96 84
97 [dns]85 [dns]
98 resolvers = ["1.1.1.1:53", "8.8.8.8:53"]86 resolvers = ["1.1.1.1:53", "8.8.8.8:53"]
@@ -107,11 +95,12 @@ obtain_batch_size = 1 # maximum new certificate orders per reconcile run
107 ```95 ```
108 96
109 Challenge behavior:97 Challenge behavior:
110-- `http-01` serves `/.well-known/acme-challenge/<token>` on the HTTP proxy port.98+- `cloudflare-dns-01` uses a Cloudflare API token from `pass`, `GORDON_CLOUDFLARE_API_TOKEN_FILE`, or `GORDON_CLOUDFLARE_API_TOKEN`; it does not need a special public port 80 edge.
111-- `cloudflare-dns-01` uses a Cloudflare API token from `pass`, `GORDON_CLOUDFLARE_API_TOKEN_FILE`, or `GORDON_CLOUDFLARE_API_TOKEN`.99+- `http-01` serves `/.well-known/acme-challenge/<token>` through Gordon's HTTP handler and requires an HTTP-capable `smart_tcp` entrypoint reachable on external port 80 for each hostname being validated.
112 - `auto` selects Cloudflare DNS-01 when a token is available, otherwise falls back to HTTP-01.100 - `auto` selects Cloudflare DNS-01 when a token is available, otherwise falls back to HTTP-01.
101+- `tls-alpn-01` is not supported.
113 102
114-HTTP-01 requires public access to port 80 for each hostname being validated. If you use Cloudflare in DNS-only/gray-cloud mode, your firewall/NAT must allow direct public traffic to Gordon's HTTP proxy port. A firewall rule that only allows Cloudflare source IPs on port 80 is compatible with orange-cloud proxying, but it blocks gray-cloud HTTP-01 validation; use DNS-01 or temporarily open port 80 for direct validation.103+HTTP-01 requires public access to external port 80 for each hostname being validated. If you use Cloudflare in DNS-only/gray-cloud mode, your firewall/NAT must allow direct public traffic to the HTTP-capable smart TCP entrypoint. A firewall rule that only allows Cloudflare source IPs on port 80 is compatible with orange-cloud proxying, but it blocks gray-cloud HTTP-01 validation; use DNS-01 or temporarily open port 80 for direct validation.
115 104
116 Gordon limits new ACME certificate orders to `obtain_batch_size` per reconcile run (default `1`) so enabling ACME on an existing multi-route server does not burst through every route and hit Let's Encrypt rate limits. Later reloads, restarts, or other explicit reconcile runs continue issuing remaining certificates.105 Gordon limits new ACME certificate orders to `obtain_batch_size` per reconcile run (default `1`) so enabling ACME on an existing multi-route server does not burst through every route and hit Let's Encrypt rate limits. Later reloads, restarts, or other explicit reconcile runs continue issuing remaining certificates.
117 106
@@ -129,7 +118,7 @@ Because Gordon's ACME challenge mode is global, `cloudflare-dns-01` requires a C
129 118
130 #### Direct HTTP Onboarding119 #### Direct HTTP Onboarding
131 120
132-When `tls_port` is non-zero, direct HTTP clients (those not arriving through a trusted proxy) are restricted to CA onboarding paths only:121+When Gordon is serving TLS-capable edge traffic, direct cleartext HTTP clients (those not arriving through a trusted proxy) are restricted to CA onboarding paths only:
133 122
134 - `/.well-known/gordon/`, `/.well-known/gordon/ca`, `/.well-known/gordon/ca.crt`, `/.well-known/gordon/ca.mobileconfig` — serve the onboarding page and CA certificate downloads123 - `/.well-known/gordon/`, `/.well-known/gordon/ca`, `/.well-known/gordon/ca.crt`, `/.well-known/gordon/ca.mobileconfig` — serve the onboarding page and CA certificate downloads
135 - `/.well-known/acme-challenge/*` — serves ACME HTTP-01 challenges when public ACME HTTP-01 is enabled, otherwise returns `404 Not Found`124 - `/.well-known/acme-challenge/*` — serves ACME HTTP-01 challenges when public ACME HTTP-01 is enabled, otherwise returns `404 Not Found`
@@ -141,7 +130,7 @@ On HTTPS, onboarding paths are served only on `server.gordon_domain`; app hosts
141 130
142 #### HTTP to HTTPS redirect131 #### HTTP to HTTPS redirect
143 132
144-When clients connect directly (no Cloudflare or reverse proxy in front), enable `force_https_redirect` to redirect all HTTP traffic to the HTTPS port. Direct HTTP onboarding paths are still served even with this flag enabled.133+When clients connect directly (no Cloudflare or reverse proxy in front), enable `force_https_redirect` to redirect cleartext HTTP traffic to HTTPS on the same smart TCP edge.
145 134
146 ```toml135 ```toml
147 [server]136 [server]
@@ -150,40 +139,22 @@ force_https_redirect = true
150 139
151 When `proxy_allowed_ips` is configured, non-proxy clients are redirected automatically even without this flag — trusted proxy IPs pass through to serve Cloudflare-proxied traffic.140 When `proxy_allowed_ips` is configured, non-proxy clients are redirected automatically even without this flag — trusted proxy IPs pass through to serve Cloudflare-proxied traffic.
152 141
153-#### Disabling TLS142+#### Firewall and Container Port Mapping
154 143
155-For setups where TLS is handled entirely by an external proxy (Cloudflare, nginx, etc.):144+Choose the bind address in `[entrypoints.edge]` and map external ports to it as needed. For example, a rootless service can bind a high port while the firewall exposes 443:
156 145
157 ```toml146 ```toml
158-[server]147+[entrypoints.edge]
159-tls_port = 0 # No HTTPS listener, no internal CA generated148+address = ":9000"
149+protocol = "smart_tcp"
160 ```150 ```
161 151
162-#### Firewall port forwarding
163-
164-For rootless user services, forward 443 to the high port at the firewall:
165-
166 ```bash152 ```bash
167-# Forward 443 -> 8443153+sudo firewall-cmd --permanent --add-forward-port=port=443:proto=tcp:toport=9000
168-sudo firewall-cmd --permanent --add-forward-port=port=443:proto=tcp:toport=8443
169 sudo firewall-cmd --reload154 sudo firewall-cmd --reload
170 ```155 ```
171 156
172-With firewalld and Tailscale:157+`:443`, `:9000`, and container `-p 443:9000` mappings are deployment choices, not protocol modes.
173-
174-```bash
175-# Verify tailscale0 is in trusted zone
176-sudo firewall-cmd --get-zone-of-interface=tailscale0
177-
178-# If needed, place tailscale0 in trusted
179-sudo firewall-cmd --permanent --zone=trusted --add-interface=tailscale0
180-sudo firewall-cmd --reload
181-
182-# Forward 443 -> 8443 permanently (IPv4 + IPv6)
183-sudo firewall-cmd --permanent --zone=trusted --add-rich-rule='rule family=ipv4 forward-port port=443 protocol=tcp to-port=8443'
184-sudo firewall-cmd --permanent --zone=trusted --add-rich-rule='rule family=ipv6 forward-port port=443 protocol=tcp to-port=8443'
185-sudo firewall-cmd --reload
186-```
187 158
188 ### Registry Port159 ### Registry Port
189 160
@@ -223,11 +194,11 @@ For a staged rename, set `gordon_domain` to the new public host and add any old
223 194
224 Without this migration, a Host/remote-target mismatch can break routing or remote CLI token exchange.195 Without this migration, a Host/remote-target mismatch can break routing or remote CLI token exchange.
225 196
226-When requests arrive on the proxy port with this domain as the Host header, Gordon routes them to the backend services (registry and admin API).197+When requests arrive on an HTTP-capable edge entrypoint with this domain as the Host header, Gordon routes them to the backend services (registry and admin API).
227 198
228 Security note:199 Security note:
229 - Direct access to `/admin/*` on `registry_port` is blocked for non-loopback clients.200 - Direct access to `/admin/*` on `registry_port` is blocked for non-loopback clients.
230-- Admin API traffic should go through the main proxy listener (`server.port`/`server.tls_port`).201+- Admin API traffic should go through an HTTP-capable edge entrypoint such as `entrypoints.edge` with `protocol = "smart_tcp"`.
231 202
232 > **Note:** `registry_domain` is a deprecated migration key; use `gordon_domain` for new and upgraded configs.203 > **Note:** `registry_domain` is a deprecated migration key; use `gordon_domain` for new and upgraded configs.
233 204
@@ -404,7 +375,7 @@ registry_listen_address = "127.0.0.1" # Loopback only
404 375
405 ## HSTS376 ## HSTS
406 377
407-HSTS headers are sent automatically on the HTTPS listener (when `r.TLS != nil`). When Gordon runs behind a TLS-terminating proxy (Cloudflare, Tailscale) and only receives plain HTTP, HSTS is not sent — the edge proxy is expected to handle it.378+HSTS headers are sent automatically on TLS requests (when `r.TLS != nil`). When Gordon runs behind a TLS-terminating proxy (Cloudflare, Tailscale) and only receives plain HTTP, HSTS is not sent — the edge proxy is expected to handle it.
408 379
409 ## Examples380 ## Examples
410 381
@@ -412,35 +383,44 @@ HSTS headers are sent automatically on the HTTPS listener (when `r.TLS != nil`).
412 383
413 ```toml384 ```toml
414 [server]385 [server]
415-port = 8088
416 registry_port = 5000386 registry_port = 5000
417 gordon_domain = "gordon.local"387 gordon_domain = "gordon.local"
418 data_dir = "./dev-data"388 data_dir = "./dev-data"
389+
390+[entrypoints.edge]
391+address = ":9000"
392+protocol = "smart_tcp"
419 ```393 ```
420 394
421 ### Production Configuration395 ### Production Configuration
422 396
423 ```toml397 ```toml
424 [server]398 [server]
425-port = 8088
426 registry_port = 5000399 registry_port = 5000
427 gordon_domain = "gordon.company.com"400 gordon_domain = "gordon.company.com"
428 data_dir = "/var/lib/gordon"401 data_dir = "/var/lib/gordon"
402+
403+[entrypoints.edge]
404+address = ":443"
405+protocol = "smart_tcp"
429 ```406 ```
430 407
431 ### Rootless Container Setup408 ### Rootless Container Setup
432 409
433 ```toml410 ```toml
434 [server]411 [server]
435-port = 8088 # High port for rootless
436 registry_port = 5000412 registry_port = 5000
437 gordon_domain = "gordon.mydomain.com"413 gordon_domain = "gordon.mydomain.com"
414+
415+[entrypoints.edge]
416+address = ":9000" # High port for rootless
417+protocol = "smart_tcp"
438 ```418 ```
439 419
440 With firewall port forwarding:420 With firewall port forwarding:
441 421
442 ```bash422 ```bash
443-sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=8088423+sudo firewall-cmd --permanent --add-forward-port=port=443:proto=tcp:toport=9000
444 sudo firewall-cmd --reload424 sudo firewall-cmd --reload
445 ```425 ```
446 426
M docs/config/traffic.md +87 -43
@@ -1,25 +1,37 @@
11 # Traffic Plane Configuration
22
3-Gordon models network listeners as a traffic graph: entrypoints receive connections, routers select a service, and services resolve to backends.
3+Gordon models network listeners as a traffic graph: entrypoints receive packets or connections, routers select a service, and services resolve to backends.
44
5-## Entrypoints
5+## Smart TCP Edge Entrypoint
66
7-`server.port` creates the default HTTP entrypoint `web`. `server.tls_port` creates `websecure`, Gordon's default TLS mux entrypoint. `websecure` serves normal HTTPS routes and can also route TLS passthrough by ClientHello SNI. Use a custom `tls_mux` entrypoint when you want an additional port or isolation from the default HTTPS listener.
8-
9-Custom entrypoints use the top-level `entrypoints` table:
7+Public application traffic is normally exposed with a `smart_tcp` entrypoint. The conventional name is `edge`, but Gordon does not assign a built-in public port. Choose the address that matches your deployment, firewall, and container mapping:
108
119 ```toml
12-[entrypoints.postgres]
13-address = "0.0.0.0:5432"
14-protocol = "tcp"
15-trusted_cidrs = ["100.64.0.0/10"]
16-
17-[entrypoints.game]
18-address = "0.0.0.0:7777"
19-protocol = "udp"
10+[entrypoints.edge]
11+address = ":443"
12+protocol = "smart_tcp"
13+trusted_cidrs = []
2014 ```
2115
22-Protocols are `http`, `tls_mux`, `tcp`, and `udp`.
16+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.
17+
18+Supported entrypoint protocols are `smart_tcp`, `http`, `tls_mux`, `tcp`, and `udp`. `smart_tcp` is the primary public edge model; `tcp` and `udp` are for explicit L4 services, and UDP remains separate from the TCP entrypoint.
19+
20+## Smart TCP Dispatch Order
21+
22+For each accepted connection on a `smart_tcp` entrypoint Gordon:
23+
24+1. Applies entrypoint-wide `trusted_cidrs` to the peer socket IP.
25+2. Rejects PROXY protocol v1 and v2 prefixes. PROXY headers are not trusted or parsed.
26+3. Peeks a minimal prefix and replays the bytes to the selected handler/backend.
27+4. Dispatches cleartext HTTP/1.x and h2c prior-knowledge to Gordon's HTTP handler.
28+5. 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.
31+6. Dispatches unknown non-HTTP/non-TLS bytes only to an explicit raw fallback router, after raw-fallback source policy allows it.
32+7. Refuses the connection when no deterministic handler or allowed fallback exists.
33+
34+Malformed HTTP-looking or TLS-looking traffic is rejected instead of bypassing to raw fallback.
2335
2436 ## Network Services
2537
@@ -43,9 +55,63 @@ Service references use:
4355
4456 `static:<name>` is reserved and currently unsupported.
4557
58+## TLS Passthrough on Smart TCP
59+
60+TLS passthrough routers run on `smart_tcp` or `tls_mux` entrypoints and route by ClientHello SNI without terminating TLS.
61+
62+```toml
63+[entrypoints.edge]
64+address = ":443"
65+protocol = "smart_tcp"
66+
67+[[traffic.tls.routers]]
68+name = "raw-tls"
69+entrypoint = "edge"
70+sni = "raw.example.com"
71+service = "network_service:raw:tls"
72+```
73+
74+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. HTTPS application routes that do not match a passthrough SNI use Gordon's normal HTTPS fallback and certificate selection.
75+
76+## Raw TCP Fallback
77+
78+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.
79+
80+```toml
81+[entrypoints.edge]
82+address = ":443"
83+protocol = "smart_tcp"
84+raw_fallback = "ssh-fallback"
85+raw_fallback_trusted_cidrs = ["100.64.0.0/10"]
86+
87+[[traffic.tcp.routers]]
88+name = "ssh-fallback"
89+entrypoint = "edge"
90+service = "network_service:ssh:ssh"
91+```
92+
93+Use `raw_fallback_trusted_cidrs` for private raw fallback exposure. To expose raw fallback publicly, acknowledge it explicitly:
94+
95+```toml
96+[entrypoints.edge]
97+address = ":443"
98+protocol = "smart_tcp"
99+raw_fallback = "public-raw"
100+allow_public_raw_fallback = true
101+```
102+
103+`trusted_cidrs` and `raw_fallback_trusted_cidrs` both use the peer socket IP. They do not use `X-Forwarded-For`.
104+
46105 ## TCP Routers
47106
107+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`.
108+
48109 ```toml
110+[entrypoints.postgres]
111+address = "0.0.0.0:5432"
112+protocol = "tcp"
113+trusted_cidrs = ["100.64.0.0/10"]
114+
49115 [traffic.tcp]
50116 dial_timeout = "10s"
51117 idle_timeout = "5m"
@@ -58,11 +124,17 @@ entrypoint = "postgres"
58124 service = "network_service:postgres:db"
59125 ```
60126
61-A plain TCP entrypoint supports one TCP router. If `max_connections` is omitted or set to `0`, Gordon applies the safe runtime default of `1024` active connections per entrypoint.
127+If `max_connections` is omitted or set to `0`, Gordon applies the safe runtime default of `1024` active connections per entrypoint.
62128
63129 ## UDP Routers
64130
131+UDP is not sniffed or unified into smart TCP. Use a separate UDP entrypoint:
132+
65133 ```toml
134+[entrypoints.game]
135+address = "0.0.0.0:7777"
136+protocol = "udp"
137+
66138 [traffic.udp]
67139 idle_timeout = "30s"
68140 drain_timeout = "30s"
@@ -76,34 +148,6 @@ service = "network_service:game:game"
76148
77149 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.
78150
79-## TLS Passthrough
80-
81-TLS passthrough routers run on `tls_mux` entrypoints and route by ClientHello SNI without terminating TLS. The default `websecure` entrypoint can host normal HTTPS routes and TLS passthrough routes on the same port as long as their hosts do not conflict.
82-
83-```toml
84-[[traffic.tls.routers]]
85-name = "raw-tls"
86-entrypoint = "websecure"
87-sni = "raw.example.com"
88-service = "network_service:raw:tls"
89-```
90-
91-Custom `tls_mux` entrypoints are useful for additional ports:
92-
93-```toml
94-[entrypoints.rawtls]
95-address = "0.0.0.0:9443"
96-protocol = "tls_mux"
97-
98-[[traffic.tls.routers]]
99-name = "raw-tls-alt"
100-entrypoint = "rawtls"
101-sni = "raw-alt.example.com"
102-service = "network_service:raw:tls"
103-```
104-
105-Exact SNI matches win over wildcard matches. Ambiguous wildcard overlaps and HTTP-host/TLS-passthrough conflicts are rejected at validation time.
106-
107151 ## Related
108152
109153 - [Server Settings](./server.md)
@@ -1,25 +1,37 @@
1 # Traffic Plane Configuration1 # Traffic Plane Configuration
2 2
3-Gordon models network listeners as a traffic graph: entrypoints receive connections, routers select a service, and services resolve to backends.3+Gordon models network listeners as a traffic graph: entrypoints receive packets or connections, routers select a service, and services resolve to backends.
4 4
5-## Entrypoints5+## Smart TCP Edge Entrypoint
6 6
7-`server.port` creates the default HTTP entrypoint `web`. `server.tls_port` creates `websecure`, Gordon's default TLS mux entrypoint. `websecure` serves normal HTTPS routes and can also route TLS passthrough by ClientHello SNI. Use a custom `tls_mux` entrypoint when you want an additional port or isolation from the default HTTPS listener.7+Public application traffic is normally exposed with a `smart_tcp` entrypoint. The conventional name is `edge`, but Gordon does not assign a built-in public port. Choose the address that matches your deployment, firewall, and container mapping:
8-
9-Custom entrypoints use the top-level `entrypoints` table:
10 8
11 ```toml9 ```toml
12-[entrypoints.postgres]10+[entrypoints.edge]
13-address = "0.0.0.0:5432"11+address = ":443"
14-protocol = "tcp"12+protocol = "smart_tcp"
15-trusted_cidrs = ["100.64.0.0/10"]13+trusted_cidrs = []
16-
17-[entrypoints.game]
18-address = "0.0.0.0:7777"
19-protocol = "udp"
20 ```14 ```
21 15
22-Protocols are `http`, `tls_mux`, `tcp`, and `udp`.16+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.
17+
18+Supported entrypoint protocols are `smart_tcp`, `http`, `tls_mux`, `tcp`, and `udp`. `smart_tcp` is the primary public edge model; `tcp` and `udp` are for explicit L4 services, and UDP remains separate from the TCP entrypoint.
19+
20+## Smart TCP Dispatch Order
21+
22+For each accepted connection on a `smart_tcp` entrypoint Gordon:
23+
24+1. Applies entrypoint-wide `trusted_cidrs` to the peer socket IP.
25+2. Rejects PROXY protocol v1 and v2 prefixes. PROXY headers are not trusted or parsed.
26+3. Peeks a minimal prefix and replays the bytes to the selected handler/backend.
27+4. Dispatches cleartext HTTP/1.x and h2c prior-knowledge to Gordon's HTTP handler.
28+5. 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.
31+6. Dispatches unknown non-HTTP/non-TLS bytes only to an explicit raw fallback router, after raw-fallback source policy allows it.
32+7. Refuses the connection when no deterministic handler or allowed fallback exists.
33+
34+Malformed HTTP-looking or TLS-looking traffic is rejected instead of bypassing to raw fallback.
23 35
24 ## Network Services36 ## Network Services
25 37
@@ -43,9 +55,63 @@ Service references use:
43 55
44 `static:<name>` is reserved and currently unsupported.56 `static:<name>` is reserved and currently unsupported.
45 57
58+## TLS Passthrough on Smart TCP
59+
60+TLS passthrough routers run on `smart_tcp` or `tls_mux` entrypoints and route by ClientHello SNI without terminating TLS.
61+
62+```toml
63+[entrypoints.edge]
64+address = ":443"
65+protocol = "smart_tcp"
66+
67+[[traffic.tls.routers]]
68+name = "raw-tls"
69+entrypoint = "edge"
70+sni = "raw.example.com"
71+service = "network_service:raw:tls"
72+```
73+
74+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. HTTPS application routes that do not match a passthrough SNI use Gordon's normal HTTPS fallback and certificate selection.
75+
76+## Raw TCP Fallback
77+
78+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.
79+
80+```toml
81+[entrypoints.edge]
82+address = ":443"
83+protocol = "smart_tcp"
84+raw_fallback = "ssh-fallback"
85+raw_fallback_trusted_cidrs = ["100.64.0.0/10"]
86+
87+[[traffic.tcp.routers]]
88+name = "ssh-fallback"
89+entrypoint = "edge"
90+service = "network_service:ssh:ssh"
91+```
92+
93+Use `raw_fallback_trusted_cidrs` for private raw fallback exposure. To expose raw fallback publicly, acknowledge it explicitly:
94+
95+```toml
96+[entrypoints.edge]
97+address = ":443"
98+protocol = "smart_tcp"
99+raw_fallback = "public-raw"
100+allow_public_raw_fallback = true
101+```
102+
103+`trusted_cidrs` and `raw_fallback_trusted_cidrs` both use the peer socket IP. They do not use `X-Forwarded-For`.
104+
46 ## TCP Routers105 ## TCP Routers
47 106
107+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`.
108+
48 ```toml109 ```toml
110+[entrypoints.postgres]
111+address = "0.0.0.0:5432"
112+protocol = "tcp"
113+trusted_cidrs = ["100.64.0.0/10"]
114+
49 [traffic.tcp]115 [traffic.tcp]
50 dial_timeout = "10s"116 dial_timeout = "10s"
51 idle_timeout = "5m"117 idle_timeout = "5m"
@@ -58,11 +124,17 @@ entrypoint = "postgres"
58 service = "network_service:postgres:db"124 service = "network_service:postgres:db"
59 ```125 ```
60 126
61-A plain TCP entrypoint supports one TCP router. If `max_connections` is omitted or set to `0`, Gordon applies the safe runtime default of `1024` active connections per entrypoint.127+If `max_connections` is omitted or set to `0`, Gordon applies the safe runtime default of `1024` active connections per entrypoint.
62 128
63 ## UDP Routers129 ## UDP Routers
64 130
131+UDP is not sniffed or unified into smart TCP. Use a separate UDP entrypoint:
132+
65 ```toml133 ```toml
134+[entrypoints.game]
135+address = "0.0.0.0:7777"
136+protocol = "udp"
137+
66 [traffic.udp]138 [traffic.udp]
67 idle_timeout = "30s"139 idle_timeout = "30s"
68 drain_timeout = "30s"140 drain_timeout = "30s"
@@ -76,34 +148,6 @@ service = "network_service:game:game"
76 148
77 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.149 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.
78 150
79-## TLS Passthrough
80-
81-TLS passthrough routers run on `tls_mux` entrypoints and route by ClientHello SNI without terminating TLS. The default `websecure` entrypoint can host normal HTTPS routes and TLS passthrough routes on the same port as long as their hosts do not conflict.
82-
83-```toml
84-[[traffic.tls.routers]]
85-name = "raw-tls"
86-entrypoint = "websecure"
87-sni = "raw.example.com"
88-service = "network_service:raw:tls"
89-```
90-
91-Custom `tls_mux` entrypoints are useful for additional ports:
92-
93-```toml
94-[entrypoints.rawtls]
95-address = "0.0.0.0:9443"
96-protocol = "tls_mux"
97-
98-[[traffic.tls.routers]]
99-name = "raw-tls-alt"
100-entrypoint = "rawtls"
101-sni = "raw-alt.example.com"
102-service = "network_service:raw:tls"
103-```
104-
105-Exact SNI matches win over wildcard matches. Ambiguous wildcard overlaps and HTTP-host/TLS-passthrough conflicts are rejected at validation time.
106-
107 ## Related151 ## Related
108 152
109 - [Server Settings](./server.md)153 - [Server Settings](./server.md)
M docs/getting-started.md +6 -4
@@ -6,7 +6,7 @@ Deploy your first app with Gordon in under 5 minutes.
66
77 - A Linux VPS with Docker or Podman installed
88 - A domain pointing to your VPS (DNS A record)
9-- HTTPS setup: Cloudflare proxy, Tailscale, or Gordon's internal CA (`server.tls_port = 8443`)
9+- A public TCP edge address for Gordon (for example external `:443`, mapped to your chosen `entrypoints.edge.address`)
1010 - [pass](https://www.passwordstore.org/) (password manager) with GPG key initialized
1111
1212 ## 1. Install Gordon
@@ -73,11 +73,13 @@ Edit `~/.config/gordon/gordon.toml`:
7373
7474 ```toml
7575 [server]
76-port = 8088 # HTTP proxy port (default)
7776 registry_port = 5000 # Registry port
78-tls_port = 8443 # HTTPS listener (default)
7977 gordon_domain = "gordon.mydomain.com" # Registry + Admin API domain
8078
79+[entrypoints.edge]
80+address = ":443" # Public smart TCP edge (choose your bind/mapping)
81+protocol = "smart_tcp"
82+
8183 [routes]
8284 "app.mydomain.com" = "myapp:latest" # Domain → Image mapping
8385 ```
@@ -99,7 +101,7 @@ Why wildcard (`*`)?
99101
100102 If your DNS provider supports wildcard CNAME flattening (Cloudflare does), `* -> gordon.mydomain.com` is usually the cleanest option.
101103
102-> **Important for Cloudflare or other proxied setups:** when Gordon's internal CA is enabled (default `tls_port = 8443`), add your proxy edge IPs to `server.proxy_allowed_ips` or proxied HTTP traffic will get `403 Forbidden`. See [Installation](./installation.md#proxy-origin-allowlist). If you do not want Gordon to manage TLS at all, set `tls_port = 0`.
104+> **Important for Cloudflare or other proxied setups:** when Gordon serves HTTP paths behind Cloudflare or another proxy, add your proxy edge IPs to `server.proxy_allowed_ips` or proxied HTTP traffic can get `403 Forbidden`. See [Installation](./installation.md#proxy-origin-allowlist).
103105
104106 ## 6. Start Gordon as a Service
105107
@@ -6,7 +6,7 @@ Deploy your first app with Gordon in under 5 minutes.
6 6
7 - A Linux VPS with Docker or Podman installed7 - A Linux VPS with Docker or Podman installed
8 - A domain pointing to your VPS (DNS A record)8 - A domain pointing to your VPS (DNS A record)
9-- HTTPS setup: Cloudflare proxy, Tailscale, or Gordon's internal CA (`server.tls_port = 8443`)9+- A public TCP edge address for Gordon (for example external `:443`, mapped to your chosen `entrypoints.edge.address`)
10 - [pass](https://www.passwordstore.org/) (password manager) with GPG key initialized10 - [pass](https://www.passwordstore.org/) (password manager) with GPG key initialized
11 11
12 ## 1. Install Gordon12 ## 1. Install Gordon
@@ -73,11 +73,13 @@ Edit `~/.config/gordon/gordon.toml`:
73 73
74 ```toml74 ```toml
75 [server]75 [server]
76-port = 8088 # HTTP proxy port (default)
77 registry_port = 5000 # Registry port76 registry_port = 5000 # Registry port
78-tls_port = 8443 # HTTPS listener (default)
79 gordon_domain = "gordon.mydomain.com" # Registry + Admin API domain77 gordon_domain = "gordon.mydomain.com" # Registry + Admin API domain
80 78
79+[entrypoints.edge]
80+address = ":443" # Public smart TCP edge (choose your bind/mapping)
81+protocol = "smart_tcp"
82+
81 [routes]83 [routes]
82 "app.mydomain.com" = "myapp:latest" # Domain → Image mapping84 "app.mydomain.com" = "myapp:latest" # Domain → Image mapping
83 ```85 ```
@@ -99,7 +101,7 @@ Why wildcard (`*`)?
99 101
100 If your DNS provider supports wildcard CNAME flattening (Cloudflare does), `* -> gordon.mydomain.com` is usually the cleanest option.102 If your DNS provider supports wildcard CNAME flattening (Cloudflare does), `* -> gordon.mydomain.com` is usually the cleanest option.
101 103
102-> **Important for Cloudflare or other proxied setups:** when Gordon's internal CA is enabled (default `tls_port = 8443`), add your proxy edge IPs to `server.proxy_allowed_ips` or proxied HTTP traffic will get `403 Forbidden`. See [Installation](./installation.md#proxy-origin-allowlist). If you do not want Gordon to manage TLS at all, set `tls_port = 0`.104+> **Important for Cloudflare or other proxied setups:** when Gordon serves HTTP paths behind Cloudflare or another proxy, add your proxy edge IPs to `server.proxy_allowed_ips` or proxied HTTP traffic can get `403 Forbidden`. See [Installation](./installation.md#proxy-origin-allowlist).
103 105
104 ## 6. Start Gordon as a Service106 ## 6. Start Gordon as a Service
105 107
M docs/index.md +1 -1
@@ -81,7 +81,7 @@ Gordon runs on your VPS and provides:
8181 - Domain pointing to your server
8282 - Optional Cloudflare account or other reverse proxy for edge TLS
8383
84-> **Note:** Gordon can run behind Cloudflare/nginx, or terminate TLS directly via its internal CA (enabled by default on `server.tls_port`). Set `tls_port = 0` to disable.
84+> **Note:** Gordon can run behind Cloudflare/nginx, or terminate TLS directly on a smart TCP edge entrypoint via static certificates, public ACME certificates, or its internal CA.
8585
8686 ## Related
8787
@@ -81,7 +81,7 @@ Gordon runs on your VPS and provides:
81 - Domain pointing to your server81 - Domain pointing to your server
82 - Optional Cloudflare account or other reverse proxy for edge TLS82 - Optional Cloudflare account or other reverse proxy for edge TLS
83 83
84-> **Note:** Gordon can run behind Cloudflare/nginx, or terminate TLS directly via its internal CA (enabled by default on `server.tls_port`). Set `tls_port = 0` to disable.84+> **Note:** Gordon can run behind Cloudflare/nginx, or terminate TLS directly on a smart TCP edge entrypoint via static certificates, public ACME certificates, or its internal CA.
85 85
86 ## Related86 ## Related
87 87
M docs/installation.md +14 -10
@@ -115,7 +115,7 @@ EOF
115115
116116 ## Firewall Configuration
117117
118-Gordon needs ports accessible for the registry and proxy.
118+Gordon needs ports accessible for the registry and the public edge entrypoint.
119119
120120 ### Using firewalld
121121
@@ -128,9 +128,10 @@ sudo systemctl enable --now firewalld
128128 sudo firewall-cmd --permanent --add-service=http
129129 sudo firewall-cmd --permanent --add-service=https
130130
131-# For rootless containers, redirect privileged ports
132-sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=8088
133-sudo firewall-cmd --permanent --add-forward-port=port=443:proto=tcp:toport=8443
131+# For rootless services, bind entrypoints.edge.address to a high port (for example :9000)
132+# and redirect the external TCP edge ports your deployment uses.
133+sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=9000
134+sudo firewall-cmd --permanent --add-forward-port=port=443:proto=tcp:toport=9000
134135
135136 # Apply changes
136137 sudo firewall-cmd --reload
@@ -165,10 +166,13 @@ Minimum required configuration:
165166
166167 ```toml
167168 [server]
168-port = 8088
169169 registry_port = 5000
170170 gordon_domain = "gordon.yourdomain.com"
171171
172+[entrypoints.edge]
173+address = ":443"
174+protocol = "smart_tcp"
175+
172176 [routes]
173177 "app.yourdomain.com" = "myapp:latest"
174178 ```
@@ -249,22 +253,22 @@ Point your domains to your server:
249253
250254 ## Cloudflare SSL Mode
251255
252-Gordon serves HTTP on `server.port` and HTTPS on `server.tls_port` by default. Choose the Cloudflare mode that matches how you want Cloudflare to reach your origin.
256+Gordon serves public application traffic through HTTP-capable entrypoints such as `entrypoints.edge` with `protocol = "smart_tcp"`. Choose the Cloudflare mode that matches how you want Cloudflare to reach your origin.
253257
254258 | Mode | How it works | Use when |
255259 |------|-------------|----------|
256-| **Flexible** | Cloudflare terminates TLS; connects to Gordon on HTTP | You want edge HTTPS only, or you set `tls_port = 0` |
260+| **Flexible** | Cloudflare terminates TLS; connects to Gordon with cleartext HTTP | You want edge HTTPS only |
257261 | **Full (Strict)** | Cloudflare connects to Gordon over HTTPS with a valid cert | You want end-to-end HTTPS using Gordon's public ACME certs or a static cert (`tls_cert_file` / `tls_key_file`) |
258262
259263 Wrong mode causes: **521** (Cloudflare can't connect) or **525** (TLS handshake failed).
260264
261-> **Important:** When `tls_port != 0`, you must also set `proxy_allowed_ips` with Cloudflare edge IPs — see [Proxy Origin Allowlist](#proxy-origin-allowlist) below.
265+> **Important:** For Cloudflare-proxied HTTP paths, set `proxy_allowed_ips` with Cloudflare edge IPs — see [Proxy Origin Allowlist](#proxy-origin-allowlist) below.
262266
263-> **Rootless note:** Unprivileged users can't bind port 80. Forward port 80→8088 (or your configured port) via firewall — see the firewall section above.
267+> **Rootless note:** Unprivileged users can't bind privileged ports. Bind `entrypoints.edge.address` to a high port (for example `:9000`) and forward/map external ports to it via firewall or container settings.
264268
265269 ## Proxy Origin Allowlist
266270
267-When Gordon's internal CA is enabled (default: `tls_port = 8443`), HTTP requests from non-localhost IPs are restricted to certificate onboarding paths only. Cloudflare and other reverse proxies must be listed in `proxy_allowed_ips` to reach your applications.
271+When Gordon serves HTTP paths through a smart TCP edge, direct non-localhost HTTP requests can be restricted to certificate onboarding paths. Cloudflare and other reverse proxies must be listed in `proxy_allowed_ips` to reach your applications.
268272
269273 Add Cloudflare edge IPs to your `gordon.toml`:
270274
@@ -115,7 +115,7 @@ EOF
115 115
116 ## Firewall Configuration116 ## Firewall Configuration
117 117
118-Gordon needs ports accessible for the registry and proxy.118+Gordon needs ports accessible for the registry and the public edge entrypoint.
119 119
120 ### Using firewalld120 ### Using firewalld
121 121
@@ -128,9 +128,10 @@ sudo systemctl enable --now firewalld
128 sudo firewall-cmd --permanent --add-service=http128 sudo firewall-cmd --permanent --add-service=http
129 sudo firewall-cmd --permanent --add-service=https129 sudo firewall-cmd --permanent --add-service=https
130 130
131-# For rootless containers, redirect privileged ports131+# For rootless services, bind entrypoints.edge.address to a high port (for example :9000)
132-sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=8088132+# and redirect the external TCP edge ports your deployment uses.
133-sudo firewall-cmd --permanent --add-forward-port=port=443:proto=tcp:toport=8443133+sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=9000
134+sudo firewall-cmd --permanent --add-forward-port=port=443:proto=tcp:toport=9000
134 135
135 # Apply changes136 # Apply changes
136 sudo firewall-cmd --reload137 sudo firewall-cmd --reload
@@ -165,10 +166,13 @@ Minimum required configuration:
165 166
166 ```toml167 ```toml
167 [server]168 [server]
168-port = 8088
169 registry_port = 5000169 registry_port = 5000
170 gordon_domain = "gordon.yourdomain.com"170 gordon_domain = "gordon.yourdomain.com"
171 171
172+[entrypoints.edge]
173+address = ":443"
174+protocol = "smart_tcp"
175+
172 [routes]176 [routes]
173 "app.yourdomain.com" = "myapp:latest"177 "app.yourdomain.com" = "myapp:latest"
174 ```178 ```
@@ -249,22 +253,22 @@ Point your domains to your server:
249 253
250 ## Cloudflare SSL Mode254 ## Cloudflare SSL Mode
251 255
252-Gordon serves HTTP on `server.port` and HTTPS on `server.tls_port` by default. Choose the Cloudflare mode that matches how you want Cloudflare to reach your origin.256+Gordon serves public application traffic through HTTP-capable entrypoints such as `entrypoints.edge` with `protocol = "smart_tcp"`. Choose the Cloudflare mode that matches how you want Cloudflare to reach your origin.
253 257
254 | Mode | How it works | Use when |258 | Mode | How it works | Use when |
255 |------|-------------|----------|259 |------|-------------|----------|
256-| **Flexible** | Cloudflare terminates TLS; connects to Gordon on HTTP | You want edge HTTPS only, or you set `tls_port = 0` |260+| **Flexible** | Cloudflare terminates TLS; connects to Gordon with cleartext HTTP | You want edge HTTPS only |
257 | **Full (Strict)** | Cloudflare connects to Gordon over HTTPS with a valid cert | You want end-to-end HTTPS using Gordon's public ACME certs or a static cert (`tls_cert_file` / `tls_key_file`) |261 | **Full (Strict)** | Cloudflare connects to Gordon over HTTPS with a valid cert | You want end-to-end HTTPS using Gordon's public ACME certs or a static cert (`tls_cert_file` / `tls_key_file`) |
258 262
259 Wrong mode causes: **521** (Cloudflare can't connect) or **525** (TLS handshake failed).263 Wrong mode causes: **521** (Cloudflare can't connect) or **525** (TLS handshake failed).
260 264
261-> **Important:** When `tls_port != 0`, you must also set `proxy_allowed_ips` with Cloudflare edge IPs — see [Proxy Origin Allowlist](#proxy-origin-allowlist) below.265+> **Important:** For Cloudflare-proxied HTTP paths, set `proxy_allowed_ips` with Cloudflare edge IPs — see [Proxy Origin Allowlist](#proxy-origin-allowlist) below.
262 266
263-> **Rootless note:** Unprivileged users can't bind port 80. Forward port 80→8088 (or your configured port) via firewall — see the firewall section above.267+> **Rootless note:** Unprivileged users can't bind privileged ports. Bind `entrypoints.edge.address` to a high port (for example `:9000`) and forward/map external ports to it via firewall or container settings.
264 268
265 ## Proxy Origin Allowlist269 ## Proxy Origin Allowlist
266 270
267-When Gordon's internal CA is enabled (default: `tls_port = 8443`), HTTP requests from non-localhost IPs are restricted to certificate onboarding paths only. Cloudflare and other reverse proxies must be listed in `proxy_allowed_ips` to reach your applications.271+When Gordon serves HTTP paths through a smart TCP edge, direct non-localhost HTTP requests can be restricted to certificate onboarding paths. Cloudflare and other reverse proxies must be listed in `proxy_allowed_ips` to reach your applications.
268 272
269 Add Cloudflare edge IPs to your `gordon.toml`:273 Add Cloudflare edge IPs to your `gordon.toml`:
270 274
M docs/upgrading.md +56 -3
@@ -6,6 +6,59 @@ This guide covers breaking changes and migration steps between major versions.
66
77 Route keys must be plain hostnames. Use inline tables like `"app.example.com" = { image = "myapp:latest" }`. Gordon still reads legacy `http://...` route entries for backward compatibility and rewrites them on the next save. Update `[routes]`, CLI commands, and automation that reference the old values.
88
9+## Next major: Unified smart TCP entrypoints
10+
11+### Breaking: `server.port` / `server.tls_port` no longer define public listeners
12+
13+Public application traffic is configured entrypoint-first. Migrate old HTTP/HTTPS listener settings to a smart TCP edge entrypoint.
14+
15+**Before:**
16+
17+```toml
18+[server]
19+port = 8088
20+tls_port = 8443
21+registry_port = 5000
22+gordon_domain = "gordon.example.com"
23+```
24+
25+**After:**
26+
27+```toml
28+[server]
29+registry_port = 5000
30+gordon_domain = "gordon.example.com"
31+
32+[entrypoints.edge]
33+address = ":443"
34+protocol = "smart_tcp"
35+```
36+
37+`edge` is the conventional route default, but it has no built-in port. `address = ":443"`, `address = ":9000"`, firewall forwarding, and container mappings such as `-p 443:9000` are deployment choices, not protocol modes. The `smart_tcp` protocol accepts TCP, sniffs HTTP/h2c/TLS, routes TLS passthrough by SNI when configured, and otherwise uses normal Gordon HTTPS fallback.
38+
39+If you run rootless and cannot bind 443 directly, bind a high port and map external ports to it:
40+
41+```toml
42+[entrypoints.edge]
43+address = ":9000"
44+protocol = "smart_tcp"
45+```
46+
47+```bash
48+sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=9000
49+sudo firewall-cmd --permanent --add-forward-port=port=443:proto=tcp:toport=9000
50+sudo firewall-cmd --reload
51+```
52+
53+Keep `server.registry_port` for Docker/Podman push and pull traffic; it is separate from public app entrypoints.
54+
55+### ACME migration notes
56+
57+- DNS-01 (`cloudflare-dns-01`) does not require a special external port 80 edge.
58+- HTTP-01 requires an HTTP-capable `smart_tcp` entrypoint reachable on external port 80 for each hostname being validated.
59+- TLS-ALPN-01 is unsupported.
60+- Normal HTTPS fallback certificate priority is static certificates, then public ACME certificates, then Gordon's internal CA.
61+
962 ## v2.30.0 to v2.31.0
1063
1164 ### Breaking: Default Port Changes
@@ -117,9 +170,9 @@ If you have automation parsing status-like fields from `routes list`, migrate it
117170
118171 Gordon can now obtain public certificates with `[tls.acme]` using either `http-01` or `cloudflare-dns-01`.
119172
120-- `server.tls_port` must stay enabled (`> 0`)
121-- `http-01` needs public HTTP reachability for each hostname being validated
122-- `cloudflare-dns-01` needs a Cloudflare token with zone-read and DNS-edit access for every zone used by HTTPS routes
173+- Normal HTTPS fallback must be available on a TLS-capable entrypoint
174+- `http-01` needs an HTTP-capable smart TCP entrypoint reachable on external port 80 for each hostname being validated
175+- `cloudflare-dns-01` needs a Cloudflare token with zone-read and DNS-edit access for every zone used by HTTPS routes and does not require a special port-80 edge
123176 - `obtain_batch_size` limits new ACME orders per reconcile pass to reduce rate-limit spikes
124177
125178 ### New: DNS Resolver Settings for ACME DNS-01
@@ -6,6 +6,59 @@ This guide covers breaking changes and migration steps between major versions.
6 6
7 Route keys must be plain hostnames. Use inline tables like `"app.example.com" = { image = "myapp:latest" }`. Gordon still reads legacy `http://...` route entries for backward compatibility and rewrites them on the next save. Update `[routes]`, CLI commands, and automation that reference the old values.7 Route keys must be plain hostnames. Use inline tables like `"app.example.com" = { image = "myapp:latest" }`. Gordon still reads legacy `http://...` route entries for backward compatibility and rewrites them on the next save. Update `[routes]`, CLI commands, and automation that reference the old values.
8 8
9+## Next major: Unified smart TCP entrypoints
10+
11+### Breaking: `server.port` / `server.tls_port` no longer define public listeners
12+
13+Public application traffic is configured entrypoint-first. Migrate old HTTP/HTTPS listener settings to a smart TCP edge entrypoint.
14+
15+**Before:**
16+
17+```toml
18+[server]
19+port = 8088
20+tls_port = 8443
21+registry_port = 5000
22+gordon_domain = "gordon.example.com"
23+```
24+
25+**After:**
26+
27+```toml
28+[server]
29+registry_port = 5000
30+gordon_domain = "gordon.example.com"
31+
32+[entrypoints.edge]
33+address = ":443"
34+protocol = "smart_tcp"
35+```
36+
37+`edge` is the conventional route default, but it has no built-in port. `address = ":443"`, `address = ":9000"`, firewall forwarding, and container mappings such as `-p 443:9000` are deployment choices, not protocol modes. The `smart_tcp` protocol accepts TCP, sniffs HTTP/h2c/TLS, routes TLS passthrough by SNI when configured, and otherwise uses normal Gordon HTTPS fallback.
38+
39+If you run rootless and cannot bind 443 directly, bind a high port and map external ports to it:
40+
41+```toml
42+[entrypoints.edge]
43+address = ":9000"
44+protocol = "smart_tcp"
45+```
46+
47+```bash
48+sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=9000
49+sudo firewall-cmd --permanent --add-forward-port=port=443:proto=tcp:toport=9000
50+sudo firewall-cmd --reload
51+```
52+
53+Keep `server.registry_port` for Docker/Podman push and pull traffic; it is separate from public app entrypoints.
54+
55+### ACME migration notes
56+
57+- DNS-01 (`cloudflare-dns-01`) does not require a special external port 80 edge.
58+- HTTP-01 requires an HTTP-capable `smart_tcp` entrypoint reachable on external port 80 for each hostname being validated.
59+- TLS-ALPN-01 is unsupported.
60+- Normal HTTPS fallback certificate priority is static certificates, then public ACME certificates, then Gordon's internal CA.
61+
9 ## v2.30.0 to v2.31.062 ## v2.30.0 to v2.31.0
10 63
11 ### Breaking: Default Port Changes64 ### Breaking: Default Port Changes
@@ -117,9 +170,9 @@ If you have automation parsing status-like fields from `routes list`, migrate it
117 170
118 Gordon can now obtain public certificates with `[tls.acme]` using either `http-01` or `cloudflare-dns-01`.171 Gordon can now obtain public certificates with `[tls.acme]` using either `http-01` or `cloudflare-dns-01`.
119 172
120-- `server.tls_port` must stay enabled (`> 0`)173+- Normal HTTPS fallback must be available on a TLS-capable entrypoint
121-- `http-01` needs public HTTP reachability for each hostname being validated174+- `http-01` needs an HTTP-capable smart TCP entrypoint reachable on external port 80 for each hostname being validated
122-- `cloudflare-dns-01` needs a Cloudflare token with zone-read and DNS-edit access for every zone used by HTTPS routes175+- `cloudflare-dns-01` needs a Cloudflare token with zone-read and DNS-edit access for every zone used by HTTPS routes and does not require a special port-80 edge
123 - `obtain_batch_size` limits new ACME orders per reconcile pass to reduce rate-limit spikes176 - `obtain_batch_size` limits new ACME orders per reconcile pass to reduce rate-limit spikes
124 177
125 ### New: DNS Resolver Settings for ACME DNS-01178 ### New: DNS Resolver Settings for ACME DNS-01
M gordon.toml.example +8 -2
@@ -2,8 +2,6 @@
22 # Copy this file to ~/.config/gordon/gordon.toml and adjust for your domain.
33
44 [server]
5-port = 8088
6-tls_port = 8443
75 registry_port = 5000
86 gordon_domain = "gordon.example.com"
97 # legacy_registry_domains = ["registry.example.com:5000"]
@@ -20,6 +18,14 @@ gordon_domain = "gordon.example.com"
2018 # max_proxy_response_size = "1GB"
2119 # max_concurrent_connections = 10000
2220
21+[entrypoints.edge]
22+address = ":443"
23+protocol = "smart_tcp"
24+# trusted_cidrs = []
25+# raw_fallback = "ssh-fallback"
26+# raw_fallback_trusted_cidrs = ["100.64.0.0/10"]
27+# allow_public_raw_fallback = false
28+
2329 [dns]
2430 resolvers = ["1.1.1.1:53", "8.8.8.8:53"]
2531 propagation_timeout = "5m"
@@ -2,8 +2,6 @@
2 # Copy this file to ~/.config/gordon/gordon.toml and adjust for your domain.2 # Copy this file to ~/.config/gordon/gordon.toml and adjust for your domain.
3 3
4 [server]4 [server]
5-port = 8088
6-tls_port = 8443
7 registry_port = 50005 registry_port = 5000
8 gordon_domain = "gordon.example.com"6 gordon_domain = "gordon.example.com"
9 # legacy_registry_domains = ["registry.example.com:5000"]7 # legacy_registry_domains = ["registry.example.com:5000"]
@@ -20,6 +18,14 @@ gordon_domain = "gordon.example.com"
20 # max_proxy_response_size = "1GB"18 # max_proxy_response_size = "1GB"
21 # max_concurrent_connections = 1000019 # max_concurrent_connections = 10000
22 20
21+[entrypoints.edge]
22+address = ":443"
23+protocol = "smart_tcp"
24+# trusted_cidrs = []
25+# raw_fallback = "ssh-fallback"
26+# raw_fallback_trusted_cidrs = ["100.64.0.0/10"]
27+# allow_public_raw_fallback = false
28+
23 [dns]29 [dns]
24 resolvers = ["1.1.1.1:53", "8.8.8.8:53"]30 resolvers = ["1.1.1.1:53", "8.8.8.8:53"]
25 propagation_timeout = "5m"31 propagation_timeout = "5m"

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close