# GoDoxy: Benchmarks URL: /docs/godoxy/Benchmarks-and-Resource-Usage Source: https://raw.githubusercontent.com/yusing/godoxy-wiki/refs/heads/main/content/docs/godoxy/Benchmarks-and-Resource-Usage.mdx Benchmark and resource usage of GoDoxy ## TL;DR [#tldr] All rows below come from one full `shadowtree benchmark` run (four proxies) on **2026-05-03** (`preset=smoke`). They are useful for relative comparison on this machine and preset, not for universal rankings. * **Reused HTTP/1.1**: **Nginx** **\~72k req/s**; **Traefik** **\~42.5k**; **GoDoxy** **\~41.4k**; **Caddy** **\~34.6k**. * **Reused HTTP/2** (`h2load -m16`): **Nginx** **\~85k req/s**; **GoDoxy** **\~39.4k**; **Caddy** **\~31.3k**; **Traefik** **\~19.6k** — this run reported **0** h2load failures and **0** HTTP **5xx** for every proxy in the summary lines. * **Reused HTTP/3** (`h3bench`): **Nginx** **\~31.5k req/s**; **GoDoxy** **\~21.0k**; **Traefik** **\~17.5k**; **Caddy** **\~15.9k** (all **Failed: 0** in the h3bench summaries). * Fresh connection tests used **2000** requests with **32** concurrent connections (`h2load --h1 -m1` / `h2load -m1` / `h3bench -m1`). Every proxy completed **2000 / 2000** for **HTTP/1.1**, **HTTP/2**, and **HTTP/3** in this run. * **HTTP/1.1 cleartext** on ports **8080-8083** was **disabled** (`h1c=false`); enable with `h1c=true` for plaintext H1 comparable to TLS H1. ## Benchmark setup [#benchmark-setup] Source code: * [dev.compose.yml](https://github.com/yusing/godoxy/blob/main/dev.compose.yml) * [.shadowtree/benchmark.toml](https://github.com/yusing/godoxy/blob/main/.shadowtree/benchmark.toml) * [cmd/bench\_server/main.go](https://github.com/yusing/godoxy/blob/main/cmd/bench_server/main.go) Run all benchmark modes: ```shell shadowtree benchmark ``` From the repo root, `shadowtree help benchmark` prints the full typed argument list. Common invocations: ```shell # Presets: smoke (default), stable, stress shadowtree benchmark preset=stable shadowtree benchmark preset=smoke shadowtree benchmark preset=stress # Protocol toggles (defaults: h1/h2/h3 on, h1c off) shadowtree benchmark h1=false shadowtree benchmark h2=false shadowtree benchmark h3=false shadowtree benchmark h1c=true # cleartext HTTP/1.1 on :8080-:8083 shadowtree benchmark h1c=false # Connection mode (default: both) shadowtree benchmark connection_mode=reused shadowtree benchmark connection_mode=fresh shadowtree benchmark connection_mode=both # Combine typed args shadowtree benchmark preset=stable h1=false h3=false ``` Selected typed arguments (defaults come from the active preset unless you override): ```shell preset=smoke|stable|stress h1=true|false h2=true|false h3=true|false h1c=true|false connection_mode=both|reused|fresh target=GoDoxy|Traefik|Caddy|Nginx # case-insensitive; omit for all four duration=10s # whole-second duration threads=4 connections=32 streams=16 # concurrent streams per H2/H3 connection requests=2000 # requests per protocol in fresh mode fresh_connections=32 # concurrent connections for fresh runs; 0 uses connections h2load_warm_up_time=3s # reused-mode warm-up; 0 disables h2load_duration=0s # whole-second h2load duration; 0s derives from duration h3_tool=auto|h2load|h3bench latency_samples=5 runs=1 repeat_delay=1 upload_body_bytes=262144 compose_file=dev.compose.yml compose_manage=true|false compose_cleanup=true|false compose_recreate=true|false host=bench.domain.com # hostname for SNI and bench URLs ``` Built-in Shadowtree presets: | Preset | Duration | Connections | Streams | Fresh requests | Fresh connections | Throughput runs | Latency samples | | ------ | -------- | ----------: | ------: | -------------: | ----------------: | --------------: | --------------: | | smoke | 10s | 32 | 16 | 2000 | 32 | 1 | 5 | | stable | 30s | 64 | 16 | 20000 | 64 | 5 | 25 | | stress | 30s | 100 | 100 | 50000 | 100 | 3 | 10 | All three presets use **4** `h2load` threads and set a non-zero warm-up before reused throughput (smoke **3s**, stable/stress **5s**). When **`runs` is greater than 1**, the recipe repeats each throughput benchmark and prints a median / coefficient-of-variation summary (`stable`, `stress`, or your own `runs`). Override preset defaults with the typed args above. ### What gets tested [#what-gets-tested] The benchmark uses a Go upstream server that returns a 4096-byte response body. The recipe starts four reverse proxies and tests each through TLS listeners: | Proxy | HTTPS URL | | ------- | -------------------------------- | | GoDoxy | `https://bench.domain.com:8440/` | | Traefik | `https://bench.domain.com:8441/` | | Caddy | `https://bench.domain.com:8442/` | | Nginx | `https://bench.domain.com:8443/` | The benchmark preserves `bench.domain.com` as SNI and `:authority`, then connects to loopback with: * `h2load --connect-to=127.0.0.1:` for throughput tests * `curl --resolve` for probe-style checks HTTP/1.1 and HTTP/2 run over HTTPS. HTTP/2 is real TLS/ALPN H2, not h2c. The compose stack also exposes cleartext HTTP on **8080-8083** (`http://bench.domain.com:/`) for optional **HTTP/1.1 plaintext** baseline runs (`h1c=true`); when enabled, the recipe exercises the same `h2load --h1 -m1` scenarios without TLS. The stack also exposes UDP listeners for HTTP/3. HTTP/3 defaults to `h3_tool=auto`: use `h2load --h3` when available, otherwise build and run the bundled `cmd/h3bench` client. With `h3bench`, reused throughput uses a fixed-duration run; fresh one-request-per-connection uses `h3bench -m1` (as in this capture). Force `h3bench` explicitly with: ```shell shadowtree benchmark h3_tool=h3bench ``` Disable HTTP/3 with either: ```shell shadowtree benchmark h3=false ``` ## Run details [#run-details] | Setting | Value | | --------------------- | -------------------- | | Date | `2026-05-03` | | Preset | `smoke` | | Target hostname | `bench.domain.com` | | Duration | `10s` | | h2load warm-up | `3s` | | h2load threads | `4` | | TLS connections | `32` | | HTTP/2 streams | `16` (`h2load -m16`) | | HTTP/3 streams/conn | `16` | | Fresh requests | `2000` | | Fresh concurrency | `32` | | Upload probe size | `256 KiB` | | Latency probe samples | `5` per scenario | | HTTP/3 tool | `h3bench` | ## HTTP/1.1 cleartext baseline [#http11-cleartext-baseline] Cleartext HTTP/1.1 reuses the same `h2load` reused- and fresh-connection settings as TLS H1, but targets `http://bench.domain.com:808x/` with `--connect-to` to loopback. **This run did not enable it**, so there are no plaintext req/s or latency rows to publish here. Re-run with: ```shell shadowtree benchmark h1c=true ``` Console markers look like `[HTTP/1.1 cleartext reused] h2load --h1 -m1` and `[HTTP/1.1 cleartext fresh]`. ## Reused TLS throughput [#reused-tls-throughput] Persistent/reused connection tests show steady-state proxy throughput after connection setup. * **H1/H2**: `h2load`; throughput in req/s; transfer in MB/s; latency is mean `time for request`. * **H3**: `h3bench`; transfer in MiB/s; latency columns are percentiles plus average. * **Done**: completed requests in the timed window (includes warm-up / elapsed semantics from the recipe). * **Fail / 5xx**: h2load failures and HTTP 5xx responses. | Proxy | H1 req/s | H1 done | H1 MB/s | H1 latency | H2 req/s | H2 done | H2 fail | H2 5xx | H2 MB/s | H2 latency | H3 req/s | H3 done | H3 MiB/s | H3 p50 | H3 p90 | H3 avg | H3 p99 | | ------- | -------: | ------: | ------: | ---------: | -------: | ------: | ------: | -----: | ------: | ---------: | -------: | ------: | -------: | ------: | ------: | ------: | ------: | | Nginx | 71,969 | 719,689 | 294.4 | 0.44 ms | 84,965 | 849,646 | 0 | 0 | 340.9 | 5.72 ms | 31,504 | 315,708 | 123.1 | 12.1 ms | 31.3 ms | 16.2 ms | 77.8 ms | | GoDoxy | 41,356 | 413,557 | 167.5 | 0.77 ms | 39,377 | 393,768 | 0 | 0 | 154.7 | 12.55 ms | 20,961 | 209,965 | 81.9 | 21.4 ms | 42.6 ms | 24.3 ms | 76.1 ms | | Traefik | 42,481 | 424,806 | 172.1 | 0.75 ms | 19,649 | 196,493 | 0 | 0 | 77.2 | 25.78 ms | 17,504 | 175,546 | 68.4 | 25.4 ms | 51.6 ms | 29.1 ms | 87.4 ms | | Caddy | 34,598 | 345,977 | 140.6 | 0.92 ms | 31,310 | 313,104 | 0 | 0 | 123.0 | 15.99 ms | 15,852 | 159,011 | 61.9 | 27.5 ms | 57.0 ms | 32.1 ms | 108 ms | Notes: * Compare HTTP/3 only across HTTP/3 rows. `h3bench` and `h2load` report different latency and transfer metrics. * HTTP/2 `mean` latency in the H1/H2 columns is h2load mean **time for request**; under multiplexed load it reflects concurrent scheduling, not single-request latency. * h2load sometimes prints slightly different totals for “done/succeeded” and the HTTP/2 `status codes:` summary; the **Fail** and **5xx** columns follow the explicit `failed` / `5xx` fields from the tool output in this capture. ## Fresh TLS throughput [#fresh-tls-throughput] Fresh connection tests use **2000** new connections with one request each (`h2load --h1 -m1` for HTTP/1.1 and `h2load -m1` for HTTP/2`). HTTP/3 uses `h3bench -m1\` with the same **2000** requests / **32** connection settings. These numbers mostly reflect handshake, accept-loop, and short-lived QUIC or TCP connection behavior. `ok/fail` shows succeeded requests versus failed attempts out of **2000** (all **2000 / 0** in this run). | Proxy | H1 req/s | H1 MB/s | H1 ok/fail | H1 done | H2 req/s | H2 MB/s | H2 ok/fail | H2 done | H3 req/s | H3 MiB/s | H3 ok/fail | H3 done | | ------- | -------: | ------: | ---------- | ------: | -------: | ------: | ---------- | ------: | -------: | -------: | ---------- | ------: | | Nginx | 32,789 | 134.15 | 2000 / 0 | 2,000 | 34,973 | 140.34 | 2000 / 0 | 2,000 | 17,068 | 66.67 | 2000 / 0 | 2,000 | | GoDoxy | 23,358 | 94.58 | 2000 / 0 | 2,000 | 24,655 | 96.90 | 2000 / 0 | 2,000 | 15,353 | 59.97 | 2000 / 0 | 2,000 | | Traefik | 25,014 | 101.31 | 2000 / 0 | 2,000 | 25,563 | 100.47 | 2000 / 0 | 2,000 | 13,344 | 52.13 | 2000 / 0 | 2,000 | | Caddy | 25,259 | 102.67 | 2000 / 0 | 2,000 | 17,208 | 67.65 | 2000 / 0 | 2,000 | 10,665 | 41.66 | 2000 / 0 | 2,000 | ## Latency probes [#latency-probes] Probe tests send five samples per route and protocol. Values below are mean TTFB in milliseconds. Bodies or first SSE chunks are about 4 KiB. | Proxy | /json H1 | /json H2 | /json H3 | /upload H1 | /upload H2 | /upload H3 | /stream H1 | /stream H2 | /stream H3 | /sse H1 | /sse H2 | /sse H3 | | ------- | -------: | -------: | -------: | ---------: | ---------: | ---------: | ---------: | ---------: | ---------: | ------: | ------: | ------: | | Nginx | 2.71 | 4.51 | 4.23 | 3.50 | 4.55 | 5.17 | 3.08 | 2.92 | 3.47 | 2.66 | 2.54 | 3.22 | | GoDoxy | 2.30 | 3.33 | 4.88 | 2.14 | 3.19 | 4.77 | 2.58 | 2.51 | 2.29 | 3.84 | 2.70 | 2.26 | | Traefik | 2.74 | 3.69 | 3.78 | 3.50 | 3.37 | 4.28 | 3.46 | 2.76 | 3.56 | 3.05 | 2.83 | 3.46 | | Caddy | 3.38 | 3.44 | 4.89 | 4.75 | 2.98 | 5.56 | 3.55 | 3.03 | 2.95 | 2.98 | 2.62 | 3.22 | HTTP/3 probe latency includes QUIC setup behavior from the probe client. Treat those values as probe behavior, not pure steady-state HTTP/3 request latency. ## SSE probe [#sse-probe] SSE probes use `/sse?count=3&interval_ms=150`. Each result is five samples. The table shows `ok/failed` plus mean TTFB in milliseconds. | Proxy | H1 ok/fail | H1 TTFB | H2 ok/fail | H2 TTFB | H3 ok/fail | H3 TTFB | | ------- | ---------- | ------: | ---------- | ------: | ---------- | ------: | | Nginx | 5 / 0 | 2.66 | 5 / 0 | 2.54 | 5 / 0 | 3.22 | | GoDoxy | 5 / 0 | 3.84 | 5 / 0 | 2.70 | 5 / 0 | 2.26 | | Traefik | 5 / 0 | 3.05 | 5 / 0 | 2.83 | 5 / 0 | 3.46 | | Caddy | 5 / 0 | 2.98 | 5 / 0 | 2.62 | 5 / 0 | 3.22 | All SSE samples succeeded. Nginx had the lowest mean HTTP/1.1 SSE TTFB among the four in this run; GoDoxy was lowest on HTTP/3 SSE means in this snapshot. ## WebSocket probe [#websocket-probe] | Proxy | OK / 5 | Status | Mean TTFB | | ------- | -----: | -----: | --------: | | Nginx | 5 / 5 | 101 | 2.25 ms | | Traefik | 5 / 5 | 101 | 4.85 ms | | GoDoxy | 5 / 5 | 101 | 3.88 ms | | Caddy | 5 / 5 | 101 | 6.64 ms | ## Interpretation [#interpretation] * Reused HTTP/1.1: **Nginx** leads on req/s and mean **time for request**; **Traefik** edges **GoDoxy** slightly in this preset, with **Caddy** fourth. * Reused HTTP/2: **Nginx** leads by a wide margin on aggregate req/s with low reported mean **time for request**; **GoDoxy** and **Caddy** follow, then **Traefik**. This smoke preset did not show the high failure rates sometimes seen at much higher client/stream counts; re-run with heavier settings before inferring stability limits. * Reused HTTP/3: **Nginx** leads on req/s and transfer; **GoDoxy**, **Traefik**, and **Caddy** follow in that order here, with **Caddy** showing the highest **p99** among the four. * Fresh connections: **Nginx** leads on H1/H2/H3 instantaneous throughput in this harness; **GoDoxy** is second on HTTP/3 fresh, then **Traefik** and **Caddy**. * Latency probes: H1/H2 stay in the low-millisecond band on most routes; a single slow QUIC dial or first sample can pull HTTP/3 `/json` averages up (visible on **Caddy** and **GoDoxy** in this run). * Plaintext HTTP/1.1 was not exercised in this capture; set `h1c=true` to compare TLS vs cleartext H1 under the same recipe. ## Resource usage [#resource-usage] ``` CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS c2437c342388 godoxy-proxy 0.89% 38.02MiB / 853.7MiB 4.45% 0B / 0B 0B / 0B 12 80ad84f07d31 socket-proxy 0.00% 2.156MiB / 853.7MiB 0.25% 15.7kB / 48kB 0B / 0B 5 ``` # GoDoxy: Gallery URL: /docs/godoxy/Gallery Source: https://raw.githubusercontent.com/yusing/godoxy-wiki/refs/heads/main/content/docs/godoxy/Gallery.mdx Gallery of GoDoxy ## Homepage [#homepage] Homepage ## Metrics and System Info [#metrics-and-system-info] ### Routes [#routes] Routes ### Servers [#servers] Servers # GoDoxy: Setup URL: /docs/godoxy/Setup Source: https://raw.githubusercontent.com/yusing/godoxy-wiki/refs/heads/main/content/docs/godoxy/Setup.mdx Setup GoDoxy The wiki on `https://docs.godoxy.dev` reflects the latest changes in the repository. Open the wiki on your WebUI instance to read docs of the version you're running. ## Prerequisites [#prerequisites] Configure Wildcard DNS Record(s) to point to machine running `GoDoxy`, e.g. * A Record: `*.domain.com` -> `10.0.10.1` * AAAA Record (if you use IPv6): `*.domain.com` -> `::ffff:a00:a01` ## Automatic [#automatic] GoDoxy is designed to be running in `host` network mode, do not change it. To change listening ports, modify `.env`. 1. Prepare a new directory for docker compose and config files. 2. Run setup script inside the directory ```shell /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/yusing/godoxy/main/scripts/setup.sh)" ``` 3. You may now do some extra configuration on WebUI `https://godoxy.yourdomain.com` ## Manual Setup [#manual-setup] 1. Make `config` directory then grab `config.example.yml` into `config/config.yml` ```shell mkdir -p config && wget https://raw.githubusercontent.com/yusing/godoxy/main/config.example.yml -O config/config.yml ``` 2. Grab `.env.example` into `.env` ```shell wget https://raw.githubusercontent.com/yusing/godoxy/main/.env.example -O .env ``` 3. Grab `compose.example.yml` into `compose.yml` ```shell wget https://raw.githubusercontent.com/yusing/godoxy/main/compose.example.yml -O compose.yml ``` # GoDoxy: Access Control URL: /docs/godoxy/advanced-topics/Access-Control Source: https://raw.githubusercontent.com/yusing/godoxy-wiki/refs/heads/main/content/docs/godoxy/advanced-topics/Access-Control.mdx Configure access control for GoDoxy GoDoxy implements access control at two distinct network layers, each operating at different points in the request lifecycle: | Layer | Description | Blocking Behavior | | --------------------- | ------------------------------------------------ | ------------------------------ | | **Layer 4 (TCP/UDP)** | IP filtering when connection is established | Connection dropped immediately | | **Layer 7 (HTTP)** | IP filtering when HTTP request is fully received | HTTP error response returned | ## Layer 4 [#layer-4] Transport-layer filtering before HTTP data exchange. Blocked IPs see immediate connection drop with no response. | Use Case | | ----------------------- | | Blocking scanners early | ### Supported Filters [#supported-filters] | Type | Example | MaxMind Credentials Required | | ---------------- | ------------------ | ---------------------------- | | IP address | `ip:1.2.3.4` | No | | CIDR | `cidr:1.2.3.4/32` | No | | ISO country code | `country:US` | **Yes** | | Timezone | `tz:Asia/Shanghai` | **Yes** | ### ACL Configuration [#acl-configuration] | Key | Type | Description | Default | | ------------- | ----------------- | ------------------------------------------------------------------- | ------- | | `default` | `allow` or `deny` | Default action | `allow` | | `allow_local` | bool | Allow local addresses | `true` | | `allow` | Filter List | Allow list | `[]` | | `deny` | Filter List | Deny list | `[]` | | `log` | object | [Log configuration](#access-logging) with extra `log_allowed` field | `{}` | | `notify` | object | Periodically send access summary to notification provider | `{}` | ### ACL Notification Configuration [#acl-notification-configuration] | Key | Type | Description | Required | Default | | ----------------- | ----------- | ------------------------------------------- | -------- | ------- | | `to` | string list | List of notification provider names | No | `[]` | | `interval` | duration | Interval between notifications | No | `1m` | | `include_allowed` | bool | Include allowed connections in notification | No | `false` | ### ACL Example [#acl-example] ```yaml # config.yml acl: default: allow # or deny (default: allow) allow_local: true # or false (default: true) allow: - ip:1.2.3.4 - cidr:1.2.3.4/32 - country:US - tz:Asia/Shanghai deny: - ip:1.2.3.4 - cidr:1.2.3.4/32 - country:US - tz:Asia/Shanghai log: buffer_size: 65536 # (default: 64KB) path: /app/logs/acl.log # (default: none) stdout: false # (default: false) keep: last 10 # (default: none) log_allowed: true # (default: false) notify: to: [discord] interval: 1h include_allowed: true providers: maxmind: account_id: 123456 license_key: your-license-key database: geolite # or geoip2 if you have subscription notification: - name: discord provider: discord url: https://discord.com/api/webhooks/1234567890/abcdefghijklmnopqrstuvwxyz token: your-token ``` ## Layer 7 [#layer-7] HTTP-level filtering after request headers/body received. Blocked IPs see HTTP 403 error response. | Use Case | | ------------------------------ | | Traffic that requires feedback | HTTP Access loggers can be configured - in `config.yml` under entrypoint section - per route in docker labels or route files ### Request Whitelist Configuration [#request-whitelist-configuration] | Key | Type | Description | Required | Default | | ------------- | ------------ | ------------- | -------- | ---------------- | | `allow` | IP/CIDR list | Allow list | No | `[]` | | `status_code` | int | Status code | No | `403` | | `message` | string | Error message | No | `IP not allowed` | ### Request Whitelist Example [#request-whitelist-example] ```yaml # config.yml entrypoint: middlewares: # allow only local (private) ips - use: CIDRWhiteList allow: - 127.0.0.1/32 - 172.16.0.0/12 - 192.168.0.0/16 - 10.0.0.0/8 status_code: 403 message: Forbidden access_log: format: json path: /app/logs/access.json.log filters: # skip logging requests from local (private) ips cidr: negative: true values: - 127.0.0.1/32 - 172.16.0.0/12 - 192.168.0.0/16 - 10.0.0.0/8 fields: headers: default: drop # drop app headers in log config: # keep only these X-Real-Ip: keep CF-Connecting-Ip: keep X-Forwarded-For: keep # docker labels proxy.#1.middlewares.cidr_whitelist: | allow: - 10.0.0.0/8 - 192.168.0.0/16 status_code: 403 message: "IP not allowed" ``` ## Access Logging [#access-logging] ### Common Fields [#common-fields] | Key | Type | Description | Allowed Values | Default | | --------------------- | -------------------------- | ------------------------------------------------------ | --------------------- | ------------ | | `path` | string | Path to the access log file | `/var/log/access.log` | **required** | | `stdout` | bool | Enable stdout logging **(can be used with path)** | `true` or `false` | `false` | | `keep` or `retention` | [See below](#log-rotation) | Retention policy | | `30 days` | | `rotate_interval` | duration | Log rotation interval | Duration | `1h` | ### Log rotation [#log-rotation] To enable log rotation, you may set `keep` or `retention` to a retention policy. **Default**: `30 days` Format: * `{N} days|weeks|months` *(e.g. `30 days`)* * `{N} KB|MB|GB|kb|mb|gb` *(e.g. `100 MB` for 100 Megabytes, `100 mb` for 100 Megabits)* * `last {N}` *(e.g. `last 10` for last 10 lines)* ### HTTP Access Log [#http-access-log] | Field | Type | Description | Allowed values | Default | | ----------------------------- | --------------------------------------------------------- | ------------------------------------ | ----------------------------------- | ---------- | | `format` | string | access log format | `common`, `combined`, `json` | `combined` | | `filters` | object | access log filters *(optional)* | | | | `filters.*.negative` | bool | negative filters | `true` or `false` | `false` | | `filters.status_codes.values` | integer or integer range | status code filters | | | | `filters.method.values` | string | method filters | `GET`, `POST`, ... | | | `filters.host.values` | string | host filters | hostname | | | `filters.headers.values` | string | headers filters | case-sensitive `key` or `key=value` | | | `filters.cidr.values` | string | CIDR filters | see below | | | `fields` | object | access log fields | see below | | | `fields.*.default` | string (field mode) | default field behavior | `keep`, `drop`, `redact` | See below | | `fields.*.config` | `key:value` mapping **(key is case-sensitive)** | headers fields | `key: field_mode` | | | `fields.headers.config.*` | string | headers fields | `header: field_mode` | | | `fields.query.config.*` | string | query fields | `query: field_mode` | | | `fields.cookies.config.*` | string | cookies fields | `cookie: field_mode` | | `format` affects file logging only. #### Field Behavior [#field-behavior] | Setting | Behavior | | -------- | ------------------------------------ | | `keep` | Field is logged as-is | | `drop` | Field is excluded from logs | | `redact` | Field value replaced with `REDACTED` | * Multiple access loggers can share the same log file * Negative filters (`filters.*.negative: true`) exclude matching requests from logging * Query redaction format: `?key=REDACTED` * Redaction for non-query fields requires `format: json` * **Default field config:** * `query.default`: `keep` * `cookies.default`: `drop` * `headers.default`: `drop` ### ACL Log [#acl-log] | Field | Type | Description | Required | Allowed values | Default | | ------------- | ---- | ---------------------- | -------- | ----------------- | ------- | | `log_allowed` | bool | log when IP is allowed | bool | `true` or `false` | `false` | ## Full Example [#full-example] ```yaml # config.yml entrypoint: access_log: format: json path: /var/log/example.log filters: status_codes: values: - 200-299 - 101 method: values: - GET host: values: - example.y.z headers: negative: true values: - foo=bar # when key "foo" is present and value is `bar` - baz # when key "baz" is present cidr: values: - 192.168.10.0/24 fields: headers: default: keep config: foo: redact query: default: drop config: foo: keep cookies: default: redact config: foo: keep # route file # same as above, but under your app config, e.g. app1: access_log: format: json ... # docker labels - string as inline mapping proxy.app1.access_log: | format: json ... # docker labels - full label proxy.app1.access_log.format: json proxy.app1.access_log.filters.status_codes.values: 200-299,300 proxy.app1.access_log.fields.headers.config.foo: redact ``` # GoDoxy: Certificates URL: /docs/godoxy/advanced-topics/Certificates Source: https://raw.githubusercontent.com/yusing/godoxy-wiki/refs/heads/main/content/docs/godoxy/advanced-topics/Certificates.mdx Configure certificates for GoDoxy ## TL;DR [#tldr] * **Main certificate** covers all domains in `autocert.domains` * **Extra certificates** can be configured for additional domains * SNI selection: exact match > wildcard match, main cert > extra cert * Managed by [`lego`](https://github.com/go-acme/lego) (ACME) with DNS-01; auto-issued and auto-renewed * Bring your own certificate with `provider: local` * Short alias vs FQDN determines how domains match * `match_domains` restricts which base domains are valid * **TCP on HTTPS**: stream routes listening on the configured `HTTPS_ADDR` are matched by **TLS SNI** (same rules as HTTP). Without `tls_termination`, bytes are forwarded unchanged; with `tls_termination: true` and `autocert`, Godoxy terminates TLS and proxies **plaintext** to the upstream TCP port. Registration rejects TLS termination when no autocert provider is configured ## Certificates [#certificates] * Main certificate covers every domain in `autocert.domains` * Extra certificates can be added via the `extra` field for additional domains * Based on **SNI (Server Name Indication)** from the client's TLS handshake * Exact match > wildcard match (e.g., `app.example.com` exact beats `*.example.com` wildcard) * Main cert > extra certificates when both match * Falls back to main cert if no match * Issued/managed by `lego` using ACME, typically Let's Encrypt via DNS-01 * Auto-issue/renew with 1-hour cooldown after failures. Renewal happens when: * `autocert` is enabled but no certs are present * The set of `autocert.domains` no longer matches the loaded certificate * The certificate will expire within 30 days ### TCP stream routes on the HTTPS listener [#tcp-stream-routes-on-the-https-listener] TCP routes whose listen address is the **HTTPS** entrypoint share the same TLS socket as the web UI and reverse proxies. GoDoxy inspects the **ClientHello SNI** to pick the stream route, using the same naming rules as HTTP routes: * **Short alias**: if SNI is `mqtt.example.com` and the route key is `mqtt`, the first label (`mqtt`) can select that route when it is registered under that name. * **FQDN**: the full normalized hostname can match the route when that is how the route is keyed. **Passthrough (default)** — After SNI matching, the encrypted stream is forwarded as-is to `host:port`. Use this when the upstream speaks TLS (for example the upstream terminates HTTPS). **Termination** — Set `tls_termination: true` only on a **TCP** route listening on the configured shared `HTTPS_ADDR`. Route validation rejects other listeners, and SNI route registration rejects TLS termination unless an operational **`autocert`** provider exists. GoDoxy matches ClientHello SNI, completes the TLS handshake using **`autocert`**, then proxies a **plaintext** TCP connection to the configured upstream. Leave `tls_termination` disabled for SNI passthrough when autocert is absent or when the upstream should serve its own certificate. #### Extra Certificates [#extra-certificates] * Must specify unique `cert_path` and `key_path` (no duplicates) * Inherit all configuration from the main config (except `extra`) * Participate in ACME cycles independently * Selected via SNI when the client's requested domain matches (exact or wildcard) * Main certificate takes precedence when both match ### Autocert Configuration [#autocert-configuration] | Field | Type | Default | Required | Description | | ---------------------- | ------ | ---------------- | --------------------- | --------------------------------------------------------------------------------- | | `provider` | string | local | Yes | Certificate / DNS-01 provider | | `email` | string | - | Yes | ACME email | | `domains` | array | - | Yes | Certificate domains | | `options` | object | - | `provider` != `local` | Provider-specific options | | `resolvers` | array | - | No | DNS resolvers | | `cert_path` | string | `certs/cert.crt` | No | Certificate file path | | `key_path` | string | `certs/priv.key` | No | Private key file path | | `extra` | array | - | No | Additional certificates | | `ca_dir_url` | string | - | No | CA directory URL | | `ca_certs` | array | - | No | CA certificates to use | | `eab_kid` | string | - | No | EAB Key ID | | `eab_hmac` | string | - | No | Base64 encoded EAB¹ HMAC | | `certificate_key_type` | string | `EC256` | No | Private key algorithm for **ACME-issued** leaf certs (lego `Certificate.KeyType`) | 1. EAB refers to External Account Binding. **`certificate_key_type`** applies only when GoDoxy obtains or renews the certificate via ACME (not for `provider: local` static files). Default is elliptic curve P-256 (`EC256`). Allowed values: `EC256`, `EC384`, `RSA2048`, `RSA3072`, `RSA4096`, `RSA8192` (case-insensitive). Aliases: `P256` / `P384` for EC, and bare sizes `2048`, `3072`, `4096`, `8192` for RSA. Invalid values are rejected during config validation. On an `extra` entry, a non-empty `certificate_key_type` overrides the main `autocert` setting for that certificate’s lego config. ### Using Existing SSL Certificate [#using-existing-ssl-certificate] ```yaml autocert: provider: local # path relative to /app cert_path: certs/cert.crt key_path: certs/priv.key ``` ### Autocert with Cloudflare [#autocert-with-cloudflare] Use `certificate_key_type` when you need an RSA leaf (for example clients without ECDSA, such as some IoT TLS stacks). See the table above for all allowed values. ```yaml autocert: provider: cloudflare email: your-email@example.com domains: - "*.yourdomain.com" # certificate_key_type: RSA2048 options: auth_token: your-zone-api-token ``` ### Autocert with a Custom Internal CA [#autocert-with-a-custom-internal-ca] You may use internal CA like [step-ca](https://github.com/smallstep/certificates) for issuing certificates. Use `step-ca` as an example: ```bash export ACME_URL=https://acme.internal # get root certs and save to `certs/roots.pem` # assume that `certs/` is mounted to `/app/certs` (by default) curl -k https://${ACME_URL}/roots.pem > certs/roots.pem ``` ```yaml autocert: provider: custom email: your-email@example.com domains: - "*.yourdomain.com" ca_dir_url: https://acme.internal/acme/acme/directory ca_certs: - certs/roots.pem ``` #### EAB [#eab] If using EAB (External Account Binding), set `eab_kid` and `eab_hmac` in `autocert`. Also works with custom ACME CAs. ```yaml autocert: provider: custom email: your-email@example.com domains: - "*.yourdomain.com" eab_kid: your-eab-kid eab_hmac: base64-encoded-hmac ``` ### Multiple Certificates with SNI [#multiple-certificates-with-sni] Configure multiple certificates for different domains. GoDoxy uses SNI to select the appropriate certificate during TLS handshake. `domains` is ignored for `local` provider. **Example: Multiple certificates with different providers** ```yaml autocert: provider: cloudflare email: your-email@example.com domains: - "*.example.com" options: auth_token: your-zone-api-token extra: # Extra cert 1: Same provider, different domain - cert_path: certs/other.crt key_path: certs/other.key domains: - "*.other.com" # Inherits provider, email, options from main config # Extra cert 2: Different provider (custom CA) - cert_path: certs/internal.crt key_path: certs/internal.key provider: custom # certificate_key_type: RSA2048 # optional; overrides main autocert when set domains: - "*.internal.local" ca_dir_url: https://ca.internal/acme/directory # Extra cert 3: Local/static certificate - cert_path: certs/static.crt key_path: certs/static.key provider: local domains: - "*.services.internal" # Static certificate, no ACME obtain/renew ``` **SNI Selection Behavior:** Based on the example above, certificate selection works as follows: | Hostname | Selected Certificate | Match Type | | ------------------------ | -------------------- | -------------------------------------- | | `app.example.com` | Main cert | Exact match (`*.example.com`) | | `sub.example.com` | Main cert | Wildcard match (`*.example.com`) | | `api.other.com` | Extra cert 1 | Wildcard match (`*.other.com`) | | `service.internal.local` | Extra cert 2 | Wildcard match (`*.internal.local`) | | `db.services.internal` | Extra cert 3 | Wildcard match (`*.services.internal`) | | `unknown.com` | Main cert (fallback) | No match | **Selection Precedence:** 1. Exact match > wildcard match 2. Main cert > extra certificates when both match 3. Falls back to main cert if no match ### Other DNS providers [#other-dns-providers] Check [DNS-01 Providers](../getting-started/DNS-01-Providers) ### Troubleshooting [#troubleshooting] If you encounter issues: * Set `LEGO_DISABLE_CNAME_SUPPORT=1` if your domain has a CNAME record * Try different DNS resolvers via `autocert.resolvers` # GoDoxy: Domain Matching URL: /docs/godoxy/advanced-topics/Domain-Matching Source: https://raw.githubusercontent.com/yusing/godoxy-wiki/refs/heads/main/content/docs/godoxy/advanced-topics/Domain-Matching.mdx Configure domain matching for GoDoxy An alias can be: * a short alias like `app` * an FQDN (Fully Qualified Domain Name) alias like `app.example.com` * a leading-label wildcard alias like `*.example.com` ### Docker [#docker] By default, the container name is used as the short alias unless `proxy.aliases` is set. ### General [#general] #### Without `match_domains` [#without-match_domains] If `match_domains` is not set in `config.yml`, below will match route with short alias `app`: * `app.anydomain.com` * `app.*.anydomain.com` * ... Below will match route with FQDN alias `app.example.com`: * `app.example.com` * `app.example.com.*` Below will match route with wildcard alias `*.example.com`: * `foo.example.com` * `bar.example.com` And will **not** match: * `example.com` * `foo.bar.example.com` #### Using `match_domains` [#using-match_domains] Use this to restrict which base domains are valid. With `match_domains` in `config.yml`: ```yaml match_domains: - example.com - example.org ``` Then a short alias `app` can be accessed only at: * `app.example.com` * `app.example.org` And a route with FQDN alias `app.example.com` can be accessed at: * `app.example.com` And a wildcard alias `*.example.com` can be accessed only at one-label subdomains such as: * `foo.example.com` * `bar.example.com` but not: * `foo.bar.example.com` ## Priority [#priority] Host resolution order is: 1. existing exact/FQDN or short-alias domain match 2. wildcard alias fallback This keeps normal exact-route matching fast while still allowing wildcard aliases. ## Route file example [#route-file-example] Quote wildcard aliases in YAML route files: ```yaml "*.example.com": host: 10.0.0.20 port: 8080 ``` #### Use case example for `match_domains` [#use-case-example-for-match_domains] Given your main domain is `my.app`: * Add `my.app` to `autocert.domains` and `match_domains` in `config.yml`. ```yaml autocert: domains: - my.app match_domains: - my.app ``` * Use short aliases like `adguard` and `sonarr` when you want them accessible under your main domain. ```yaml services: adguard: # adguard.my.app ... labels: proxy.aliases: adguard sonarr: # sonarr.my.app ... labels: proxy.aliases: sonarr ``` * Use FQDN aliases like `adguard.other.app` and `sonarr.other.app` when you want them accessible under other domains. ```yaml # docker compose services: adguard: ... labels: proxy.aliases: adguard.other.app sonarr: ... labels: proxy.aliases: sonarr.other.app # config.yml autocert: domains: - my.app ``` # GoDoxy: Health monitoring URL: /docs/godoxy/advanced-topics/Health-Monitoring Source: https://raw.githubusercontent.com/yusing/godoxy-wiki/refs/heads/main/content/docs/godoxy/advanced-topics/Health-Monitoring.mdx Configure health monitoring for GoDoxy ## Behaviors [#behaviors] * Health monitoring is enabled by default for all services, including the excluded ones. * If docker healthcheck1 is enabled, it is the primary check. A valid Docker result is authoritative, so `use_get` and `path` are ignored and GoDoxy does not probe the upstream. * If a docker healthcheck is not configured or Docker cannot perform the check because of a client initialization, API, decoding, or timeout failure, the **GoDoxy** healthcheck is used as a fallback while Docker is retried on later intervals. * You can disable GoDoxy health probing by setting `healthcheck.disable: true` in the route file2 or in the docker labels3. To disable it for every route of a Docker container, set `proxy.exclude: healthcheck`4. Docker health monitoring remains enabled for Docker routes. ### References [#references] 1. Health check in [Docker compose](https://docs.docker.com/reference/compose-file/services/#healthcheck) and [Dockerfile](https://docs.docker.com/engine/reference/builder/#healthcheck), e.g. ```yaml healthcheck: test: ["CMD", "curl", "-f", "http://localhost"] interval: 1m30s timeout: 10s retries: 3 start_period: 40s start_interval: 5s ``` 2. Disable GoDoxy health probing in route file ```yaml app: host: 10.0.0.1 port: 8080 healthcheck: disable: true ``` 3. Docker labels ```yaml services: app: labels: proxy.#1.healthcheck.disable: true ``` 4. Every route of a Docker container ```yaml services: app: labels: proxy.exclude: healthcheck ``` Use `proxy.exclude: proxy,healthcheck` or `proxy.exclude: all` to exclude the routes from proxying as well. The boolean value `true` excludes only proxying for backward compatibility. ## Implementations [#implementations] | Healthcheck Type | Method | Healthy Condition | Unhealthy Condition | | ---------------- | ----------------------------------------------------------------------- | ------------------------------------------- | ----------------------------------- | | HTTP | Sends `HEAD` or `GET` request to specified `path` (default: `/`) | Server replies with a non-5xx HTTP response | Connection failure/error, 5xx reply | | TCP/UDP | Dials a TCP or UDP connection to upstream server | Connection is established | Connection cannot be established | | File Server | Checks if root directory exists and is accessible by GoDoxy (`os.Stat`) | Root directory exists and is accessible | Otherwise | ## Properties [#properties] | Property | Description | Default | Allowed Values / Syntax | | -------- | --------------------------------------------------- | ------------------------- | --------------------------- | | disable | Disable GoDoxy probing (Docker health remains) | false | boolean | | path | Relative path **(HTTP only)** | empty | empty or URI like `/health` | | use\_get | Use GET method instead of HEAD **(HTTP only)** | false | boolean | | interval | Health check interval | 5s | duration | | timeout | Health check timeout | 5s | duration | | retries | Health check retries before notifying unhealthy | 15s divided by `interval` | integer | # GoDoxy: Idle Sleep URL: /docs/godoxy/advanced-topics/Idle-Sleep Source: https://raw.githubusercontent.com/yusing/godoxy-wiki/refs/heads/main/content/docs/godoxy/advanced-topics/Idle-Sleep.mdx Automatically sleep idle containers and wake them on traffic. Saves resources for infrequently used services ## Overview [#overview] GoDoxy puts containers to sleep after a period of inactivity and wakes them automatically when traffic arrives. This feature is ideal for development environments, staging servers, or services with unpredictable usage patterns. ### Supported Platforms [#supported-platforms] * Docker * Proxmox LXCs ### Supported Protocols [#supported-protocols] | Protocol | Description | | -------- | ----------------------- | | HTTP | Standard web traffic | | TCP | Generic TCP connections | | UDP | Generic UDP connections | ## Quick Reference [#quick-reference] | Property | Description | Default | Required | | ----------------- | ----------------------- | -------------- | -------- | | `idle_timeout` | Inactivity before sleep | Disabled | Yes | | `wake_timeout` | Wait time for wake | `30s` | No | | `stop_method` | Container stop method | `stop` | No | | `stop_timeout` | Stop command timeout | `10s` | No | | `stop_signal` | Signal for stop/kill | Docker default | No | | `start_endpoint` | Wake trigger endpoint | Any | No | | `no_loading_page` | Disable wake page | `false` | No | ## How It Works [#how-it-works] ### Sleep Behavior [#sleep-behavior] 1. Container receives no traffic for `idle_timeout` duration 2. GoDoxy executes `stop_method` on the container 3. Container dependencies are stopped in order 4. Traffic to the route triggers wake-up sequence ### Wake Behavior [#wake-behavior] 1. Request arrives for sleeping route 2. For an HTML navigation, GoDoxy returns the loading page immediately and starts one background wake operation 3. Dependencies start first (if configured) 4. Main container starts 5. The loading page receives startup progress over server-sent events 6. GoDoxy waits up to `wake_timeout` for readiness, then the loading page refreshes into the active service Non-HTML requests, and all requests when `no_loading_page` is enabled, wait for wake-up so GoDoxy can proxy the original request after the service becomes ready. Canceling an HTML navigation after its loading page has been returned does not cancel the shared wake operation. Idle start, ready, sleep, pause, and error transitions appear in the WebUI Live Activity feed. ## Dependency Management [#dependency-management] Containers with `depends_on` are managed as a group. This ensures related services start and stop together. Do not set `idle_timeout` on dependency containers. ### Condition Types [#condition-types] Control when dependencies are considered ready: | Condition | Description | | ----------------- | ----------------------------- | | `service_started` | Dependency process started | | `service_healthy` | Dependency healthcheck passes | ### Docker Compose Example [#docker-compose-example] ```yaml # Without conditions (implies service_started) depends_on: - redis - postgres # With explicit conditions depends_on: redis: condition: service_healthy postgres: condition: service_started ``` ### Label Syntax [#label-syntax] ```yaml labels: proxy.depends_on: | - redis:service_healthy - postgres:service_started ``` ## Docker Configuration [#docker-configuration] Enable idle sleep by adding labels to your container: ```yaml services: app: image: nginx:latest container_name: my-app labels: # Required proxy.idle_timeout: 1h30s # Optional proxy.wake_timeout: 30s proxy.stop_method: stop proxy.stop_timeout: 10s proxy.stop_signal: SIGINT proxy.start_endpoint: /start proxy.no_loading_page: true depends_on: redis: condition: service_healthy postgres: condition: service_healthy redis: image: redis:alpine healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 5 postgres: image: postgres:16 # Healthcheck configured via GoDoxy health monitoring ``` ## Proxmox LXC Configuration [#proxmox-lxc-configuration] See [Proxmox Integration](/docs/godoxy/getting-started/Proxmox) for detailed configuration options. ## Property Reference [#property-reference] ### Core Properties [#core-properties] | Property | Description | Default | Accepted Values | | ---------------- | ------------------------ | -------- | ------------------------- | | `idle_timeout` | Inactivity before sleep | Disabled | Duration (`1h30s`, `15m`) | | `wake_timeout` | Wait for wake completion | `30s` | Duration | | `start_endpoint` | Wake trigger path | Any | Relative URI | ### Stop Control [#stop-control] | Property | Description | Default | Accepted Values | | -------------- | ---------------------- | -------------- | ------------------------------------------------------------------------ | | `stop_method` | Container stop action | `stop` | `stop`, `pause`, `kill` | | `stop_timeout` | Stop command wait time | `10s` | Duration | | `stop_signal` | Signal for stop/kill | Docker default | `SIGINT`, `SIGTERM`, `SIGQUIT`, `SIGKILL` (with or without `SIG` prefix) | ### UI Options [#ui-options] | Property | Description | Default | Values | | ----------------- | ------------------------- | ------- | ------- | | `no_loading_page` | Disable wake loading page | `false` | boolean | ### Duration Format [#duration-format] Use standard duration notation: | Unit | Description | Example | | ------- | ----------- | ---------- | | `s` | Seconds | `30s` | | `m` | Minutes | `15m` | | `h` | Hours | `1h` | | `h m s` | Combined | `1h30m15s` | ## Examples [#examples] ### Development Environment [#development-environment] ```yaml services: dev-api: image: myapp/api:latest labels: proxy.idle_timeout: 30m proxy.wake_timeout: 60s proxy.stop_method: kill proxy.stop_timeout: 5s depends_on: dev-db: condition: service_healthy dev-db: image: postgres:16 healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s timeout: 3s ``` ### Staging with Specific Wake Endpoint [#staging-with-specific-wake-endpoint] ```yaml services: staging-app: image: myapp/staging:latest labels: proxy.idle_timeout: 2h proxy.start_endpoint: /api/wake proxy.no_loading_page: false ``` ## Best Practices [#best-practices] 1. **Use healthchecks** with `service_healthy` condition for reliable dependencies 2. **Set appropriate timeouts** based on your service startup time 3. **Test wake behavior** before deploying to production 4. **Monitor container logs** during initial setup 5. **Use consistent stop signals** that match your application # GoDoxy: Inbound mTLS URL: /docs/godoxy/advanced-topics/Inbound-MTLS Source: https://raw.githubusercontent.com/yusing/godoxy-wiki/refs/heads/main/content/docs/godoxy/advanced-topics/Inbound-MTLS.mdx Require client certificates on HTTPS using named trust profiles ## What it is [#what-it-is] Inbound mTLS makes the proxy verify **client certificates** on HTTPS. You define **trust profiles** (which CAs may issue those clients), then either require a profile for **all** HTTPS on the entrypoint or only for **specific routes** (selected by TLS SNI). Server certificates are still configured separately via [`autocert`](./Certificates). Inbound mTLS only controls **client** trust. ## Trust profiles [#trust-profiles] At the **root** of your config: ```yaml inbound_mtls_profiles: corp: use_system_cas: false # optional: include OS trust store ca_files: - /path/to/ca-chain.pem # PEM CA certs; at least one of use_system_cas or ca_files ``` Each profile must have at least one of `use_system_cas` or `ca_files`. Paths must be readable and contain valid PEM CAs. ## Entrypoint-wide (all HTTPS) [#entrypoint-wide-all-https] ```yaml entrypoint: inbound_mtls_profile: corp ``` Every HTTPS listener on that entrypoint then requires a client cert that chains to the `corp` pool. You **cannot** set `inbound_mtls_profile` on individual routes when this is set. ## Per-route (no entrypoint profile) [#per-route-no-entrypoint-profile] Omit `entrypoint.inbound_mtls_profile`. On **HTTP-based** routes (`http`, `https`, `h2c`, `fileserver`), set `inbound_mtls_profile` to a name from `inbound_mtls_profiles`. For those routes, the TLS handshake requires a client cert when **SNI** matches that route. Hostnames without a profile stay optional client certs. The HTTP **`Host`** and TLS **SNI** must resolve to the **same** route for secured routes; otherwise the server responds with **421 Misdirected Request**. See [Configuring Routes](../getting-started/Configuring-Routes) for how route files and Docker labels are structured. ### Route file [#route-file] ```yaml # config.yml inbound_mtls_profiles: corp: ca_files: - /path/to/client-ca.pem providers: include: - config/api.yml ``` ```yaml # config/api.yml api.example.com: host: 10.0.9.98 port: 80 inbound_mtls_profile: corp ``` ### Docker Compose [#docker-compose] ```yaml services: api: image: myapp labels: proxy.aliases: api.example.com proxy.#1.port: 80 proxy.#1.inbound_mtls_profile: corp ``` ## Practical notes [#practical-notes] * Wrong or missing client cert: **TLS handshake fails** (no HTTP response). * `use_system_cas: true` alone can trust very broad issuers; for internal mTLS, prefer a dedicated `ca_files` chain. * Go’s TLS stack does not check CRL/OCSP for client certs here. * After changing CA files, **reload or restart** so pools are rebuilt. # GoDoxy: Load Balancing URL: /docs/godoxy/advanced-topics/Load-Balancing Source: https://raw.githubusercontent.com/yusing/godoxy-wiki/refs/heads/main/content/docs/godoxy/advanced-topics/Load-Balancing.mdx Distribute traffic across multiple backend instances for high availability and scalability ## Overview [#overview] Load balancing groups routes with the same `load_balance.link` value into a single load balancer. Requests are distributed according to the configured mode, with optional sticky sessions for session persistence. Load balancing is currently supported for HTTP routes only. TCP/UDP load balancing is planned for future releases. ## Quick Reference [#quick-reference] | Mode | Description | Use Case | | ------------- | ---------------------------------- | ---------------------- | | `round_robin` | Distribute requests evenly | General purpose | | `least_conn` | Route to fewest active connections | Long-lived connections | | `ip_hash` | Consistent routing by client IP | Session persistence | ## Property Reference [#property-reference] | Property | Description | Default | Values | | ---------------- | ----------------------------------------------------------------------------- | ------------- | -------------------------------------- | | `link` | Load balancer group identifier | Required | string | | `mode` | Distribution algorithm | `round_robin` | `round_robin`, `least_conn`, `ip_hash` | | `sticky` | Enable [Sticky session](https://traefik.io/glossary/what-are-sticky-sessions) | `false` | boolean | | `sticky_max_age` | Sticky session lifetime | `1h` | duration | ## Load Balancing Modes [#load-balancing-modes] ### Round Robin [#round-robin] Distributes requests evenly across all backends in sequence. Suitable for homogeneous backends with similar performance. ```yaml load_balance: link: api mode: round_robin ``` ### Least Connections [#least-connections] Routes new requests to the backend with the fewest active connections. Ideal for backends with varying response times or heterogeneous hardware. ```yaml load_balance: link: api mode: least_conn ``` ### IP Hash [#ip-hash] Uses the client IP address to consistently route requests to the same backend. Useful when session data is stored locally on backends. ```yaml load_balance: link: api mode: ip_hash ``` For IP hash mode, ensure the [RealIP middleware](middlewares/ip-resolution/Real-IP) is configured if GoDoxy operates behind a proxy. ## Sticky Sessions [#sticky-sessions] Enable sticky sessions to route the same client to the same backend for the session duration. ```yaml load_balance: link: api mode: round_robin sticky: true sticky_max_age: 2h ``` | Parameter | Description | | ---------------- | ----------------------------------------- | | `sticky` | Enable cookie-based session stickiness | | `sticky_max_age` | Session cookie lifetime (default: 1 hour) | ## Examples [#examples] ### Docker Compose with Multiple Replicas [#docker-compose-with-multiple-replicas] ```yaml services: whoami: image: traefik/whoami deploy: replicas: 10 labels: proxy.*.load_balance: | link: whoami mode: round_robin ports: - "80" restart: unless-stopped ``` ### Multi-Node Deployment [#multi-node-deployment] Configure the same `load_balance.link` across all nodes for distributed load balancing: ```yaml services: whoami: image: traefik/whoami container_name: whoami labels: proxy.whoami.load_balance: | link: whoami mode: round_robin sticky: true ports: - "80" restart: unless-stopped ``` ### Route File Configuration [#route-file-configuration] ```yaml # config/loadbalancer.yml whoami-1: host: 10.0.2.1 port: 80 load_balance: link: whoami mode: round_robin whoami-2: host: 10.0.2.2 port: 80 load_balance: link: whoami mode: round_robin whoami-3: host: 10.0.2.3 port: 80 load_balance: link: whoami mode: round_robin ``` ## Best Practices [#best-practices] 1. **Health Checks**: Ensure backends have health checks configured to remove unhealthy instances from rotation 2. **Homogeneous Backends**: Use round robin for backends with similar capacity 3. **Session Requirements**: Use ip\_hash or sticky sessions only when necessary 4. **Sticky Age**: Set appropriate session lifetimes based on your application needs # GoDoxy: Notifications URL: /docs/godoxy/advanced-topics/Notifications Source: https://raw.githubusercontent.com/yusing/godoxy-wiki/refs/heads/main/content/docs/godoxy/advanced-topics/Notifications.mdx Receive alerts about service health, certificate renewals, and configuration errors ## Overview [#overview] GoDoxy sends notifications to configured providers when significant events occur. Configure one or more providers to receive alerts. ### Notification Events [#notification-events] | Event | Description | | ------------------- | --------------------------------------------- | | Service Health | Route health status changes | | Certificate Renewal | Success or failure of TLS certificate renewal | | Configuration | Errors during config file reload | ## Configuration Methods [#configuration-methods] | Method | Description | | ---------- | --------------------------------- | | WebUI | Configure via WebUI config editor | | config.yml | Edit `config/config.yml` directly | ## Providers [#providers] ### Gotify [#gotify] A simple self-hosted notification service. ```yaml notification: - name: gotify provider: gotify url: https://gotify.my.site token: abcdef.12345 ``` | Field | Description | Required | | ---------- | --------------------- | -------------- | | `name` | Provider identifier | Yes | | `provider` | Provider type | Yes (`gotify`) | | `url` | Gotify server address | Yes | | `token` | Authentication token | Yes | ### Ntfy [#ntfy] A flexible notification service with topic-based messaging. ```yaml notification: - name: ntfy provider: ntfy url: https://ntfy.domain.com topic: some-topic # token: xxx # Optional: if access tokens are configured ``` | Field | Description | Required | | ---------- | ----------------------------------- | ------------ | | `name` | Provider identifier | Yes | | `provider` | Provider type | Yes (`ntfy`) | | `url` | Ntfy server address (without topic) | Yes | | `topic` | Target topic for messages | Yes | | `token` | Access token (if required) | No | ### Discord Webhook [#discord-webhook] Send notifications to a Discord channel. ```yaml notification: - name: discord provider: webhook url: https://discord.com/api/webhooks/... template: discord ``` | Field | Description | Required | | ---------- | ------------------- | --------------------------------- | | `name` | Provider identifier | Yes | | `provider` | Provider type | Yes (`webhook`) | | `url` | Discord webhook URL | Yes | | `template` | Message template | No (`discord` for Discord format) | ### Custom Webhooks [#custom-webhooks] Integrate with Slack, Matrix, or any service supporting webhooks. ```yaml notification: - name: slack provider: webhook url: https://hooks.slack.com/services/... payload: | { "text": "$message", "username": "GoDoxy" } mime_type: application/json ``` | Field | Description | Required | | ------------ | -------------------------- | --------------- | | `name` | Provider identifier | Yes | | `provider` | Provider type | Yes (`webhook`) | | `url` | Webhook URL | Yes | | `payload` | JSON body template | See note | | `method` | HTTP method | No (`POST`) | | `mime_type` | Content type | No | | `token` | Bearer token (if required) | No | | `color_mode` | Color format | No (`hex`) | `payload` is required unless using a template like `discord`. ## Property Reference [#property-reference] ### Common Fields [#common-fields] | Field | Description | Default | Values | | ---------- | ------------------- | ---------- | --------------------------- | | `name` | Provider identifier | Required | string | | `provider` | Provider type | Required | `gotify`, `ntfy`, `webhook` | | `url` | Server/webhook URL | Required | full URL | | `format` | Message format | `markdown` | `markdown`, `plain` | ### Webhook Fields [#webhook-fields] | Field | Description | Default | Values | | ------------ | --------------------- | ------- | -------------------- | | `template` | Predefined template | none | `discord` | | `payload` | Custom JSON payload | none | JSON string | | `method` | HTTP request method | `POST` | `GET`, `POST`, `PUT` | | `mime_type` | Content-Type header | auto | MIME type | | `token` | Bearer authentication | none | string | | `color_mode` | Color value format | `hex` | `hex`, `dec` | ## Payload Variables [#payload-variables] Use these variables in custom webhook payloads: | Variable | Description | Format | | ---------- | ----------------------- | -------------------------- | | `$title` | Message title | JSON-escaped string | | `$message` | Message body (Markdown) | JSON-escaped string | | `$fields` | Structured message data | JSON object | | `$color` | Message color | hex (`#ff0000`) or decimal | ### Example: Discord Embed [#example-discord-embed] ```json { "embeds": [ { "title": $title, "fields": $fields, "color": "$color" } ] } ``` # GoDoxy: v0.29.0 URL: /docs/godoxy/migration/v0.29.0 Source: https://raw.githubusercontent.com/yusing/godoxy-wiki/refs/heads/main/content/docs/godoxy/migration/v0.29.0.mdx Breaking changes and Docker upgrades from v0.28.1 ## Docker Compose and images [#docker-compose-and-images] **Drop the separate WebUI container.** The example stack no longer includes a `frontend` service (`ghcr.io/yusing/godoxy-frontend`). The main `ghcr.io/yusing/godoxy` image serves the dashboard from embedded assets. 1. Remove the `frontend` service and delete the old `godoxy-frontend` container. 2. List dashboard hostnames under **`webui.aliases`** in `config/config.yml` (see `config.example.yml`). If you only used **`GODOXY_FRONTEND_ALIASES`** before, that still applies when `webui.aliases` is empty, but config is preferred. 3. Drop Docker labels that existed only on the frontend (`proxy.aliases` for the UI). One definition per alias. Duplicates log warnings. **Socket proxy env rename:** **`SOCKET_PROXY_LISTEN_ADDR`** is now **`LISTEN_ADDR`** everywhere Compose uses it (published port and `DOCKER_HOST=tcp://…` on `app`). Example (trimmed from `compose.example.yml`; there is no `frontend` service): ```yaml services: socket-proxy: image: ghcr.io/yusing/socket-proxy:latest ports: - ${LISTEN_ADDR:-127.0.0.1:2375}:2375 volumes: - ${DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock # … full env and options in compose.example.yml app: image: ghcr.io/yusing/godoxy:${TAG:-latest} network_mode: host # do not change this env_file: .env environment: - DOCKER_HOST=tcp://${LISTEN_ADDR:-127.0.0.1:2375} # WebUI: set webui.aliases in config/config.yml (embedded assets in this image) volumes: - ./config:/app/config # … logs, error_pages, data, certs: see compose.example.yml # no more frontend service ``` **TCP on `:443`:** Optional **`GODOXY_SNI_ROUTING_FOR_TCP_ROUTES`** (default `true`). See [below](#tcp-on-the-same-port-as-https) before setting it to `false`. Building from the repo **`Dockerfile`**: default target is the main image (embedded UI). Targets **`agent`** and **`socket-proxy`** are the smaller service images. *** ## Compat images and Docker API client [#compat-images-and-docker-api-client] GitHub Actions no longer publishes **`compat`** tags from the **`compat`** branch. If you used **`ghcr.io/yusing/godoxy:compat`** or **`ghcr.io/yusing/godoxy-agent:compat`** (or the same names on another registry), move to **`latest`**, **`nightly`**, or a **`v…`** tag. Those compat builds also tracked **older Docker Engine API** expectations. The standard images again pin an **older `github.com/docker/docker` client** module, so you should not need a separate image just for daemon API mismatches. JSON uses **`encoding/json/v2`**. The **`USE_SONIC_JSON`** toggle is gone and is ignored if still set. # GoDoxy: v0.30.3 URL: /docs/godoxy/migration/v0.30.3 Source: https://raw.githubusercontent.com/yusing/godoxy-wiki/refs/heads/main/content/docs/godoxy/migration/v0.30.3.mdx OIDC middleware standalone issuers, scopes list, and WebUI display name ## OIDC middleware [#oidc-middleware] Per-route OIDC can now set its own **`issuer_url`**, credentials, allow lists, and **`scopes`**. Omitted fields still inherit from `GODOXY_OIDC_*`. Global OIDC does not need to be enabled for the middleware to run. **`scopes` is a list.** A comma-separated string still parses, but `scope` is an unknown field and is rejected. ```yaml # before: `scope` is rejected scope: openid, profile, email, groups # after scopes: - openid - profile - email - groups ``` Standalone Authentik-style issuer: ```yaml myapp: middlewares: oidc: issuer_url: https://auth.example.com/application/o/app-b/ client_id: app-b-client-id client_secret: app-b-client-secret allowed_users: - user1 scopes: - openid - profile - email - groups ``` See [OIDC Middleware](../advanced-topics/middlewares/authentication-and-security/OIDC). ## WebUI display name [#webui-display-name] Set **`webui.display_name`** in `config.yml` to change the name shown in the server list and headings. Empty values become `GoDoxy`. ```yaml webui: display_name: Home lab aliases: - godoxy ``` See [Configuring GoDoxy](../getting-started/Configuring-GoDoxy#built-in-webui-route). ## Socket-proxy image build [#socket-proxy-image-build] Custom socket-proxy images now build from the shared repository `Dockerfile` `socket-proxy` target. `socket-proxy.Dockerfile` was removed. ```shell docker build --target socket-proxy -t ghcr.io/yusing/socket-proxy:latest . ``` # GoDoxy: CSS Injection URL: /docs/godoxy/customization/CSS-Injection Source: https://raw.githubusercontent.com/yusing/godoxy-wiki/refs/heads/main/content/docs/godoxy/customization/CSS-Injection.mdx Inject CSS into the HTML of your application ## Overview [#overview] CSS Injection is simple with help of the [`themed`](../advanced-topics/middlewares/content-modification/Themed) middleware. ## Examples [#examples] Using themes from [Theme Park](https://docs.theme-park.dev) ```yaml # docker labels proxy.app.middlewares.themed.css: https://theme-park.dev/css/base//.css # route file app: middlewares: themed: css: https://theme-park.dev/css/base//.css ``` Check out the [`themed` middleware](../advanced-topics/middlewares/content-modification/Themed) middleware for more options. # GoDoxy: Custom Error Pages URL: /docs/godoxy/customization/Custom-Error-Pages Source: https://raw.githubusercontent.com/yusing/godoxy-wiki/refs/heads/main/content/docs/godoxy/customization/Custom-Error-Pages.mdx Configure custom HTML error pages to provide branded, user-friendly responses when errors occur ## Overview [#overview] GoDoxy supports custom error pages for HTTP status codes. When an error occurs, GoDoxy will serve your custom page instead of generic browser error pages. This feature is useful for: * Maintaining consistent branding across error responses * Providing helpful guidance to users when errors occur * Supporting multiple languages or themes * Integrating with your application's design system ## Quick Start [#quick-start] 1. Create an `error_pages` directory in your project 2. Add HTML files named after status codes (e.g., `404.html`, `500.html`) 3. Mount the directory to `/app/error_pages` in the GoDoxy container ## Directory Structure [#directory-structure] All error page files must reside in the root of the `error_pages` directory. Subdirectories are not supported. ```plaintext project/ ├── error_pages/ │ ├── 400.html │ ├── 403.html │ ├── 404.html │ ├── 500.html │ ├── 502.html │ ├── 503.html │ └── style.css └── docker-compose.yml ``` ## Configuration [#configuration] ### Docker Mount [#docker-mount] Mount your error pages directory to the GoDoxy container: ```yaml services: godoxy: image: godoxy:latest container_name: godoxy volumes: - ./error_pages:/app/error_pages - ./config.yaml:/app/config.yaml:ro ports: - "80:80" - "443:443" ``` ### File Naming Convention [#file-naming-convention] Error pages must be named using the HTTP status code: | File | HTTP Status | | ---------- | --------------------- | | `400.html` | Bad Request | | `403.html` | Forbidden | | `404.html` | Not Found | | `500.html` | Internal Server Error | | `502.html` | Bad Gateway | | `503.html` | Service Unavailable | ## Asset References [#asset-references] Use the `/$gperrorpage/` prefix for all relative asset paths. This ensures assets load correctly regardless of the original request URL. ```html Page Not Found

404

The page you're looking for doesn't exist.

Go Home
``` ### Supported Asset Types [#supported-asset-types] * CSS files (``) * JavaScript files (`