diff --git a/dns/bind/pkg-descr b/dns/bind/pkg-descr
index 284434dc92..24421ef893 100644
--- a/dns/bind/pkg-descr
+++ b/dns/bind/pkg-descr
@@ -7,6 +7,12 @@ necessary for asking and answering name service questions.
Plugin Changelog
================
+1.35
+
+* Add per-forwarder destination port for plain DNS forwarders
+* Add DNS-over-TLS (DoT) forwarders with TLS hostname verification
+* Migrate legacy DNS Forwarders list to the new DNS Forwarders tab
+
1.34
* Add custom configuration include directory /usr/local/etc/namedb/named.conf.d (contributed by Nicholas Card)
diff --git a/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/Api/ForwarderController.php b/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/Api/ForwarderController.php
new file mode 100644
index 0000000000..00e438d3e4
--- /dev/null
+++ b/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/Api/ForwarderController.php
@@ -0,0 +1,99 @@
+
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+namespace OPNsense\Bind\Api;
+
+use OPNsense\Base\ApiMutableModelControllerBase;
+
+class ForwarderController extends ApiMutableModelControllerBase
+{
+ protected static $internalModelName = 'forwarder';
+ protected static $internalModelClass = '\OPNsense\Bind\Forwarder';
+
+ public function searchDnsForwarderAction()
+ {
+ return $this->searchBase('forwarders.dns', ['enabled', 'ip', 'port']);
+ }
+
+ public function getDnsForwarderAction($uuid = null)
+ {
+ return $this->getBase('dns', 'forwarders.dns', $uuid);
+ }
+
+ public function addDnsForwarderAction()
+ {
+ return $this->addBase('dns', 'forwarders.dns');
+ }
+
+ public function delDnsForwarderAction($uuid)
+ {
+ return $this->delBase('forwarders.dns', $uuid);
+ }
+
+ public function setDnsForwarderAction($uuid)
+ {
+ return $this->setBase('dns', 'forwarders.dns', $uuid);
+ }
+
+ public function toggleDnsForwarderAction($uuid)
+ {
+ return $this->toggleBase('forwarders.dns', $uuid);
+ }
+
+ public function searchDotForwarderAction()
+ {
+ return $this->searchBase('forwarders.dot', ['enabled', 'ip', 'port', 'tlshostname']);
+ }
+
+ public function getDotForwarderAction($uuid = null)
+ {
+ return $this->getBase('dot', 'forwarders.dot', $uuid);
+ }
+
+ public function addDotForwarderAction()
+ {
+ return $this->addBase('dot', 'forwarders.dot');
+ }
+
+ public function delDotForwarderAction($uuid)
+ {
+ return $this->delBase('forwarders.dot', $uuid);
+ }
+
+ public function setDotForwarderAction($uuid)
+ {
+ return $this->setBase('dot', 'forwarders.dot', $uuid);
+ }
+
+ public function toggleDotForwarderAction($uuid)
+ {
+ return $this->toggleBase('forwarders.dot', $uuid);
+ }
+}
diff --git a/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/ForwardingController.php b/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/ForwardingController.php
new file mode 100644
index 0000000000..015327b7a4
--- /dev/null
+++ b/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/ForwardingController.php
@@ -0,0 +1,39 @@
+
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace OPNsense\Bind;
+
+class ForwardingController extends \OPNsense\Base\IndexController
+{
+ public function indexAction()
+ {
+ $this->view->formDialogEditBindDnsForwarder = $this->getForm("dialogEditBindDnsForwarder");
+ $this->view->formDialogEditBindDotForwarder = $this->getForm("dialogEditBindDotForwarder");
+ $this->view->pick('OPNsense/Bind/forwarding');
+ }
+}
diff --git a/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/forms/dialogEditBindDnsForwarder.xml b/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/forms/dialogEditBindDnsForwarder.xml
new file mode 100644
index 0000000000..506350c75e
--- /dev/null
+++ b/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/forms/dialogEditBindDnsForwarder.xml
@@ -0,0 +1,46 @@
+
+
+
diff --git a/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/forms/dialogEditBindDotForwarder.xml b/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/forms/dialogEditBindDotForwarder.xml
new file mode 100644
index 0000000000..794fb0f2f3
--- /dev/null
+++ b/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/forms/dialogEditBindDotForwarder.xml
@@ -0,0 +1,52 @@
+
+
+
diff --git a/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/forms/general.xml b/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/forms/general.xml
index 23e9c92026..73ae992496 100644
--- a/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/forms/general.xml
+++ b/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/forms/general.xml
@@ -61,14 +61,6 @@
trueSpecify the IPv6 address used as a source for zone transfers.
-
- general.forwarders
-
-
- select_multiple
- true
- Set one or more hosts to send your DNS queries if the request is unknown.
- general.filteraaaav4
@@ -126,6 +118,12 @@
select_multipleDefine the ACLs where you allow which client are allowed to query this server.
+
+ general.forwarding
+
+ dropdown
+ Set to "Forward Only" to prevent BIND from falling back to its own recursive resolution when forwarders are unreachable. If set to "Forward Only", configure forwarders on the DNS Forwarders tab.
+ general.dnssecvalidation
diff --git a/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/Forwarder.php b/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/Forwarder.php
new file mode 100644
index 0000000000..54616d2652
--- /dev/null
+++ b/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/Forwarder.php
@@ -0,0 +1,31 @@
+
+ All rights reserved.
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+*/
+
+namespace OPNsense\Bind;
+
+use OPNsense\Base\BaseModel;
+
+class Forwarder extends BaseModel
+{
+}
diff --git a/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/Forwarder.xml b/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/Forwarder.xml
new file mode 100644
index 0000000000..ce433e405e
--- /dev/null
+++ b/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/Forwarder.xml
@@ -0,0 +1,78 @@
+
+
+
+ //OPNsense/bind/forwarder
+ BIND DNS and DNS-over-TLS forwarders
+ 1.0.1
+
+
+
+
+ 1
+ Y
+
+
+ Y
+ N
+
+
+ A plain forwarder with this IP already exists.
+ UniqueConstraint
+
+
+
+
+ 53
+ Y
+
+
+
+
+ 1
+ Y
+
+
+ Y
+ N
+
+
+ A DoT forwarder with this IP already exists.
+ UniqueConstraint
+
+
+
+
+ 853
+ Y
+
+
+ N
+
+
+
+
+
diff --git a/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/General.xml b/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/General.xml
index 238c9dc248..69fc744e9e 100644
--- a/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/General.xml
+++ b/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/General.xml
@@ -45,9 +45,14 @@
53530Y
-
- Y
-
+
+ first
+ Y
+
+ Forward First
+ Forward Only
+
+ 0Y
diff --git a/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/Menu/Menu.xml b/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/Menu/Menu.xml
index 06ed53ca81..a5adcf179b 100644
--- a/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/Menu/Menu.xml
+++ b/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/Menu/Menu.xml
@@ -1,8 +1,9 @@
diff --git a/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/Migrations/M1_0_1.php b/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/Migrations/M1_0_1.php
new file mode 100644
index 0000000000..9f4adde6bf
--- /dev/null
+++ b/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/Migrations/M1_0_1.php
@@ -0,0 +1,90 @@
+
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace OPNsense\Bind\Migrations;
+
+use OPNsense\Base\BaseModelMigration;
+use OPNsense\Bind\Forwarder;
+use OPNsense\Core\Config;
+
+class M1_0_1 extends BaseModelMigration
+{
+ /**
+ * Migrate the legacy General model forwarders CSV into this model's DNS
+ * forwarder grid.
+ * @param $model
+ */
+ public function run($model)
+ {
+ if (!($model instanceof Forwarder)) {
+ return;
+ }
+
+ $config = Config::getInstance()->object();
+ if (empty($config->OPNsense->bind->general->forwarders)) {
+ return;
+ }
+
+ $legacy = trim((string)$config->OPNsense->bind->general->forwarders);
+ if ($legacy === '') {
+ return;
+ }
+
+ if (count(iterator_to_array($model->forwarders->dns->iterateItems())) > 0) {
+ return;
+ }
+
+ foreach (explode(',', $legacy) as $token) {
+ $token = trim($token);
+ if ($token === '') {
+ continue;
+ }
+
+ $ip = $token;
+ $port = '53';
+
+ if (filter_var($token, FILTER_VALIDATE_IP) === false && strpos($token, ':') !== false) {
+ [$candidateIp, $candidatePort] = explode(':', $token, 2);
+ if (filter_var($candidateIp, FILTER_VALIDATE_IP) !== false &&
+ ctype_digit($candidatePort) && $candidatePort >= 1 && $candidatePort <= 65535
+ ) {
+ $ip = $candidateIp;
+ $port = $candidatePort;
+ } else {
+ syslog(LOG_WARNING, sprintf('BIND migration: no valid port found in "%s", defaulting to 53.', $token));
+ }
+ }
+
+ $forwarder = $model->forwarders->dns->add();
+ $forwarder->ip = $ip;
+ $forwarder->port = $port;
+ }
+
+ $config->OPNsense->bind->general->forwarders = '';
+ }
+}
diff --git a/dns/bind/src/opnsense/mvc/app/views/OPNsense/Bind/forwarding.volt b/dns/bind/src/opnsense/mvc/app/views/OPNsense/Bind/forwarding.volt
new file mode 100644
index 0000000000..df99a49ca7
--- /dev/null
+++ b/dns/bind/src/opnsense/mvc/app/views/OPNsense/Bind/forwarding.volt
@@ -0,0 +1,153 @@
+{#
+ # Copyright (C) 2026 Bryan Wiegand
+ # All rights reserved.
+ #
+ # Redistribution and use in source and binary forms, with or without
+ # modification, are permitted provided that the following conditions are met:
+ #
+ # 1. Redistributions of source code must retain the above copyright notice,
+ # this list of conditions and the following disclaimer.
+ #
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
+ # this list of conditions and the following disclaimer in the documentation
+ # and/or other materials provided with the distribution.
+ #
+ # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS AND ANY
+ # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ # DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
+ # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#}
+
+
+