diff --git a/www/nginx/Makefile b/www/nginx/Makefile
index a952c7337..c16651e7d 100644
--- a/www/nginx/Makefile
+++ b/www/nginx/Makefile
@@ -1,6 +1,5 @@
PLUGIN_NAME= nginx
-PLUGIN_VERSION= 1.34
-PLUGIN_REVISION= 8
+PLUGIN_VERSION= 1.35
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 0f7cb8ac8..ce2b4b95b 100644
--- a/www/nginx/pkg-descr
+++ b/www/nginx/pkg-descr
@@ -10,6 +10,15 @@ WWW: https://nginx.org/
Plugin Changelog
================
+1.35
+
+* Global options sendfile directive typo fix
+* Add HTTP/2 option to GUI
+* Add multiple client authentication trusted CA support
+* Add proxy_intercept_errors directive support
+* Add Variables hashes size support
+* Add proxy_cache_valid directive support with response codes and multiple selection options
+
1.34
* Add the option to not log TLS handshakes
diff --git a/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/Api/SettingsController.php b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/Api/SettingsController.php
index 0458bc2aa..7b2043b90 100644
--- a/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/Api/SettingsController.php
+++ b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/Api/SettingsController.php
@@ -249,6 +249,32 @@ class SettingsController extends ApiMutableModelControllerBase
return $this->setBase('resolver', 'resolver', $uuid);
}
+ // proxy_cache_valid
+ public function searchproxyCacheValidAction()
+ {
+ return $this->searchBase('proxy_cache_valid', array('uuid', 'description', 'code', 'valid'));
+ }
+
+ public function getproxyCacheValidAction($uuid = null)
+ {
+ return $this->getBase('proxy_cache_valid', 'proxy_cache_valid', $uuid);
+ }
+
+ public function addproxyCacheValidAction()
+ {
+ return $this->addBase('proxy_cache_valid', 'proxy_cache_valid');
+ }
+
+ public function delproxyCacheValidAction($uuid)
+ {
+ return $this->delBase('proxy_cache_valid', $uuid);
+ }
+
+ public function setproxyCacheValidAction($uuid)
+ {
+ return $this->setBase('proxy_cache_valid', 'proxy_cache_valid', $uuid);
+ }
+
// http server
public function searchhttpserverAction()
{
diff --git a/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/IndexController.php b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/IndexController.php
index a30d036d1..01ad3330a 100644
--- a/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/IndexController.php
+++ b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/IndexController.php
@@ -64,6 +64,7 @@ class IndexController extends \OPNsense\Base\IndexController
$this->view->errorpage = $this->getForm("errorpage");
$this->view->tls_fingerprint = $this->getForm("tls_fingerprint");
$this->view->resolver = $this->getForm("resolver");
+ $this->view->proxy_cache_valid = $this->getForm("proxy_cache_valid");
$this->view->syslog_target = $this->getForm("syslog_target");
$nginx = new Nginx();
$this->view->show_naxsi_download_button =
diff --git a/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/httpserver.xml b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/httpserver.xml
index 33cf1ce52..21f8b4a28 100644
--- a/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/httpserver.xml
+++ b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/httpserver.xml
@@ -113,7 +113,8 @@
httpserver.ca
- dropdown
+ select_multiple
+ Trusted CA certificates
httpserver.verify_client
@@ -165,6 +166,13 @@
checkbox
If the request scheme is not HTTPS, redirect to use HTTPS for this server.
+
+ httpserver.http2
+
+ checkbox
+ Enable the HTTP/2 protocol.
+ true
+
httpserver.tls_protocols
@@ -320,4 +328,11 @@
select_multiple
Select custom error pages to display instead of the default builtin error pages. If at least one error page is selected here, all default error pages will be disabled.
+
+ httpserver.proxy_intercept_errors
+
+ checkbox
+ Intercept responses with codes greater than or equal to 300 and redirect to processing with custom error pages.
+ true
+
diff --git a/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/location.xml b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/location.xml
index d6ee67abb..23fdccff5 100644
--- a/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/location.xml
+++ b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/location.xml
@@ -98,11 +98,19 @@
location.cache_valid
-
+
text
true
Force caching of 200, 301 and 302 responses according to the request methods enabled for caching. Given in minutes; leave empty to rely on request/response headers from client and upstream.
+
+ location.proxy_cache_valid
+
+ select_multiple
+
+ true
+ Force caching of response codes specified at Response Code Caching page.
+
location.cache_background_update
@@ -335,4 +343,12 @@
select_multiple
Select custom error pages to display instead of the default builtin error pages. Selection will override error pages configured on HTTP server.
+
+ location.proxy_intercept_errors
+
+ dropdown
+
+ Intercept responses with codes greater than or equal to 300 and redirect to processing with custom error pages.
+ true
+
diff --git a/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/proxy_cache_valid.xml b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/proxy_cache_valid.xml
new file mode 100644
index 000000000..e37702e9d
--- /dev/null
+++ b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/proxy_cache_valid.xml
@@ -0,0 +1,22 @@
+
diff --git a/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/settings.xml b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/settings.xml
index 8ac01921c..3afcab4c6 100644
--- a/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/settings.xml
+++ b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/settings.xml
@@ -31,7 +31,7 @@
true
- nginx.http.enabled
+ nginx.http.sendfile
checkbox
Enable sendfile support (faster).
@@ -56,7 +56,7 @@
nginx.http.server_names_hash_bucket_size
-
+
text
true
@@ -66,6 +66,18 @@
text
true
+
+ nginx.http.variables_hash_bucket_size
+
+ text
+ true
+
+
+ nginx.http.variables_hash_max_size
+
+ text
+ true
+
nginx.http.bots_ua
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 fdc25d56d..423b56422 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.34
+ 1.35
nginx web server, reverse proxy and waf
@@ -56,6 +56,14 @@
N
1
+
+ N
+ 1
+
+
+ N
+ 1
+
N
@@ -352,6 +360,18 @@
N
+
+
+
+ OPNsense.Nginx.Nginx
+ proxy_cache_valid
+ description
+
+
+ Selected caching time settings not found
+ N
+ Y
+
Y
0
@@ -557,6 +577,15 @@
N
Y
+
+ Inherit
+ Y
+
+ Inherit
+ On
+ Off
+
+
@@ -827,6 +856,7 @@
ca
N
+ Y
Off
@@ -881,6 +911,10 @@
0
Y
+
+ 1
+ Y
+
Y
Y
@@ -1038,6 +1072,10 @@
N
Y
+
+ 0
+ Y
+
@@ -1955,6 +1993,24 @@
+
+
+ Y
+ /^[^" \t]+$/i
+
+
+ Y
+ any
+ Y
+ /(^\d{3}(,\d{3})*$)|(^any$)/
+ Please use three digit response code(s) or use "any" word.
+
+
+ 1
+ Y
+
+
+
Y
diff --git a/www/nginx/src/opnsense/mvc/app/views/OPNsense/Nginx/index.volt b/www/nginx/src/opnsense/mvc/app/views/OPNsense/Nginx/index.volt
index 5c715e748..f64990afb 100644
--- a/www/nginx/src/opnsense/mvc/app/views/OPNsense/Nginx/index.volt
+++ b/www/nginx/src/opnsense/mvc/app/views/OPNsense/Nginx/index.volt
@@ -193,6 +193,9 @@
{{ lang._('Cache Path')}}
+
+ {{ lang._('Response Code Caching')}}
+
{{ lang._('Error Pages')}}
@@ -603,6 +606,30 @@
+
+
+
+
+ | {{ lang._('ID') }} |
+ {{ lang._('Description') }} |
+ {{ lang._('Codes') }} |
+ {{ lang._('Time') }} |
+ {{ lang._('Commands') }} |
+
+
+
+
+
+
+ |
+
+
+
+ |
+
+
+
+
@@ -825,6 +852,7 @@
{{ partial("layout_partials/base_dialog",['fields': limit_request_connection,'id':'limit_request_connectiondlg', 'label':lang._('Edit Request Connection Limit')]) }}
{{ partial("layout_partials/base_dialog",['fields': limit_zone,'id':'limit_zonedlg', 'label':lang._('Edit Limit Zone')]) }}
{{ partial("layout_partials/base_dialog",['fields': cache_path,'id':'cache_pathdlg', 'label':lang._('Edit Cache Path')]) }}
+{{ partial("layout_partials/base_dialog",['fields': proxy_cache_valid,'id':'proxy_cache_validdlg', 'label':lang._('Edit Response Code Caching')]) }}
{{ partial("layout_partials/base_dialog",['fields': sni_hostname_map,'id':'sni_hostname_mapdlg', 'label':lang._('Edit SNI Hostname Mapping')]) }}
{{ partial("layout_partials/base_dialog",['fields': ipacl,'id':'ipacl_dlg', 'label':lang._('Edit IP ACL')]) }}
{{ partial("layout_partials/base_dialog",['fields': errorpage,'id':'errorpage_dlg', 'label':lang._('Edit Error Page')]) }}
diff --git a/www/nginx/src/opnsense/scripts/nginx/setup.php b/www/nginx/src/opnsense/scripts/nginx/setup.php
index 96a863c74..2e92730c2 100755
--- a/www/nginx/src/opnsense/scripts/nginx/setup.php
+++ b/www/nginx/src/opnsense/scripts/nginx/setup.php
@@ -120,15 +120,25 @@ if (isset($nginx['http_server'])) {
$cert['prv']
);
if (!empty($http_server['ca'])) {
- foreach ($http_server['ca'] as $caref) {
- $ca = find_ca($caref);
- if (isset($ca)) {
- export_pem_file(
- KEY_DIRECTORY . $hostname . '_ca.pem',
- $ca['crt']
- );
+ syslog(LOG_DEBUG, "NGINX setup: Setting up the CA certs for {$hostname}.");
+ $ca_certs = [];
+ foreach ($http_server['ca'] as $carefs) {
+ foreach(explode(',', $carefs) as $caref) {
+ syslog(LOG_DEBUG, "NGINX setup: Searching for {$caref} CA data");
+ $ca = find_ca($caref);
+ if (isset($ca)) {
+ syslog(LOG_DEBUG, "NGINX setup: client auth CA found. Adding to the list");
+ $ca_certs[] = base64_decode($ca['crt']);
+ }
}
}
+ if (count($ca_certs) > 0) {
+ export_pem_file(
+ KEY_DIRECTORY . $hostname . '_ca.pem',
+ '',
+ implode("\n", $ca_certs)
+ );
+ }
}
}
}
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 87592f4b4..f787eff2c 100644
--- a/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/http.conf
+++ b/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/http.conf
@@ -48,6 +48,12 @@ server_names_hash_max_size {{ OPNsense.Nginx.http.server_names_hash_max_size }};
{% if OPNsense.Nginx.http.server_names_hash_bucket_size is defined and OPNsense.Nginx.http.server_names_hash_bucket_size != '' %}
server_names_hash_bucket_size {{ OPNsense.Nginx.http.server_names_hash_bucket_size }};
{% endif %}
+{% if OPNsense.Nginx.http.variables_hash_max_size is defined and OPNsense.Nginx.http.variables_hash_max_size != '' %}
+variables_hash_max_size {{ OPNsense.Nginx.http.variables_hash_max_size }};
+{% endif %}
+{% if OPNsense.Nginx.http.variables_hash_bucket_size is defined and OPNsense.Nginx.http.variables_hash_bucket_size != '' %}
+variables_hash_bucket_size {{ OPNsense.Nginx.http.variables_hash_bucket_size }};
+{% endif %}
{% if OPNsense.Nginx.http.keepalive_timeout is defined and OPNsense.Nginx.http.keepalive_timeout != '' %}
keepalive_timeout {{ OPNsense.Nginx.http.keepalive_timeout }};
{% endif %}
@@ -117,7 +123,7 @@ server {
{% for listen_address in server.listen_https_address.split(',') %}
listen {{ listen_address }} ssl{% if server.proxy_protocol is defined and server.proxy_protocol == '1' %} proxy_protocol{% endif %}{% if server.default_server is defined and server.default_server == '1' %} default_server{% endif %};
{% endfor %}
- http2 on;
+ http2 {% if server.http2|default("1") == "1" %}on{% else %}off{% endif %};
{% if server.tls_reject_handshake is defined and server.tls_reject_handshake == '1'%}
ssl_reject_handshake on;
{% endif %}
@@ -246,6 +252,7 @@ server {
root /usr/local/etc/nginx/views;
}
{% endif %}
+ proxy_intercept_errors {% if server.proxy_intercept_errors|default("0") == "1" %}on{% else %}off{% endif %};
{% if server.security_header is defined and server.security_header != '' %}
{% set security_rule = helpers.getUUID(server.security_header) %}
{% if security_rule is defined %}
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 9f9b57603..71529378b 100644
--- a/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/location.conf
+++ b/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/location.conf
@@ -47,6 +47,9 @@ location {{ location.matchtype }} {{ location.urlpattern }} {
error_page {{ errorpage.statuscodes.replace(',', ' ') }} {% if errorpage.response is defined and errorpage.response != '' %}={{ errorpage.response }} {% endif %}{% if errorpage.redirect is defined and errorpage.redirect != '' %}{{ errorpage.redirect }}{% else %}/error_{{ errorpage_uuid.replace('-', '') }}.html{% endif %};
{% endfor %}
{% endif %}
+{% if location.proxy_intercept_errors is defined and location.proxy_intercept_errors != 'Inherit' %}
+ proxy_intercept_errors {{ location.proxy_intercept_errors }};
+{% endif %}
{% if location.force_https is defined and location.force_https == '1' %}
if ($scheme != "https") {
return 302 https://$host$request_uri;
@@ -144,7 +147,13 @@ location {{ location.matchtype }} {{ location.urlpattern }} {
proxy_cache_use_stale {{ location.cache_use_stale.replace(',', ' ') }};
{% endif %}
{% if location.cache_valid is defined and location.cache_valid != '' %}
- proxy_cache_valid {{ location.cache_valid }}m;
+ proxy_cache_valid {{ location.cache_valid }}m;
+{% endif %}
+{% if location.proxy_cache_valid is defined and location.proxy_cache_valid != '' %}
+{% for pcache_valid_uuid in location.proxy_cache_valid.split(',') %}
+{% set pcache_valid = helpers.getUUID(pcache_valid_uuid) %}
+ proxy_cache_valid {{ pcache_valid.code.replace(',', ' ') }} {{ pcache_valid.valid }}m;
+{% endfor %}
{% endif %}
proxy_cache_min_uses {{ location.cache_min_uses|default('1') }};
proxy_cache_background_update {% if location.cache_background_update is defined and location.cache_background_update == '1' %}on{% else %}off{% endif %};
diff --git a/www/nginx/src/opnsense/www/js/nginx/dist/configuration.min.js b/www/nginx/src/opnsense/www/js/nginx/dist/configuration.min.js
index 3ef498d00..d25e07387 100644
--- a/www/nginx/src/opnsense/www/js/nginx/dist/configuration.min.js
+++ b/www/nginx/src/opnsense/www/js/nginx/dist/configuration.min.js
@@ -1 +1 @@
-!function(t){var e={};function n(i){if(e[i])return e[i].exports;var s=e[i]={i:i,l:!1,exports:{}};return t[i].call(s.exports,s,s.exports,n),s.l=!0,s.exports}n.m=t,n.c=e,n.d=function(t,e,i){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:i})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var i=Object.create(null);if(n.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var s in t)n.d(i,s,function(e){return t[e]}.bind(null,s));return i},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=25)}({25:function(t,e,n){"use strict";n.r(e);var i=Backbone.View.extend({tagName:"div",attributes:{class:"container-fluid"},child_views:[],createModel:null,upstreamCollection:null,initialize:function(t){this.dataField=$(t.dataField),this.entryclass=t.entryclass,this.createModel=t.createModel,this.upstreamCollection=t.upstreamCollection,this.listenTo(this.collection,"add remove reset",this.render),this.listenTo(this.collection,"change",this.update),this.dataField.after(this.$el)},events:{"click .add":"addEntry"},render:function(){this.child_views.forEach(t=>t.remove()),this.$el.html(""),this.child_views=[],this.update(),this.collection.each(t=>{const e=new this.entryclass({model:t,collection:this.collection,upstreamCollection:this.upstreamCollection});this.child_views.push(e),this.$el.append(e.$el),e.render()}),this.$el.append($('\n \n \n
'))},update:function(){this.dataField.data("data",this.collection.toJSON())},addEntry:function(t){t.preventDefault(),this.collection.add(this.createModel())}});var s=Backbone.Collection.extend({url:"/api/nginx/settings/searchupstream",parse:function(t){return t.rows}});const a=Backbone.View.extend({tagName:"div",attributes:{class:"row"},events:{"keyup .key":function(){this.model.set("hostname",this.key.value)},"change .value":function(){this.model.set("upstream",this.value.value)},"click .delete":"deleteEntry"},key:null,value:null,delBtn:null,first:null,second:null,third:null,upstreamCollection:null,initialize:function(t){this.upstreamCollection=t.upstreamCollection,this.listenTo(this.upstreamCollection,"update reset add remove",this.regenerate_list),this.first=document.createElement("div"),this.first.classList.add("col-sm-5"),this.key=document.createElement("input"),this.first.append(this.key),this.key.type="text",this.key.classList.add("key"),this.key.value=this.model.get("hostname"),this.second=document.createElement("div"),this.second.classList.add("col-sm-5"),this.value=document.createElement("select"),this.second.append(this.value),this.value.classList.add("value"),this.value.classList.add("form-control"),this.value.value=this.model.get("upstream"),this.third=document.createElement("div"),this.third.classList.add("col-sm-2"),this.third.style.textAlign="right",this.delBtn=document.createElement("button"),this.delBtn.classList.add("delete"),this.delBtn.classList.add("btn"),this.delBtn.innerHTML='',this.third.append(this.delBtn),this.model.has("upstream")&&0!==this.upstreamCollection.where({uuid:this.model.get("upstream")}).length||this.upstreamCollection.length>0&&this.model.set("upstream",this.upstreamCollection.at(0).get("uuid")),this.$el.append(this.first).append(this.second).append(this.third)},render:function(){$(this.key).val(this.model.get("hostname")),this.regenerate_list(),$(this.value).val(this.model.get("upstream"))},deleteEntry:function(t){t.preventDefault(),this.collection.remove(this.model)},regenerate_list:function(){const t=$(this.value);t.html(""),this.upstreamCollection.each(e=>t.append(``)),t.val(this.model.get("upstream")),t.selectpicker("refresh")}}),l=Backbone.View.extend({tagName:"div",attributes:{class:"row"},events:{"keyup .key":function(){this.model.set("network",this.key.value)},"change .value":function(){this.model.set("action",this.value.value)},"click .delete":"deleteEntry"},key:null,value:null,delBtn:null,first:null,second:null,third:null,upstreamCollection:null,initialize:function(t){this.upstreamCollection=t.upstreamCollection,this.listenTo(this.upstreamCollection,"update reset add remove",this.regenerate_list),this.first=document.createElement("div"),this.first.classList.add("col-sm-5"),this.key=document.createElement("input"),this.first.append(this.key),this.key.type="text",this.key.classList.add("key"),this.key.value=this.model.get("network"),this.second=document.createElement("div"),this.second.classList.add("col-sm-5"),this.value=document.createElement("select"),this.second.append(this.value),this.value.classList.add("value"),this.value.classList.add("form-control"),this.value.value=this.model.get("action"),this.third=document.createElement("div"),this.third.classList.add("col-sm-2"),this.third.style.textAlign="right",this.delBtn=document.createElement("button"),this.delBtn.classList.add("delete"),this.delBtn.classList.add("btn"),this.delBtn.innerHTML='',this.third.append(this.delBtn),this.$el.append(this.first).append(this.second).append(this.third)},render:function(){$(this.key).val(this.model.get("network")),this.regenerate_list(),$(this.value).val(this.model.get("action"))},deleteEntry:function(t){t.preventDefault(),this.collection.remove(this.model)},regenerate_list:function(){const t=$(this.value);t.html(""),this.upstreamCollection.each(e=>t.append(``)),t.val(this.model.get("action")),t.selectpicker("refresh")}});var o=Backbone.Collection.extend({initialize:function(){let t=this;$("#snihostname\\.data").change(function(){t.regenerateFromView()})},regenerateFromView:function(){let t=$("#snihostname\\.data").data("data");_.isArray(t)||(t=[]),this.reset(t)}}),r=Backbone.Model.extend({}),d=Backbone.Model.extend({}),c=Backbone.Collection.extend({initialize:function(){let t=this;$("#ipacl\\.data").change(function(){t.regenerateFromView()})},regenerateFromView:function(){let t=$("#ipacl\\.data").data("data");_.isArray(t)||(t=[]),this.reset(t)}});const u=new s,h=new Backbone.Collection([{name:"Deny",value:"deny"},{name:"Allow",value:"allow"}]);$(document).ready(function(){mapDataToFormUI({frm_nginx:"/api/nginx/settings/get"}).done(function(){formatTokenizersUI(),$('select[data-allownew="false"]').selectpicker("refresh"),updateServiceControlUI("nginx")}),""!==window.location.hash&&$('a[href="'+window.location.hash+'"]').click(),$(".nav-tabs a").on("shown.bs.tab",function(t){history.pushState(null,null,t.target.hash)}),$(".reload_btn").click(function(){$(".reloadAct_progress").addClass("fa-spin"),ajaxCall("/api/nginx/service/reconfigure",{},function(){$(".reloadAct_progress").removeClass("fa-spin")})}),$('[id*="save_"]').each(function(){$(this).click(function(){let t=$(this).closest("form").attr("id"),e=$(this).closest("form").attr("data-title");saveFormToEndpoint("/api/nginx/settings/set",t,function(){$("#"+t+"_progress").addClass("fa fa-spinner fa-pulse"),ajaxCall("/api/nginx/service/reconfigure",{},function(n,i){$("#"+t+"_progress").removeClass("fa fa-spinner fa-pulse"),void 0===n||"success"===i&&"ok"===n.status?updateServiceControlUI("nginx"):BootstrapDialog.show({type:BootstrapDialog.TYPE_WARNING,title:e,message:JSON.stringify(n),draggable:!0})})})})}),["upstream","upstreamserver","location","credential","userlist","httpserver","streamserver","httprewrite","custompolicy","security_header","ipacl","limit_zone","cache_path","limit_request_connection","snifwd","errorpage","tls_fingerprint","resolver","syslog_target","naxsirule"].forEach(function(t){$("#grid-"+t).UIBootgrid({search:"/api/nginx/settings/search"+t,get:"/api/nginx/settings/get"+t+"/",set:"/api/nginx/settings/set"+t+"/",add:"/api/nginx/settings/add"+t+"/",del:"/api/nginx/settings/del"+t+"/",commands:{copy_uuid:{method:function(t){navigator.clipboard.writeText($(this).data("row-id"))}}},options:{selection:!1,multiSelect:!1,formatters:{commands:function(t,e){return' '},response:function(t,e){return"none"==e.response?"unchanged":e.response},statuscodes:function(t,e){const n=[],i=e.statuscodes.split(",");for(let t of i)n.push(t.substr(0,3));return n.join(", ")}}}})}),bind_naxsi_rule_dl_button(),function(){let t=new i({dataField:document.getElementById("snihostname.data"),upstreamCollection:u,entryclass:a,collection:new o,createModel:function(){return new r({hostname:"localhost"})}});window.snifield=t,t.render(),$("#grid-upstream").on("loaded.rs.jquery.bootgrid",function(){u.fetch()}),u.fetch()}();let t=new i({dataField:document.getElementById("ipacl.data"),upstreamCollection:h,entryclass:l,collection:new c,createModel:function(){return new d({network:"::",action:"deny"})}});window.ipaclfield=t,t.render()})}});
\ No newline at end of file
+!function(t){var e={};function n(i){if(e[i])return e[i].exports;var s=e[i]={i:i,l:!1,exports:{}};return t[i].call(s.exports,s,s.exports,n),s.l=!0,s.exports}n.m=t,n.c=e,n.d=function(t,e,i){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:i})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var i=Object.create(null);if(n.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var s in t)n.d(i,s,function(e){return t[e]}.bind(null,s));return i},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=25)}({25:function(t,e,n){"use strict";n.r(e);var i=Backbone.View.extend({tagName:"div",attributes:{class:"container-fluid"},child_views:[],createModel:null,upstreamCollection:null,initialize:function(t){this.dataField=$(t.dataField),this.entryclass=t.entryclass,this.createModel=t.createModel,this.upstreamCollection=t.upstreamCollection,this.listenTo(this.collection,"add remove reset",this.render),this.listenTo(this.collection,"change",this.update),this.dataField.after(this.$el)},events:{"click .add":"addEntry"},render:function(){this.child_views.forEach(t=>t.remove()),this.$el.html(""),this.child_views=[],this.update(),this.collection.each(t=>{const e=new this.entryclass({model:t,collection:this.collection,upstreamCollection:this.upstreamCollection});this.child_views.push(e),this.$el.append(e.$el),e.render()}),this.$el.append($('\n \n \n
'))},update:function(){this.dataField.data("data",this.collection.toJSON())},addEntry:function(t){t.preventDefault(),this.collection.add(this.createModel())}});var s=Backbone.Collection.extend({url:"/api/nginx/settings/searchupstream",parse:function(t){return t.rows}});const a=Backbone.View.extend({tagName:"div",attributes:{class:"row"},events:{"keyup .key":function(){this.model.set("hostname",this.key.value)},"change .value":function(){this.model.set("upstream",this.value.value)},"click .delete":"deleteEntry"},key:null,value:null,delBtn:null,first:null,second:null,third:null,upstreamCollection:null,initialize:function(t){this.upstreamCollection=t.upstreamCollection,this.listenTo(this.upstreamCollection,"update reset add remove",this.regenerate_list),this.first=document.createElement("div"),this.first.classList.add("col-sm-5"),this.key=document.createElement("input"),this.first.append(this.key),this.key.type="text",this.key.classList.add("key"),this.key.value=this.model.get("hostname"),this.second=document.createElement("div"),this.second.classList.add("col-sm-5"),this.value=document.createElement("select"),this.second.append(this.value),this.value.classList.add("value"),this.value.classList.add("form-control"),this.value.value=this.model.get("upstream"),this.third=document.createElement("div"),this.third.classList.add("col-sm-2"),this.third.style.textAlign="right",this.delBtn=document.createElement("button"),this.delBtn.classList.add("delete"),this.delBtn.classList.add("btn"),this.delBtn.innerHTML='',this.third.append(this.delBtn),this.model.has("upstream")&&0!==this.upstreamCollection.where({uuid:this.model.get("upstream")}).length||this.upstreamCollection.length>0&&this.model.set("upstream",this.upstreamCollection.at(0).get("uuid")),this.$el.append(this.first).append(this.second).append(this.third)},render:function(){$(this.key).val(this.model.get("hostname")),this.regenerate_list(),$(this.value).val(this.model.get("upstream"))},deleteEntry:function(t){t.preventDefault(),this.collection.remove(this.model)},regenerate_list:function(){const t=$(this.value);t.html(""),this.upstreamCollection.each(e=>t.append(``)),t.val(this.model.get("upstream")),t.selectpicker("refresh")}}),l=Backbone.View.extend({tagName:"div",attributes:{class:"row"},events:{"keyup .key":function(){this.model.set("network",this.key.value)},"change .value":function(){this.model.set("action",this.value.value)},"click .delete":"deleteEntry"},key:null,value:null,delBtn:null,first:null,second:null,third:null,upstreamCollection:null,initialize:function(t){this.upstreamCollection=t.upstreamCollection,this.listenTo(this.upstreamCollection,"update reset add remove",this.regenerate_list),this.first=document.createElement("div"),this.first.classList.add("col-sm-5"),this.key=document.createElement("input"),this.first.append(this.key),this.key.type="text",this.key.classList.add("key"),this.key.value=this.model.get("network"),this.second=document.createElement("div"),this.second.classList.add("col-sm-5"),this.value=document.createElement("select"),this.second.append(this.value),this.value.classList.add("value"),this.value.classList.add("form-control"),this.value.value=this.model.get("action"),this.third=document.createElement("div"),this.third.classList.add("col-sm-2"),this.third.style.textAlign="right",this.delBtn=document.createElement("button"),this.delBtn.classList.add("delete"),this.delBtn.classList.add("btn"),this.delBtn.innerHTML='',this.third.append(this.delBtn),this.$el.append(this.first).append(this.second).append(this.third)},render:function(){$(this.key).val(this.model.get("network")),this.regenerate_list(),$(this.value).val(this.model.get("action"))},deleteEntry:function(t){t.preventDefault(),this.collection.remove(this.model)},regenerate_list:function(){const t=$(this.value);t.html(""),this.upstreamCollection.each(e=>t.append(``)),t.val(this.model.get("action")),t.selectpicker("refresh")}});var o=Backbone.Collection.extend({initialize:function(){let t=this;$("#snihostname\\.data").change(function(){t.regenerateFromView()})},regenerateFromView:function(){let t=$("#snihostname\\.data").data("data");_.isArray(t)||(t=[]),this.reset(t)}}),r=Backbone.Model.extend({}),d=Backbone.Model.extend({}),c=Backbone.Collection.extend({initialize:function(){let t=this;$("#ipacl\\.data").change(function(){t.regenerateFromView()})},regenerateFromView:function(){let t=$("#ipacl\\.data").data("data");_.isArray(t)||(t=[]),this.reset(t)}});const u=new s,h=new Backbone.Collection([{name:"Deny",value:"deny"},{name:"Allow",value:"allow"}]);$(document).ready(function(){mapDataToFormUI({frm_nginx:"/api/nginx/settings/get"}).done(function(){formatTokenizersUI(),$('select[data-allownew="false"]').selectpicker("refresh"),updateServiceControlUI("nginx")}),""!==window.location.hash&&$('a[href="'+window.location.hash+'"]').click(),$(".nav-tabs a").on("shown.bs.tab",function(t){history.pushState(null,null,t.target.hash)}),$(".reload_btn").click(function(){$(".reloadAct_progress").addClass("fa-spin"),ajaxCall("/api/nginx/service/reconfigure",{},function(){$(".reloadAct_progress").removeClass("fa-spin")})}),$('[id*="save_"]').each(function(){$(this).click(function(){let t=$(this).closest("form").attr("id"),e=$(this).closest("form").attr("data-title");saveFormToEndpoint("/api/nginx/settings/set",t,function(){$("#"+t+"_progress").addClass("fa fa-spinner fa-pulse"),ajaxCall("/api/nginx/service/reconfigure",{},function(n,i){$("#"+t+"_progress").removeClass("fa fa-spinner fa-pulse"),void 0===n||"success"===i&&"ok"===n.status?updateServiceControlUI("nginx"):BootstrapDialog.show({type:BootstrapDialog.TYPE_WARNING,title:e,message:JSON.stringify(n),draggable:!0})})})})}),["upstream","upstreamserver","location","credential","userlist","httpserver","streamserver","httprewrite","custompolicy","security_header","ipacl","limit_zone","cache_path","proxy_cache_valid","limit_request_connection","snifwd","errorpage","tls_fingerprint","resolver","syslog_target","naxsirule"].forEach(function(t){$("#grid-"+t).UIBootgrid({search:"/api/nginx/settings/search"+t,get:"/api/nginx/settings/get"+t+"/",set:"/api/nginx/settings/set"+t+"/",add:"/api/nginx/settings/add"+t+"/",del:"/api/nginx/settings/del"+t+"/",commands:{copy_uuid:{method:function(t){navigator.clipboard.writeText($(this).data("row-id"))}}},options:{selection:!1,multiSelect:!1,formatters:{commands:function(t,e){return' '},response:function(t,e){return"none"==e.response?"unchanged":e.response},statuscodes:function(t,e){const n=[],i=e.statuscodes.split(",");for(let t of i)n.push(t.substr(0,3));return n.join(", ")}}}})}),bind_naxsi_rule_dl_button(),function(){let t=new i({dataField:document.getElementById("snihostname.data"),upstreamCollection:u,entryclass:a,collection:new o,createModel:function(){return new r({hostname:"localhost"})}});window.snifield=t,t.render(),$("#grid-upstream").on("loaded.rs.jquery.bootgrid",function(){u.fetch()}),u.fetch()}();let t=new i({dataField:document.getElementById("ipacl.data"),upstreamCollection:h,entryclass:l,collection:new c,createModel:function(){return new d({network:"::",action:"deny"})}});window.ipaclfield=t,t.render()})}});
diff --git a/www/nginx/src/opnsense/www/js/nginx/src/nginx_config.js b/www/nginx/src/opnsense/www/js/nginx/src/nginx_config.js
index e61a2469b..4272efdaa 100644
--- a/www/nginx/src/opnsense/www/js/nginx/src/nginx_config.js
+++ b/www/nginx/src/opnsense/www/js/nginx/src/nginx_config.js
@@ -66,6 +66,7 @@ function init_grids() {
'ipacl',
'limit_zone',
'cache_path',
+ 'proxy_cache_valid',
'limit_request_connection',
'snifwd',
'errorpage',