From 963daf89088a2fb8d6293445d6f2a3315a6200bf Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Mon, 18 May 2026 09:22:52 +0000 Subject: [PATCH] docs: document trusted_proxies config option Cover the option in config-example.yaml, the reverse-proxy integration guide, and the 0.29.0 CHANGELOG. --- CHANGELOG.md | 1 + config-example.yaml | 7 +++++++ docs/ref/integration/reverse-proxy.md | 24 ++++++++++++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5620f291..1ac906fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -307,6 +307,7 @@ connected" routers that maintain their control session but cannot route packets. - Tagged nodes (registered with tagged pre-auth keys) are exempt from default expiry - `oidc.expiry` has been removed; use `node.expiry` instead (applies to all registration methods including OIDC) - `ephemeral_node_inactivity_timeout` is deprecated in favour of `node.ephemeral.inactivity_timeout` +- Add `trusted_proxies` to gate `True-Client-IP` / `X-Real-IP` / `X-Forwarded-For` (previously honoured from any client) #### Debug diff --git a/config-example.yaml b/config-example.yaml index b7870d52..9b29a0a6 100644 --- a/config-example.yaml +++ b/config-example.yaml @@ -39,6 +39,13 @@ grpc_listen_addr: 127.0.0.1:50443 # are doing. grpc_allow_insecure: false +# CIDR(s) of reverse proxies (e.g. 127.0.0.1/32) whose +# True-Client-IP, X-Real-IP and X-Forwarded-For headers should +# be honoured. Empty (default) ignores those headers; setting +# this without a proxy in front lets clients spoof their logged +# source IP. +trusted_proxies: [] + # The Noise section includes specific configuration for the # TS2021 Noise protocol noise: diff --git a/docs/ref/integration/reverse-proxy.md b/docs/ref/integration/reverse-proxy.md index 3586171f..792328eb 100644 --- a/docs/ref/integration/reverse-proxy.md +++ b/docs/ref/integration/reverse-proxy.md @@ -31,6 +31,30 @@ tls_cert_path: "" tls_key_path: "" ``` +### Trusted proxies + +Headscale ignores `True-Client-IP`, `X-Real-IP` and `X-Forwarded-For` +unless the request's TCP peer matches `trusted_proxies`. Set this to +the CIDR(s) your reverse proxy connects from so the real client IP +appears in access logs: + +```yaml title="config.yaml" +trusted_proxies: + - 127.0.0.1/32 + - ::1/128 +``` + +The reverse proxy must also strip any client-supplied +`True-Client-IP` / `X-Real-IP` / `X-Forwarded-For` on inbound requests +and set its own values. nginx's `$proxy_add_x_forwarded_for` only +appends to whatever the client sent — pair it with +`proxy_set_header X-Real-IP $remote_addr;` and clear the inbound XFF +yourself if your nginx version does not do so. + +Leaving `trusted_proxies` empty when there is no proxy in front is +safe: the headers are dropped from every request and the access log +shows the directly-connecting TCP peer. + ## nginx The following example configuration can be used in your nginx setup, substituting values as necessary. `` should be the IP address and port where headscale is running. In most cases, this will be `http://localhost:8080`.