Server Configuration
Core server settings for Gordon.
Configuration
[server]
port = 8088 # HTTP proxy port
registry_port = 5000 # Container registry port
tls_port = 8443 # HTTPS listener port (0 = disabled)
# tls_cert_file = "" # Optional: PEM certificate for static TLS
# tls_key_file = "" # Optional: PEM key for static TLS
# force_https_redirect = false # Redirect all HTTP traffic to HTTPS
gordon_domain = "gordon.mydomain.com" # Gordon domain (required)
# data_dir = "~/.gordon" # Data storage directory (default)
max_blob_chunk_size = "95MB" # Max registry blob upload chunk
max_blob_size = "1GB" # Max cumulative registry blob/layer upload
Options
| Option |
Type |
Default |
Description |
port |
int |
8088 |
HTTP proxy port for routing traffic to containers |
registry_port |
int |
5000 |
Docker registry port for image push/pull |
tls_port |
int |
8443 |
HTTPS listener port with internal CA. Set to 0 to disable TLS entirely |
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 |
tls_key_file |
string |
"" |
Path to PEM private key file. Must be set together with tls_cert_file |
force_https_redirect |
bool |
false |
Redirect all HTTP requests to the HTTPS port. For direct-access setups without a TLS-terminating proxy |
gordon_domain |
string |
required |
Domain for Gordon (registry + admin API) |
registry_domain |
string |
- |
Deprecated migration key. Set gordon_domain instead. |
data_dir |
string |
~/.gordon |
Directory for registry data, logs, and env files |
max_proxy_body_size |
string |
"512MB" |
Maximum request body size for proxied requests |
max_blob_chunk_size |
string |
"95MB" |
Maximum request body size for a single registry blob upload chunk |
max_blob_size |
string |
"1GB" |
Maximum cumulative size for one registry blob/layer upload |
registry_allowed_ips |
[]string |
[] |
IPs or CIDR ranges allowed to access the registry (empty = allow all) |
proxy_allowed_ips |
[]string |
[] |
IPs or CIDR ranges allowed to reach the proxy (empty = allow all) |
registry_listen_address |
string |
"" |
Bind address for registry (empty = all interfaces) |
Port Configuration
Proxy Port
The port setting controls where Gordon listens for HTTP traffic:
[server]
port = 8080 # Use 8080 for rootless containers with port forwarding
For rootless containers, you'll typically use a high port and configure firewall port forwarding:
# Forward port 80 to 8080
sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=8080
Internal CA and TLS
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).
[server]
tls_port = 8443 # Default. Set to 0 to disable TLS entirely.
With TLS enabled:
- Gordon runs an internal CA (root + intermediate) stored in
{data_dir}/pki/
- Leaf certificates are issued on-demand per domain (SNI-based) and cached in memory
- The intermediate CA auto-renews before expiry
- An onboarding page at
https://<gordon-host>:<tls_port>/.well-known/gordon/ lets clients download the root CA certificate
- The root CA is stable across restarts (generated once, persisted to disk)
Custom certificates
To use your own certificate (e.g. from Tailscale, Let's Encrypt, or a corporate CA) alongside the internal CA:
[server]
tls_cert_file = "/etc/gordon/tls/cert.pem"
tls_key_file = "/etc/gordon/tls/key.pem"
The static certificate is served for SNI-matching domains (including wildcards). All other domains get on-demand certs from the internal CA.
Public ACME certificates
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).
[server]
tls_port = 8443
[tls.acme]
enabled = true
email = "admin@example.com"
challenge = "auto" # auto, http-01, or cloudflare-dns-01
obtain_batch_size = 1 # maximum new certificate orders per reconcile run
Challenge behavior:
http-01 serves /.well-known/acme-challenge/<token> on the HTTP proxy port.
cloudflare-dns-01 uses a Cloudflare API token from pass, GORDON_CLOUDFLARE_API_TOKEN_FILE, or GORDON_CLOUDFLARE_API_TOKEN.
auto selects Cloudflare DNS-01 when a token is available, otherwise falls back to HTTP-01.
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.
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.
If the initial ACME reconcile fails (e.g. due to a transient network error or misconfiguration), Gordon logs the failure but still starts the renewal loop for already cached certificates. Operators should fix the underlying error and restart/reload Gordon to trigger a new obtain attempt for missing certificates.
For Cloudflare Full/Strict, Cloudflare terminates browser TLS at the edge and connects to Gordon over HTTPS. A public ACME certificate served by Gordon is the preferred origin certificate because Cloudflare Strict can validate it without custom origin trust. Cloudflare Flexible mode (HTTPS at the edge, HTTP to Gordon) is not end-to-end HTTPS.
Static certificates have priority, then public ACME certificates, then Gordon's internal CA fallback. Gordon uses the go-acme/lego ACME client, including its DNS-provider support for Cloudflare DNS-01.
Direct HTTP Onboarding
When tls_port is non-zero, direct HTTP clients (those not arriving through a trusted proxy) are restricted to CA onboarding paths only:
/.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
/.well-known/acme-challenge/* — serves ACME HTTP-01 challenges when public ACME HTTP-01 is enabled, otherwise returns 404 Not Found
- All other paths return
403 Forbidden
This lets new clients discover and trust the internal CA over plain HTTP without exposing the full application. Trusted proxy traffic (e.g. from Cloudflare) continues through the normal HTTP proxy path unaffected.
On HTTPS, onboarding paths are served only on server.gordon_domain; app hosts can use their own routes without Gordon intercepting them. If gordon_domain is empty, HTTPS onboarding paths are not served at all to avoid intercepting app hosts.
HTTP to HTTPS redirect
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.
[server]
force_https_redirect = true
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.
Disabling TLS
For setups where TLS is handled entirely by an external proxy (Cloudflare, nginx, etc.):
[server]
tls_port = 0 # No HTTPS listener, no internal CA generated
Firewall port forwarding
For rootless user services, forward 443 to the high port at the firewall:
# Forward 443 -> 8443
sudo firewall-cmd --permanent --add-forward-port=port=443:proto=tcp:toport=8443
sudo firewall-cmd --reload
With firewalld and Tailscale:
# Verify tailscale0 is in trusted zone
sudo firewall-cmd --get-zone-of-interface=tailscale0
# If needed, place tailscale0 in trusted
sudo firewall-cmd --permanent --zone=trusted --add-interface=tailscale0
sudo firewall-cmd --reload
# Forward 443 -> 8443 permanently (IPv4 + IPv6)
sudo firewall-cmd --permanent --zone=trusted --add-rich-rule='rule family=ipv4 forward-port port=443 protocol=tcp to-port=8443'
sudo firewall-cmd --permanent --zone=trusted --add-rich-rule='rule family=ipv6 forward-port port=443 protocol=tcp to-port=8443'
sudo firewall-cmd --reload
Registry Port
The registry_port setting controls where Gordon accepts image pushes:
[server]
registry_port = 5000
Docker/Podman clients push to this port:
docker push registry.mydomain.com:5000/myapp:latest
If the registry is served over plain HTTP (no TLS), clients must be configured to allow insecure access. See Troubleshooting: insecure registry for Docker and Podman instructions.
Gordon Domain
The gordon_domain is required and must match your DNS configuration:
[server]
gordon_domain = "gordon.mydomain.com"
This domain is used for:
- Docker login and image push/pull operations
- Admin API access (
/admin/* endpoints)
- CLI remote targeting (
gordon routes --remote https://gordon.mydomain.com)
- Authentication endpoints (
/auth/*)
Warning: If you are upgrading an older config, copy server.registry_domain to server.gordon_domain before restarting.
Without this migration, a Host/remote-target mismatch can break routing or remote CLI token exchange.
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).
Security note:
- Direct access to
/admin/* on registry_port is blocked for non-loopback clients.
- Admin API traffic should go through the main proxy listener (
server.port/server.tls_port).
Note: registry_domain is a deprecated migration key; use gordon_domain for new and upgraded configs.
Data Directory
The data_dir setting controls where Gordon stores data:
[server]
data_dir = "~/.gordon" # Default for user installations
Directory Structure
~/.gordon/
├── registry/ # Container images and manifests
│ ├── blobs/
│ └── manifests/
├── env/ # Environment files per domain
│ ├── app_mydomain_com.env
│ └── api_mydomain_com.env
├── logs/ # Application and container logs
│ ├── gordon.log
│ ├── proxy.log
│ └── containers/
└── secrets/ # Secrets (unsafe backend only)
Permissions
Gordon creates directories with secure permissions:
- Directories:
0700 (owner only)
- Files:
0600 (owner only)
Maximum Proxy Body Size
The max_proxy_body_size setting limits the size of request bodies that Gordon will forward to backend containers:
[server]
max_proxy_body_size = "512MB" # Default
Purpose: Prevents resource exhaustion attacks from extremely large uploads through the proxy.
Supported units: B, KB, MB, GB, TB (case-insensitive, decimal/1024-based)
Examples:
max_proxy_body_size = "100MB" # Stricter limit
max_proxy_body_size = "1GB" # Allow larger uploads
max_proxy_body_size = "0" # No limit (not recommended)
Behavior:
- When a request exceeds the limit, Gordon returns
413 Request Entity Too Large
- The limit is applied before the request reaches the backend container
- Uploads in progress are terminated when the limit is exceeded
Use cases:
- File upload services: increase to
1GB or higher
- CI/CD artifact uploads: adjust based on your largest artifacts
- Tight security: reduce to
100MB or lower
- Default (512MB): suitable for most applications
Warning: Setting this to 0 (no limit) allows unlimited upload sizes, which can lead to disk exhaustion and DoS vulnerabilities. Always set a reasonable limit for production deployments.
Maximum Registry Blob Chunk Size
The max_blob_chunk_size setting limits the size of a single blob upload request to the registry (PATCH/PUT under /v2/*/blobs/uploads/*):
[server]
max_blob_chunk_size = "95MB" # Default
Purpose: Prevents excessive memory usage during blob uploads while allowing large real-world image layers.
Supported units: B, KB, MB, GB, TB (case-insensitive, decimal/1024-based)
Examples:
max_blob_chunk_size = "256MB" # Stricter limit
max_blob_chunk_size = "1GB" # Allow larger layers
Behavior:
- When a blob chunk exceeds the limit, Gordon returns
413 Request Entity Too Large
- Gordon CLI uploads image layers in 50MB chunks by default, and this setting caps the maximum chunk accepted per request
- Keep this below Cloudflare's 100MB per-request limit when operating behind Cloudflare; the default (
95MB) is fine
- Increase it only if you expect larger direct registry uploads from other clients
Maximum Registry Blob Size
The max_blob_size setting limits the cumulative size of one blob/layer upload across all chunks:
[server]
max_blob_size = "1GB" # Default
Purpose: Prevents disk exhaustion from multi-chunk uploads where each individual chunk is below max_blob_chunk_size but the total blob grows without bound.
Behavior:
- Gordon tracks the current upload size and rejects appends that would exceed the limit.
- Rejected uploads return an OCI-compatible
413 size error.
- Failed uploads are cleaned up so temporary blob data does not accumulate indefinitely.
Registry IP Allowlist
The registry_allowed_ips setting restricts registry access to specific IPs or IP ranges. Accepts both CIDR notation (100.64.0.0/10) and individual IPs (203.0.113.50). When set, only requests from listed addresses (plus localhost) can reach registry and auth endpoints. An empty list allows all traffic (default).
[server]
registry_allowed_ips = [
"100.64.0.0/10", # Tailscale CGNAT range
"203.0.113.50", # single IP (treated as /32)
]
Behavior:
- Single IPs are automatically converted to
/32 (IPv4) or /128 (IPv6)
- Denied requests receive
403 Forbidden
- Localhost (
127.0.0.0/8, ::1) is always allowed so Gordon can pull from its own registry
- Respects
trusted_proxies for accurate client IP extraction behind reverse proxies
- Applied before rate limiting and authentication
- Changes require a Gordon server restart; not picked up by config hot-reload
Use cases:
- Restrict registry to Tailscale network:
["100.64.0.0/10"]
- Restrict to private subnets:
["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
- Allow a specific CI server:
["100.64.0.0/10", "203.0.113.50"]
Via environment variable:
GORDON_SERVER_REGISTRY_ALLOWED_IPS=100.64.0.0/10
Proxy Origin IP Allowlist
The proxy_allowed_ips setting restricts which IPs can reach the proxy server. This prevents direct-to-origin attacks that bypass CDN protections (e.g. Cloudflare WAF, DDoS mitigation). When set, only connections from listed CIDRs (plus localhost) are accepted.
[server]
# Only accept proxy traffic from Cloudflare edge IPs
proxy_allowed_ips = [
"173.245.48.0/20", "103.21.244.0/22", "103.22.200.0/22",
"103.31.4.0/22", "141.101.64.0/18", "108.162.192.0/18",
"190.93.240.0/20", "188.114.96.0/20", "197.234.240.0/22",
"198.41.128.0/17", "162.158.0.0/15", "104.16.0.0/13",
"104.24.0.0/14", "172.64.0.0/13", "131.0.72.0/22",
]
Behavior:
- Checks the direct connection IP (
RemoteAddr), not X-Forwarded-For
- Localhost is always allowed
- Denied requests receive
403 Forbidden
- An empty list allows all traffic (default)
Important: Cloudflare publishes their IP ranges at cloudflare.com/ips. Keep your allowlist updated when Cloudflare adds new ranges.
Registry Listen Address
The registry_listen_address controls which interface the registry server binds to. By default it binds to all interfaces. Set to 127.0.0.1 to restrict registry access to localhost only, preventing containers from reaching the registry via the Docker bridge gateway.
[server]
registry_listen_address = "127.0.0.1" # Loopback only
HSTS
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.
Examples
Development Configuration
[server]
port = 8080
registry_port = 5000
gordon_domain = "gordon.local"
data_dir = "./dev-data"
Production Configuration
[server]
port = 8080
registry_port = 5000
gordon_domain = "gordon.company.com"
data_dir = "/var/lib/gordon"
Rootless Container Setup
[server]
port = 8080 # High port for rootless
registry_port = 5000
gordon_domain = "gordon.mydomain.com"
With firewall port forwarding:
sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=8080
sudo firewall-cmd --reload
source
1# Server Configuration
2
3Core server settings for Gordon.
4
5## Configuration
6
7```toml
8[server]
9port = 8088 # HTTP proxy port
10registry_port = 5000 # Container registry port
11tls_port = 8443 # HTTPS listener port (0 = disabled)
12# tls_cert_file = "" # Optional: PEM certificate for static TLS
13# tls_key_file = "" # Optional: PEM key for static TLS
14# force_https_redirect = false # Redirect all HTTP traffic to HTTPS
15gordon_domain = "gordon.mydomain.com" # Gordon domain (required)
16# data_dir = "~/.gordon" # Data storage directory (default)
17max_blob_chunk_size = "95MB" # Max registry blob upload chunk
18max_blob_size = "1GB" # Max cumulative registry blob/layer upload
19```
20
21## Options
22
23| Option | Type | Default | Description |
24|--------|------|---------|-------------|
25| `port` | int | `8088` | HTTP proxy port for routing traffic to containers |
26| `registry_port` | int | `5000` | Docker registry port for image push/pull |
27| `tls_port` | int | `8443` | HTTPS listener port with internal CA. Set to `0` to disable TLS entirely |
28| `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_key_file` | string | `""` | Path to PEM private key file. Must be set together with `tls_cert_file` |
30| `force_https_redirect` | bool | `false` | Redirect all HTTP requests to the HTTPS port. For direct-access setups without a TLS-terminating proxy |
31| `gordon_domain` | string | **required** | Domain for Gordon (registry + admin API) |
32| `registry_domain` | string | - | Deprecated migration key. Set `gordon_domain` instead. |
33| `data_dir` | string | `~/.gordon` | Directory for registry data, logs, and env files |
34| `max_proxy_body_size` | string | `"512MB"` | Maximum request body size for proxied requests |
35| `max_blob_chunk_size` | string | `"95MB"` | Maximum request body size for a single registry blob upload chunk |
36| `max_blob_size` | string | `"1GB"` | Maximum cumulative size for one registry blob/layer upload |
37| `registry_allowed_ips` | []string | `[]` | IPs or CIDR ranges allowed to access the registry (empty = allow all) |
38| `proxy_allowed_ips` | []string | `[]` | IPs or CIDR ranges allowed to reach the proxy (empty = allow all) |
39| `registry_listen_address` | string | `""` | Bind address for registry (empty = all interfaces) |
40
41## Port Configuration
42
43### Proxy Port
44
45The `port` setting controls where Gordon listens for HTTP traffic:
46
47```toml
48[server]
49port = 8080 # Use 8080 for rootless containers with port forwarding
50```
51
52For rootless containers, you'll typically use a high port and configure firewall port forwarding:
53
54```bash
55# Forward port 80 to 8080
56sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=8080
57```
58
59### Internal CA and TLS
60
61Gordon 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).
62
63```toml
64[server]
65tls_port = 8443 # Default. Set to 0 to disable TLS entirely.
66```
67
68With TLS enabled:
69- Gordon runs an internal CA (root + intermediate) stored in `{data_dir}/pki/`
70- Leaf certificates are issued on-demand per domain (SNI-based) and cached in memory
71- The intermediate CA auto-renews before expiry
72- An onboarding page at `https://<gordon-host>:<tls_port>/.well-known/gordon/` lets clients download the root CA certificate
73- The root CA is stable across restarts (generated once, persisted to disk)
74
75#### Custom certificates
76
77To use your own certificate (e.g. from Tailscale, Let's Encrypt, or a corporate CA) alongside the internal CA:
78
79```toml
80[server]
81tls_cert_file = "/etc/gordon/tls/cert.pem"
82tls_key_file = "/etc/gordon/tls/key.pem"
83```
84
85The static certificate is served for SNI-matching domains (including wildcards). All other domains get on-demand certs from the internal CA.
86
87#### Public ACME certificates
88
89Gordon 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`).
90
91```toml
92[server]
93tls_port = 8443
94
95[tls.acme]
96enabled = true
97email = "admin@example.com"
98challenge = "auto" # auto, http-01, or cloudflare-dns-01
99obtain_batch_size = 1 # maximum new certificate orders per reconcile run
100```
101
102Challenge behavior:
103- `http-01` serves `/.well-known/acme-challenge/<token>` on the HTTP proxy port.
104- `cloudflare-dns-01` uses a Cloudflare API token from `pass`, `GORDON_CLOUDFLARE_API_TOKEN_FILE`, or `GORDON_CLOUDFLARE_API_TOKEN`.
105- `auto` selects Cloudflare DNS-01 when a token is available, otherwise falls back to HTTP-01.
106
107HTTP-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.
108
109Gordon 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.
110
111If the initial ACME reconcile fails (e.g. due to a transient network error or misconfiguration), Gordon logs the failure but still starts the renewal loop for already cached certificates. Operators should fix the underlying error and restart/reload Gordon to trigger a new obtain attempt for missing certificates.
112
113For Cloudflare Full/Strict, Cloudflare terminates browser TLS at the edge and connects to Gordon over HTTPS. A public ACME certificate served by Gordon is the preferred origin certificate because Cloudflare Strict can validate it without custom origin trust. Cloudflare Flexible mode (HTTPS at the edge, HTTP to Gordon) is not end-to-end HTTPS.
114
115Static certificates have priority, then public ACME certificates, then Gordon's internal CA fallback. Gordon uses the `go-acme/lego` ACME client, including its DNS-provider support for Cloudflare DNS-01.
116
117#### Direct HTTP Onboarding
118
119When `tls_port` is non-zero, direct HTTP clients (those not arriving through a trusted proxy) are restricted to CA onboarding paths only:
120
121- `/.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
122- `/.well-known/acme-challenge/*` — serves ACME HTTP-01 challenges when public ACME HTTP-01 is enabled, otherwise returns `404 Not Found`
123- All other paths return `403 Forbidden`
124
125This lets new clients discover and trust the internal CA over plain HTTP without exposing the full application. Trusted proxy traffic (e.g. from Cloudflare) continues through the normal HTTP proxy path unaffected.
126
127On HTTPS, onboarding paths are served only on `server.gordon_domain`; app hosts can use their own routes without Gordon intercepting them. If `gordon_domain` is empty, HTTPS onboarding paths are not served at all to avoid intercepting app hosts.
128
129#### HTTP to HTTPS redirect
130
131When 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.
132
133```toml
134[server]
135force_https_redirect = true
136```
137
138When `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.
139
140#### Disabling TLS
141
142For setups where TLS is handled entirely by an external proxy (Cloudflare, nginx, etc.):
143
144```toml
145[server]
146tls_port = 0 # No HTTPS listener, no internal CA generated
147```
148
149#### Firewall port forwarding
150
151For rootless user services, forward 443 to the high port at the firewall:
152
153```bash
154# Forward 443 -> 8443
155sudo firewall-cmd --permanent --add-forward-port=port=443:proto=tcp:toport=8443
156sudo firewall-cmd --reload
157```
158
159With firewalld and Tailscale:
160
161```bash
162# Verify tailscale0 is in trusted zone
163sudo firewall-cmd --get-zone-of-interface=tailscale0
164
165# If needed, place tailscale0 in trusted
166sudo firewall-cmd --permanent --zone=trusted --add-interface=tailscale0
167sudo firewall-cmd --reload
168
169# Forward 443 -> 8443 permanently (IPv4 + IPv6)
170sudo firewall-cmd --permanent --zone=trusted --add-rich-rule='rule family=ipv4 forward-port port=443 protocol=tcp to-port=8443'
171sudo firewall-cmd --permanent --zone=trusted --add-rich-rule='rule family=ipv6 forward-port port=443 protocol=tcp to-port=8443'
172sudo firewall-cmd --reload
173```
174
175### Registry Port
176
177The `registry_port` setting controls where Gordon accepts image pushes:
178
179```toml
180[server]
181registry_port = 5000
182```
183
184Docker/Podman clients push to this port:
185
186```bash
187docker push registry.mydomain.com:5000/myapp:latest
188```
189
190If the registry is served over plain HTTP (no TLS), clients must be configured to allow insecure access. See [Troubleshooting: insecure registry](../reference/troubleshooting.md#server-gave-http-response-to-https-client) for Docker and Podman instructions.
191
192## Gordon Domain
193
194The `gordon_domain` is required and must match your DNS configuration:
195
196```toml
197[server]
198gordon_domain = "gordon.mydomain.com"
199```
200
201This domain is used for:
202- Docker login and image push/pull operations
203- Admin API access (`/admin/*` endpoints)
204- CLI remote targeting (`gordon routes --remote https://gordon.mydomain.com`)
205- Authentication endpoints (`/auth/*`)
206
207> **Warning:** If you are upgrading an older config, copy `server.registry_domain` to `server.gordon_domain` before restarting.
208
209Without this migration, a Host/remote-target mismatch can break routing or remote CLI token exchange.
210
211When requests arrive on the proxy port with this domain as the Host header, Gordon routes them to the backend services (registry and admin API).
212
213Security note:
214- Direct access to `/admin/*` on `registry_port` is blocked for non-loopback clients.
215- Admin API traffic should go through the main proxy listener (`server.port`/`server.tls_port`).
216
217> **Note:** `registry_domain` is a deprecated migration key; use `gordon_domain` for new and upgraded configs.
218
219## Data Directory
220
221The `data_dir` setting controls where Gordon stores data:
222
223```toml
224[server]
225data_dir = "~/.gordon" # Default for user installations
226```
227
228### Directory Structure
229
230```
231~/.gordon/
232├── registry/ # Container images and manifests
233│ ├── blobs/
234│ └── manifests/
235├── env/ # Environment files per domain
236│ ├── app_mydomain_com.env
237│ └── api_mydomain_com.env
238├── logs/ # Application and container logs
239│ ├── gordon.log
240│ ├── proxy.log
241│ └── containers/
242└── secrets/ # Secrets (unsafe backend only)
243```
244
245### Permissions
246
247 Gordon creates directories with secure permissions:
248 - Directories: `0700` (owner only)
249 - Files: `0600` (owner only)
250
251 ## Maximum Proxy Body Size
252
253 The `max_proxy_body_size` setting limits the size of request bodies that Gordon will forward to backend containers:
254
255 ```toml
256 [server]
257 max_proxy_body_size = "512MB" # Default
258 ```
259
260 **Purpose:** Prevents resource exhaustion attacks from extremely large uploads through the proxy.
261
262 **Supported units:** B, KB, MB, GB, TB (case-insensitive, decimal/1024-based)
263
264 **Examples:**
265 ```toml
266 max_proxy_body_size = "100MB" # Stricter limit
267 max_proxy_body_size = "1GB" # Allow larger uploads
268 max_proxy_body_size = "0" # No limit (not recommended)
269 ```
270
271 **Behavior:**
272 - When a request exceeds the limit, Gordon returns `413 Request Entity Too Large`
273 - The limit is applied before the request reaches the backend container
274 - Uploads in progress are terminated when the limit is exceeded
275
276 **Use cases:**
277 - File upload services: increase to `1GB` or higher
278 - CI/CD artifact uploads: adjust based on your largest artifacts
279 - Tight security: reduce to `100MB` or lower
280 - Default (512MB): suitable for most applications
281
282 > **Warning:** Setting this to `0` (no limit) allows unlimited upload sizes, which can lead to disk exhaustion and DoS vulnerabilities. Always set a reasonable limit for production deployments.
283
284## Maximum Registry Blob Chunk Size
285
286The `max_blob_chunk_size` setting limits the size of a single blob upload request to the registry (`PATCH`/`PUT` under `/v2/*/blobs/uploads/*`):
287
288```toml
289[server]
290max_blob_chunk_size = "95MB" # Default
291```
292
293**Purpose:** Prevents excessive memory usage during blob uploads while allowing large real-world image layers.
294
295**Supported units:** B, KB, MB, GB, TB (case-insensitive, decimal/1024-based)
296
297**Examples:**
298
299```toml
300max_blob_chunk_size = "256MB" # Stricter limit
301max_blob_chunk_size = "1GB" # Allow larger layers
302```
303
304**Behavior:**
305- When a blob chunk exceeds the limit, Gordon returns `413 Request Entity Too Large`
306- Gordon CLI uploads image layers in 50MB chunks by default, and this setting caps the maximum chunk accepted per request
307- Keep this below Cloudflare's 100MB per-request limit when operating behind Cloudflare; the default (`95MB`) is fine
308- Increase it only if you expect larger direct registry uploads from other clients
309
310## Maximum Registry Blob Size
311
312The `max_blob_size` setting limits the cumulative size of one blob/layer upload across all chunks:
313
314```toml
315[server]
316max_blob_size = "1GB" # Default
317```
318
319**Purpose:** Prevents disk exhaustion from multi-chunk uploads where each individual chunk is below `max_blob_chunk_size` but the total blob grows without bound.
320
321**Behavior:**
322- Gordon tracks the current upload size and rejects appends that would exceed the limit.
323- Rejected uploads return an OCI-compatible `413` size error.
324- Failed uploads are cleaned up so temporary blob data does not accumulate indefinitely.
325
326## Registry IP Allowlist
327
328The `registry_allowed_ips` setting restricts registry access to specific IPs or IP ranges. Accepts both CIDR notation (`100.64.0.0/10`) and individual IPs (`203.0.113.50`). When set, only requests from listed addresses (plus localhost) can reach registry and auth endpoints. An empty list allows all traffic (default).
329
330```toml
331[server]
332registry_allowed_ips = [
333 "100.64.0.0/10", # Tailscale CGNAT range
334 "203.0.113.50", # single IP (treated as /32)
335]
336```
337
338**Behavior:**
339- Single IPs are automatically converted to `/32` (IPv4) or `/128` (IPv6)
340- Denied requests receive `403 Forbidden`
341- Localhost (`127.0.0.0/8`, `::1`) is always allowed so Gordon can pull from its own registry
342- Respects `trusted_proxies` for accurate client IP extraction behind reverse proxies
343- Applied before rate limiting and authentication
344- Changes require a Gordon server restart; not picked up by config hot-reload
345
346**Use cases:**
347- Restrict registry to Tailscale network: `["100.64.0.0/10"]`
348- Restrict to private subnets: `["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]`
349- Allow a specific CI server: `["100.64.0.0/10", "203.0.113.50"]`
350
351Via environment variable:
352
353```bash
354GORDON_SERVER_REGISTRY_ALLOWED_IPS=100.64.0.0/10
355```
356
357## Proxy Origin IP Allowlist
358
359The `proxy_allowed_ips` setting restricts which IPs can reach the proxy server. This prevents direct-to-origin attacks that bypass CDN protections (e.g. Cloudflare WAF, DDoS mitigation). When set, only connections from listed CIDRs (plus localhost) are accepted.
360
361```toml
362[server]
363# Only accept proxy traffic from Cloudflare edge IPs
364proxy_allowed_ips = [
365 "173.245.48.0/20", "103.21.244.0/22", "103.22.200.0/22",
366 "103.31.4.0/22", "141.101.64.0/18", "108.162.192.0/18",
367 "190.93.240.0/20", "188.114.96.0/20", "197.234.240.0/22",
368 "198.41.128.0/17", "162.158.0.0/15", "104.16.0.0/13",
369 "104.24.0.0/14", "172.64.0.0/13", "131.0.72.0/22",
370]
371```
372
373**Behavior:**
374- Checks the direct connection IP (`RemoteAddr`), not `X-Forwarded-For`
375- Localhost is always allowed
376- Denied requests receive `403 Forbidden`
377- An empty list allows all traffic (default)
378
379**Important:** Cloudflare publishes their IP ranges at [cloudflare.com/ips](https://www.cloudflare.com/ips/). Keep your allowlist updated when Cloudflare adds new ranges.
380
381## Registry Listen Address
382
383The `registry_listen_address` controls which interface the registry server binds to. By default it binds to all interfaces. Set to `127.0.0.1` to restrict registry access to localhost only, preventing containers from reaching the registry via the Docker bridge gateway.
384
385```toml
386[server]
387registry_listen_address = "127.0.0.1" # Loopback only
388```
389
390## HSTS
391
392HSTS 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.
393
394## Examples
395
396### Development Configuration
397
398```toml
399[server]
400port = 8080
401registry_port = 5000
402gordon_domain = "gordon.local"
403data_dir = "./dev-data"
404```
405
406### Production Configuration
407
408```toml
409[server]
410port = 8080
411registry_port = 5000
412gordon_domain = "gordon.company.com"
413data_dir = "/var/lib/gordon"
414```
415
416### Rootless Container Setup
417
418```toml
419[server]
420port = 8080 # High port for rootless
421registry_port = 5000
422gordon_domain = "gordon.mydomain.com"
423```
424
425With firewall port forwarding:
426```bash
427sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=8080
428sudo firewall-cmd --reload
429```
430
431## Related
432
433- [Configuration Overview](./index.md)
434- [Installation Guide](../installation.md)
435- [Routes Configuration](./routes.md)