From 34daeb85f930927c981965270b436877a3a9f82a Mon Sep 17 00:00:00 2001
From: EyJunge1 <149941075+EyJunge1@users.noreply.github.com>
Date: Fri, 28 Aug 2026 19:59:29 +0200
Subject: [PATCH 1/2] www/nginx: make upstream client-address headers
configurable
Allow X-Forwarded-For, Forwarded, and provider client-IP headers to be
sanitized at the upstream so nginx can act as a strict trust boundary.
---
www/nginx/Makefile | 2 +-
www/nginx/pkg-descr | 1 +
.../OPNsense/Nginx/forms/upstream.xml | 23 ++++++++++++++++++
.../mvc/app/models/OPNsense/Nginx/Nginx.xml | 24 ++++++++++++++++++-
.../templates/OPNsense/Nginx/http.conf | 10 ++++++++
.../templates/OPNsense/Nginx/location.conf | 15 ++++++++++++
6 files changed, 73 insertions(+), 2 deletions(-)
diff --git a/www/nginx/Makefile b/www/nginx/Makefile
index 27170b4207..4541c0cf0c 100644
--- a/www/nginx/Makefile
+++ b/www/nginx/Makefile
@@ -1,6 +1,6 @@
PLUGIN_NAME= nginx
PLUGIN_VERSION= 1.36
-PLUGIN_REVISION= 5
+PLUGIN_REVISION= 6
PLUGIN_COMMENT= Nginx HTTP server and reverse proxy
PLUGIN_DEPENDS= nginx
PLUGIN_MAINTAINER= franz.fabian.94@gmail.com
diff --git a/www/nginx/pkg-descr b/www/nginx/pkg-descr
index ce13d67f6a..5548d0a692 100644
--- a/www/nginx/pkg-descr
+++ b/www/nginx/pkg-descr
@@ -12,6 +12,7 @@ Plugin Changelog
* Add optional HTTP/3 support with dynamic Alt-Svc (contributed by Jan Chlouba)
* Fix HTTP/3 reuseport duplicates (contributed by Jan Chlouba)
+* Make upstream client-address headers configurable (X-Forwarded-For, Forwarded, CF-/True-Client-IP)
1.35
diff --git a/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/upstream.xml b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/upstream.xml
index 3f9bd6af5b..904f6d17a4 100644
--- a/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/upstream.xml
+++ b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/upstream.xml
@@ -57,6 +57,29 @@
truecheckbox
+
+ upstream.xff_header_mode
+
+
+ dropdown
+ true
+ Controls the X-Forwarded-For header sent to the upstream. Append (default) keeps the existing chain via $proxy_add_x_forwarded_for. Replace sends only the validated client address ($remote_addr after trusted-proxy / real_ip processing). Drop suppresses the header. Use Replace with Forwarded Replace and Suppress CF-/True-Client-IP when nginx should act as a strict client-IP trust boundary.
+
+
+ upstream.forwarded_header_mode
+
+
+ dropdown
+ true
+ Controls the RFC 7239 Forwarded header sent to the upstream. Preserve (default) leaves any client-supplied Forwarded header unchanged. Replace overwrites it with a sanitized value derived from $remote_addr and $scheme (IPv6 is quoted per RFC 7239). Drop suppresses the header. Without Replace or Drop, backends that prefer Forwarded over X-Real-IP may accept a spoofed client address.
+
+
+ upstream.suppress_client_headers
+
+ Clear CF-Connecting-IP and True-Client-IP before proxying so provider-specific client identity headers cannot bypass the validated $remote_addr trust boundary.
+ true
+ checkbox
+ upstream.tls_enable
diff --git a/www/nginx/src/opnsense/mvc/app/models/OPNsense/Nginx/Nginx.xml b/www/nginx/src/opnsense/mvc/app/models/OPNsense/Nginx/Nginx.xml
index f740f7297d..8ee16e492d 100644
--- a/www/nginx/src/opnsense/mvc/app/models/OPNsense/Nginx/Nginx.xml
+++ b/www/nginx/src/opnsense/mvc/app/models/OPNsense/Nginx/Nginx.xml
@@ -1,6 +1,6 @@
//OPNsense/Nginx
- 1.35.2
+ 1.35.3nginx web server, reverse proxy and waf
@@ -131,6 +131,28 @@
0Y
+
+
+ Append
+ Replace
+ Drop
+
+ Y
+ append
+
+
+
+ Preserve
+ Replace
+ Drop
+
+ Y
+ preserve
+
+
+ 0
+ Y
+ 0Y
diff --git a/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/http.conf b/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/http.conf
index 64c2b305d1..b426a6e507 100644
--- a/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/http.conf
+++ b/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/http.conf
@@ -72,6 +72,16 @@ map $ssl_server_name $upstream_sni_name {
'' $host;
}
+# Maps used in location.conf for RFC 7239 Forwarded header
+map $remote_addr $forwarded_for {
+ ~^[0-9.]+$ "for=$remote_addr";
+ ~^[0-9A-Fa-f:.]+$ "for=\"[$remote_addr]\"";
+ default "for=unknown";
+}
+map $scheme $proxy_forwarded {
+ default "$forwarded_for;proto=$scheme";
+}
+
include http_post/*.conf;
# TODO add when core is ready for allowing nginx to serve the web interface
diff --git a/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/location.conf b/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/location.conf
index 71529378b3..9a98f73d6e 100644
--- a/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/location.conf
+++ b/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/location.conf
@@ -176,10 +176,25 @@ location {{ location.matchtype }} {{ location.urlpattern }} {
proxy_set_header Early-Data $ssl_early_data;
{% endif %}
proxy_set_header X-Real-IP $remote_addr;
+{% if upstream.xff_header_mode is defined and upstream.xff_header_mode == 'replace' %}
+ proxy_set_header X-Forwarded-For $remote_addr;
+{% elif upstream.xff_header_mode is defined and upstream.xff_header_mode == 'drop' %}
+ proxy_set_header X-Forwarded-For "";
+{% else %}
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+{% endif %}
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Host {% if upstream.x_forwarded_host_verbatim is defined and upstream.x_forwarded_host_verbatim == '1'%}$http_host{% else %}$host{% endif %};
+{% if upstream.forwarded_header_mode is defined and upstream.forwarded_header_mode == 'replace' %}
+ proxy_set_header Forwarded $proxy_forwarded;
+{% elif upstream.forwarded_header_mode is defined and upstream.forwarded_header_mode == 'drop' %}
+ proxy_set_header Forwarded "";
+{% endif %}
+{% if upstream.suppress_client_headers is defined and upstream.suppress_client_headers == '1' %}
+ proxy_set_header CF-Connecting-IP "";
+ proxy_set_header True-Client-IP "";
+{% endif %}
proxy_set_header X-TLS-Client-Intercepted $tls_intercepted;
{% if location.proxy_read_timeout is defined and location.proxy_read_timeout != '' %}
proxy_read_timeout {{ location.proxy_read_timeout }}s;
From 324d130843944dea5317ab6429b406e89325c700 Mon Sep 17 00:00:00 2001
From: EyJunge1 <149941075+EyJunge1@users.noreply.github.com>
Date: Fri, 28 Aug 2026 20:01:36 +0200
Subject: [PATCH 2/2] www/nginx: tighten client-address header help and maps
Shorten upstream help text to match existing style and emit the
Forwarded RFC 7239 maps only when an upstream uses Replace.
---
.../mvc/app/controllers/OPNsense/Nginx/forms/upstream.xml | 6 +++---
.../opnsense/service/templates/OPNsense/Nginx/http.conf | 8 ++++++++
2 files changed, 11 insertions(+), 3 deletions(-)
diff --git a/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/upstream.xml b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/upstream.xml
index 904f6d17a4..f08bd6690b 100644
--- a/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/upstream.xml
+++ b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/upstream.xml
@@ -63,7 +63,7 @@
dropdowntrue
- Controls the X-Forwarded-For header sent to the upstream. Append (default) keeps the existing chain via $proxy_add_x_forwarded_for. Replace sends only the validated client address ($remote_addr after trusted-proxy / real_ip processing). Drop suppresses the header. Use Replace with Forwarded Replace and Suppress CF-/True-Client-IP when nginx should act as a strict client-IP trust boundary.
+ Append (default) keeps the X-Forwarded-For chain via $proxy_add_x_forwarded_for. Replace sends only the validated client address ($remote_addr). Drop suppresses the header.upstream.forwarded_header_mode
@@ -71,12 +71,12 @@
dropdowntrue
- Controls the RFC 7239 Forwarded header sent to the upstream. Preserve (default) leaves any client-supplied Forwarded header unchanged. Replace overwrites it with a sanitized value derived from $remote_addr and $scheme (IPv6 is quoted per RFC 7239). Drop suppresses the header. Without Replace or Drop, backends that prefer Forwarded over X-Real-IP may accept a spoofed client address.
+ Preserve (default) leaves a client-supplied Forwarded header unchanged. Replace sets a sanitized RFC 7239 value from $remote_addr and $scheme. Drop suppresses the header. Backends that prefer Forwarded may otherwise accept a spoofed address.upstream.suppress_client_headers
- Clear CF-Connecting-IP and True-Client-IP before proxying so provider-specific client identity headers cannot bypass the validated $remote_addr trust boundary.
+ Clear CF-Connecting-IP and True-Client-IP before proxying so they cannot bypass the validated client address.truecheckbox
diff --git a/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/http.conf b/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/http.conf
index b426a6e507..68ee37d211 100644
--- a/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/http.conf
+++ b/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/http.conf
@@ -72,6 +72,13 @@ map $ssl_server_name $upstream_sni_name {
'' $host;
}
+{% set need_forwarded_map = [] %}
+{% for upstream in helpers.toList('OPNsense.Nginx.upstream') %}
+{% if upstream.forwarded_header_mode is defined and upstream.forwarded_header_mode == 'replace' %}
+{% do need_forwarded_map.append(1) %}
+{% endif %}
+{% endfor %}
+{% if need_forwarded_map %}
# Maps used in location.conf for RFC 7239 Forwarded header
map $remote_addr $forwarded_for {
~^[0-9.]+$ "for=$remote_addr";
@@ -81,6 +88,7 @@ map $remote_addr $forwarded_for {
map $scheme $proxy_forwarded {
default "$forwarded_for;proto=$scheme";
}
+{% endif %}
include http_post/*.conf;