diff --git a/data/templates/frr/daemons.frr.tmpl b/data/templates/frr/daemons.frr.tmpl
index a65f0868a..c637e18bc 100644
--- a/data/templates/frr/daemons.frr.tmpl
+++ b/data/templates/frr/daemons.frr.tmpl
@@ -1,114 +1,113 @@
 #
 # The watchfrr, zebra, mgmtd and staticd daemons are always started.
 #
 # Note: The following FRR-services must be kept disabled because they are replaced by other packages in VyOS:
 #
 # pimd   Replaced by package igmpproxy.
 # nhrpd  Replaced by package opennhrp.
 # pbrd   Replaced by PBR in nftables.
 # vrrpd  Replaced by package keepalived.
 #
 # And these must be disabled aswell since they are currently missing a VyOS CLI:
 #
 # eigrp
 # sharpd
 # fabricd
 # pathd
 #
 # The zebra, mgmtd and staticd daemons are always started and can not be disabled
 #
 #zebra=yes
 #mgmtd=yes
 #staticd=yes
 
 bgpd=yes
 ospfd=yes
 ospf6d=yes
 ripd=yes
 ripngd=yes
 isisd=yes
 pimd=no
 pim6d=yes
 ldpd=yes
 nhrpd=no
 eigrpd=no
 babeld=yes
 sharpd=no
 pbrd=no
 bfdd=yes
 fabricd=no
 vrrpd=no
 pathd=no
 
 #
 # Define defaults for all services even those who shall be kept disabled.
 #
 
 zebra_options="  --daemon -A 127.0.0.1 -s 90000000{{ ' -M snmp' if snmp.zebra is vyos_defined }}{{ ' -M irdp' if irdp is vyos_defined }}"
 mgmtd_options="  --daemon -A 127.0.0.1"
 staticd_options="--daemon -A 127.0.0.1"
 bgpd_options="   --daemon -A 127.0.0.1 -M rpki{{ ' -M snmp' if snmp.bgpd is vyos_defined }}{{ ' -M bmp' if bmp is vyos_defined }}"
 ospfd_options="  --daemon -A 127.0.0.1{{ ' -M snmp' if snmp.ospfd is vyos_defined }}"
 ospf6d_options=" --daemon -A ::1{{ ' -M snmp' if snmp.ospf6d is vyos_defined }}"
 ripd_options="   --daemon -A 127.0.0.1{{ ' -M snmp' if snmp.ripd is vyos_defined }}"
 ripngd_options=" --daemon -A ::1"
 isisd_options="  --daemon -A 127.0.0.1{{ ' -M snmp' if snmp.isisd is vyos_defined }}"
 pimd_options="   --daemon -A 127.0.0.1"
 pim6d_options="  --daemon -A ::1"
 ldpd_options="   --daemon -A 127.0.0.1{{ ' -M snmp' if snmp.ldpd is vyos_defined }}"
 nhrpd_options="  --daemon -A 127.0.0.1"
 eigrpd_options=" --daemon -A 127.0.0.1"
 babeld_options=" --daemon -A 127.0.0.1"
 sharpd_options=" --daemon -A 127.0.0.1"
 pbrd_options="   --daemon -A 127.0.0.1"
 bfdd_options="   --daemon -A 127.0.0.1"
 fabricd_options="--daemon -A 127.0.0.1"
 vrrpd_options="  --daemon -A 127.0.0.1"
 pathd_options="  --daemon -A 127.0.0.1"
 
 #frr_global_options=""
 
 #zebra_wrap=""
 #mgmtd_wrap=""
 #staticd_wrap=""
 #bgpd_wrap=""
 #ospfd_wrap=""
 #ospf6d_wrap=""
 #ripd_wrap=""
 #ripngd_wrap=""
 #isisd_wrap=""
 #pimd_wrap=""
 #pim6d_wrap=""
 #ldpd_wrap=""
 #nhrpd_wrap=""
 #eigrpd_wrap=""
 #babeld_wrap=""
 #sharpd_wrap=""
 #pbrd_wrap=""
 #bfdd_wrap=""
 #fabricd_wrap=""
 #vrrpd_wrap=""
 #pathd_wrap=""
 
 #all_wrap=""
 
 #
 # Other options.
 #
 # For more information see:
 # https://github.com/FRRouting/frr/blob/stable/9.0/tools/etc/frr/daemons
 # https://docs.frrouting.org/en/stable-9.0/setup.html
 #
 
 vtysh_enable=yes
 watchfrr_enable=no
 valgrind_enable=no
 
 #watchfrr_options=""
 
 frr_profile="traditional"
 
-#MAX_FDS=1024
+MAX_FDS={{ descriptors }}
 
 #FRR_NO_ROOT="yes"
-
diff --git a/interface-definitions/system-frr.xml.in b/interface-definitions/system-frr.xml.in
index 9fe23ed75..76001b392 100644
--- a/interface-definitions/system-frr.xml.in
+++ b/interface-definitions/system-frr.xml.in
@@ -1,77 +1,91 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interfaceDefinition>
   <node name="system">
     <children>
       <node name="frr" owner="${vyos_conf_scripts_dir}/system_frr.py">
         <properties>
           <help>Configure FRR parameters</help>
           <!-- Before components that use FRR -->
           <priority>150</priority>
         </properties>
         <children>
           <leafNode name="bmp">
             <properties>
               <help>Enable BGP Monitoring Protocol support</help>
               <valueless/>
             </properties>
           </leafNode>
+          <leafNode name="descriptors">
+            <properties>
+              <help>Number of open file descriptors a process is allowed to use</help>
+              <valueHelp>
+                <format>u32:1024-8192</format>
+                <description>Number of file descriptors</description>
+              </valueHelp>
+              <constraint>
+                <validator name="numeric" argument="--range 1024-8192"/>
+              </constraint>
+              <constraintErrorMessage>Port number must be in range 1024 to 8192</constraintErrorMessage>
+            </properties>
+            <defaultValue>1024</defaultValue>
+          </leafNode>
           <leafNode name="irdp">
             <properties>
               <help>Enable ICMP Router Discovery Protocol support</help>
               <valueless/>
             </properties>
           </leafNode>
           <node name="snmp">
             <properties>
               <help>Enable SNMP integration for next daemons</help>
             </properties>
             <children>
               <leafNode name="bgpd">
                 <properties>
                   <help>BGP</help>
                   <valueless/>
                 </properties>
               </leafNode>
               <leafNode name="isisd">
                 <properties>
                   <help>IS-IS</help>
                   <valueless/>
                 </properties>
               </leafNode>
               <leafNode name="ldpd">
                 <properties>
                   <help>LDP</help>
                   <valueless/>
                 </properties>
               </leafNode>
               <leafNode name="ospf6d">
                 <properties>
                   <help>OSPFv3</help>
                   <valueless/>
                 </properties>
               </leafNode>
               <leafNode name="ospfd">
                 <properties>
                   <help>OSPFv2</help>
                   <valueless/>
                 </properties>
               </leafNode>
               <leafNode name="ripd">
                 <properties>
                   <help>RIP</help>
                   <valueless/>
                 </properties>
               </leafNode>
               <leafNode name="zebra">
                 <properties>
                   <help>Zebra (IP routing manager)</help>
                   <valueless/>
                 </properties>
               </leafNode>
             </children>
           </node>
         </children>
       </node>
     </children>
   </node>
 </interfaceDefinition>
diff --git a/smoketest/scripts/cli/test_system_frr.py b/smoketest/scripts/cli/test_system_frr.py
index 3eb0cd0ab..a2ce58bf6 100755
--- a/smoketest/scripts/cli/test_system_frr.py
+++ b/smoketest/scripts/cli/test_system_frr.py
@@ -1,146 +1,162 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2019-2020 VyOS maintainers and contributors
+# Copyright (C) 2021-2023 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import re
 import unittest
+
 from base_vyostest_shim import VyOSUnitTestSHIM
 from vyos.utils.file import read_file
 
 config_file = '/etc/frr/daemons'
 base_path = ['system', 'frr']
 
-
 def daemons_config_parse(daemons_config):
     # create regex for parsing daemons options
     regex_daemon_config = re.compile(
         r'^(?P<daemon_name>\w+)_options="(?P<daemon_options>.*)"$', re.M)
     # create empty dict for config
     daemons_config_dict = {}
     # fill dictionary with actual config
     for daemon in regex_daemon_config.finditer(daemons_config):
         daemon_name = daemon.group('daemon_name')
         daemon_options = daemon.group('daemon_options')
-        daemons_config_dict[daemon_name] = daemon_options
+        daemons_config_dict[daemon_name] = daemon_options.lstrip()
 
     # return daemons config
     return (daemons_config_dict)
 
 
 class TestSystemFRR(VyOSUnitTestSHIM.TestCase):
+    @classmethod
+    def setUpClass(cls):
+        super(TestSystemFRR, cls).setUpClass()
+
+        # ensure we can also run this test on a live system - so lets clean
+        # out the current configuration :)
+        cls.cli_delete(cls, base_path)
 
     def tearDown(self):
         self.cli_delete(base_path)
         self.cli_commit()
 
     def test_frr_snmp_multipledaemons(self):
         # test SNMP integration for multiple daemons
         test_daemon_names = ['ospfd', 'bgpd']
         for test_daemon_name in test_daemon_names:
             self.cli_set(base_path + ['snmp', test_daemon_name])
         self.cli_commit()
 
         # read the config file and check content
         daemons_config = read_file(config_file)
         daemons_config_dict = daemons_config_parse(daemons_config)
         # prepare regex for matching SNMP integration
         regex_snmp = re.compile(r'^.* -M snmp.*$')
         for (daemon_name, daemon_options) in daemons_config_dict.items():
             snmp_enabled = regex_snmp.match(daemon_options)
             if daemon_name in test_daemon_names:
                 self.assertTrue(snmp_enabled)
             else:
                 self.assertFalse(snmp_enabled)
 
-    def test_frr_snmp_addandremove(self):
+    def test_frr_snmp_add_remove(self):
         # test enabling and disabling of SNMP integration
         test_daemon_names = ['ospfd', 'bgpd']
         for test_daemon_name in test_daemon_names:
             self.cli_set(base_path + ['snmp', test_daemon_name])
         self.cli_commit()
 
         self.cli_delete(base_path)
         self.cli_commit()
 
         # read the config file and check content
         daemons_config = read_file(config_file)
         daemons_config_dict = daemons_config_parse(daemons_config)
         # prepare regex for matching SNMP integration
         regex_snmp = re.compile(r'^.* -M snmp.*$')
         for test_daemon_name in test_daemon_names:
             snmp_enabled = regex_snmp.match(
                 daemons_config_dict[test_daemon_name])
             self.assertFalse(snmp_enabled)
 
     def test_frr_snmp_empty(self):
         # test empty config section
         self.cli_set(base_path + ['snmp'])
         self.cli_commit()
 
         # read the config file and check content
         daemons_config = read_file(config_file)
         daemons_config_dict = daemons_config_parse(daemons_config)
         # prepare regex for matching SNMP integration
         regex_snmp = re.compile(r'^.* -M snmp.*$')
         for daemon_options in daemons_config_dict.values():
             snmp_enabled = regex_snmp.match(daemon_options)
             self.assertFalse(snmp_enabled)
 
     def test_frr_bmp(self):
         # test BMP
         self.cli_set(base_path + ['bmp'])
         self.cli_commit()
 
         # read the config file and check content
         daemons_config = read_file(config_file)
         daemons_config_dict = daemons_config_parse(daemons_config)
         # prepare regex
         regex_bmp = re.compile(r'^.* -M bmp.*$')
         bmp_enabled = regex_bmp.match(daemons_config_dict['bgpd'])
         self.assertTrue(bmp_enabled)
 
     def test_frr_irdp(self):
         # test IRDP
         self.cli_set(base_path + ['irdp'])
         self.cli_commit()
 
         # read the config file and check content
         daemons_config = read_file(config_file)
         daemons_config_dict = daemons_config_parse(daemons_config)
         # prepare regex
         regex_irdp = re.compile(r'^.* -M irdp.*$')
         irdp_enabled = regex_irdp.match(daemons_config_dict['zebra'])
         self.assertTrue(irdp_enabled)
 
-    def test_frr_bmpandsnmp(self):
+    def test_frr_bmp_and_snmp(self):
         # test empty config section
         self.cli_set(base_path + ['bmp'])
         self.cli_set(base_path + ['snmp', 'bgpd'])
         self.cli_commit()
 
         # read the config file and check content
         daemons_config = read_file(config_file)
         daemons_config_dict = daemons_config_parse(daemons_config)
         # prepare regex
         regex_snmp = re.compile(r'^.* -M bmp.*$')
         regex_snmp = re.compile(r'^.* -M snmp.*$')
         bmp_enabled = regex_snmp.match(daemons_config_dict['bgpd'])
         snmp_enabled = regex_snmp.match(daemons_config_dict['bgpd'])
         self.assertTrue(bmp_enabled)
         self.assertTrue(snmp_enabled)
 
+    def test_frr_file_descriptors(self):
+        file_descriptors = '4096'
+
+        self.cli_set(base_path + ['descriptors', file_descriptors])
+        self.cli_commit()
+
+        # read the config file and check content
+        daemons_config = read_file(config_file)
+        self.assertIn(f'MAX_FDS={file_descriptors}', daemons_config)
 
 if __name__ == '__main__':
     unittest.main(verbosity=2)
diff --git a/src/conf_mode/system_frr.py b/src/conf_mode/system_frr.py
index d8224b3c3..d43b172a6 100755
--- a/src/conf_mode/system_frr.py
+++ b/src/conf_mode/system_frr.py
@@ -1,84 +1,86 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2021-2023 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 from pathlib import Path
 from sys import exit
 
 from vyos import ConfigError
 from vyos import airbag
 from vyos.config import Config
 from vyos.logger import syslog
 from vyos.template import render_to_string
 from vyos.utils.boot import boot_configuration_complete
 from vyos.utils.file import read_file
 from vyos.utils.file import write_file
 from vyos.utils.process import call
 airbag.enable()
 
 # path to daemons config and config status files
 config_file = '/etc/frr/daemons'
 
 def get_config(config=None):
     if config:
         conf = config
     else:
         conf = Config()
 
     base = ['system', 'frr']
-    frr_config = conf.get_config_dict(base, get_first_key=True)
+    frr_config = conf.get_config_dict(base, key_mangling=('-', '_'),
+                                      get_first_key=True,
+                                      with_recursive_defaults=True)
 
     return frr_config
 
 def verify(frr_config):
     # Nothing to verify here
     pass
 
 def generate(frr_config):
     # read daemons config file
     daemons_config_current = read_file(config_file)
     # generate new config file
     daemons_config_new = render_to_string('frr/daemons.frr.tmpl', frr_config)
     # update configuration file if this is necessary
     if daemons_config_new != daemons_config_current:
         syslog.warning('FRR daemons configuration file need to be changed')
         write_file(config_file, daemons_config_new)
         frr_config['config_file_changed'] = True
 
 def apply(frr_config):
     # display warning to user
     if boot_configuration_complete() and frr_config.get('config_file_changed'):
         # Since FRR restart is not safe thing, better to give
         # control over this to users
         print('''
         You need to reboot a router (preferred) or restart FRR
         to apply changes in modules settings
         ''')
 
     # restart FRR automatically
     # During initial boot this should be safe in most cases
     if not boot_configuration_complete() and frr_config.get('config_file_changed'):
         syslog.warning('Restarting FRR to apply changes in modules')
         call(f'systemctl restart frr.service')
 
 if __name__ == '__main__':
     try:
         c = get_config()
         verify(c)
         generate(c)
         apply(c)
     except ConfigError as e:
         print(e)
         exit(1)