diff --git a/cobbler/actions/sync.py b/cobbler/actions/sync.py
index 03e9692c13..458f77822a 100644
--- a/cobbler/actions/sync.py
+++ b/cobbler/actions/sync.py
@@ -245,7 +245,7 @@ def sync_dhcp(self):
         """
         if self.settings.manage_dhcp:
             self.write_dhcp()
-            self.dhcp.sync_dhcp()
+            self.dhcp.sync()
 
     def clean_link_cache(self):
         """
@@ -434,9 +434,10 @@ def add_single_system(self, name: str):
             return
         # rebuild system_list file in webdir
         if self.settings.manage_dhcp:
-            self.dhcp.regen_ethers()
+            self.dhcp.sync_single_system(system)
+            self.dhcp.sync_single_ethers_entry(system)
         if self.settings.manage_dns:
-            self.dns.regen_hosts()
+            self.dns.sync_single_hosts_entry(system)
         # write the PXE files for the system
         self.tftpd.sync_single_system(system)
 
diff --git a/cobbler/cobbler_collections/collection.py b/cobbler/cobbler_collections/collection.py
index cb1462c54d..35c42379b0 100644
--- a/cobbler/cobbler_collections/collection.py
+++ b/cobbler/cobbler_collections/collection.py
@@ -406,7 +406,6 @@ def add(self, ref, save: bool = False, with_copy: bool = False, with_triggers: b
                     if ref.virt_type == "openvz":
                         ref.netboot_enabled = False
                     self.lite_sync.add_single_system(ref.name)
-                    self.api.sync_systems(systems=[ref.name])
                 elif isinstance(ref, profile.Profile):
                     # we don't need openvz containers to be network bootable
                     if ref.virt_type == "openvz":
diff --git a/cobbler/modules/managers/dnsmasq.py b/cobbler/modules/managers/dnsmasq.py
index 96375b992b..2fb098895f 100644
--- a/cobbler/modules/managers/dnsmasq.py
+++ b/cobbler/modules/managers/dnsmasq.py
@@ -20,6 +20,7 @@
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 02110-1301  USA
 """
+from cobbler import utils
 
 import time
 
@@ -28,6 +29,9 @@
 
 MANAGER = None
 
+COBBLER_HOSTS_FILE = "/var/lib/cobbler/cobbler_hosts"
+ETHERS_FILE = "/etc/ethers"
+
 
 def register() -> str:
     """
@@ -52,12 +56,34 @@ def what() -> str:
         """
         return "dnsmasq"
 
-    def write_configs(self):
+    def sync(self):
+        """
+        Generate full config from scratch, write it to the filesystem, and restart DHCP service.
+        """
+        # Reset cached config
+        self.config = self.gen_full_config()
+        self._write_configs(self.config)
+        return self.restart_service()
+
+    def write_configs(self) -> None:
         """
         DHCP files are written when ``manage_dhcp`` is set in our settings.
 
         :raises OSError
+        :raises ValueError
+        """
+        data = self.gen_full_config()
+        self._write_configs(data)
+
+    def _write_configs(self, config_data=None) -> None:
+        """
+        Internal function to write DHCP files.
+
+        :raises OSError
+        :raises ValueError
         """
+        if not config_data:
+            raise ValueError("No config to write.")
 
         settings_file = "/etc/dnsmasq.conf"
         template_file = "/etc/cobbler/dnsmasq.template"
@@ -69,56 +95,23 @@ def write_configs(self):
         template_data = f2.read()
         f2.close()
 
-        system_definitions = {}
+        config_copy = config_data.copy() # template rendering changes the passed dict
+        self.logger.info("Writing %s", settings_file)
+        self.templar.render(template_data, config_copy, settings_file)
 
-        # we used to just loop through each system, but now we must loop
-        # through each network interface of each system.
+    def gen_full_config(self):
+        """Generate DHCP configuration for all systems."""
+        system_definitions: Dict[str, str] = {}
 
         for system in self.systems:
-
-            if not system.is_management_supported(cidr_ok=False):
-                continue
-
             profile = system.get_conceptual_parent()
-            distro = profile.get_conceptual_parent()
-            for interface in system.interfaces.values():
-
-                mac = interface.mac_address
-                ip = interface.ip_address
-                host = interface.dns_name
-                ipv6 = interface.ipv6_address
-
-                if not mac:
-                    # can't write a DHCP entry for this system
-                    continue
-
-                # In many reallife situations there is a need to control the IP address and hostname for a specific
-                # client when only the MAC address is available. In addition to that in some scenarios there is a need
-                # to explicitly label a host with the applicable architecture in order to correctly handle situations
-                # where we need something other than ``pxelinux.0``. So we always write a dhcp-host entry with as much
-                # info as possible to allow maximum control and flexibility within the dnsmasq config.
-
-                systxt = "dhcp-host=net:" + distro.arch.value.lower() + "," + mac
-
-                if host != "":
-                    systxt += "," + host
-
-                if ip != "":
-                    systxt += "," + ip
-                if ipv6 != "":
-                    systxt += ",[%s]" % ipv6
-
-                systxt += "\n"
-
-                dhcp_tag = interface.dhcp_tag
-                if dhcp_tag == "":
-                    dhcp_tag = "default"
-
-                if dhcp_tag not in system_definitions:
-                    system_definitions[dhcp_tag] = ""
-                system_definitions[dhcp_tag] = system_definitions[dhcp_tag] + systxt
-
-        # We are now done with the looping through each interface of each system.
+            if profile is None:
+                continue
+            distro: Distro = profile.get_conceptual_parent()  # type: ignore
+            if distro is None:
+                continue
+            system_config = self._gen_system_config(system)
+            system_definitions = utils.merge_dicts_recursive(system_definitions, system_config, str_append=True)
 
         metadata = {
             "insert_cobbler_system_definitions": system_definitions.get("default", ""),
@@ -134,58 +127,192 @@ def write_configs(self):
                 continue
             metadata["insert_cobbler_system_definitions_%s" % x] = system_definitions[x]
 
-        self.templar.render(template_data, metadata, settings_file)
+        return metadata
+
+    def _gen_system_config(
+        self,
+        system_obj: "System",
+    ):
+        """
+        Generate dnsmasq config for a single system.
+
+        :param system_obj: System to generate dnsmasq config for
+        """
+        # we used to just loop through each system, but now we must loop
+        # through each network interface of each system.
+        system_definitions: Dict[str, str] = {}
+
+        if not system_obj.is_management_supported(cidr_ok=False):
+            self.logger.debug(
+                "%s does not meet precondition: MAC, IPv4, or IPv6 address is required.",
+                system_obj.name,
+            )
+            return {}
+
+        profile = system_obj.get_conceptual_parent()
+        if profile is None:
+            raise ValueError("Profile for system not found!")
+        distro = profile.get_conceptual_parent()
+        if distro is None:
+            raise ValueError("Distro for system not found!")
+
+        for interface in system_obj.interfaces.values():
+            mac = interface.mac_address
+            ip_address = interface.ip_address
+            host = interface.dns_name
+            ipv6 = interface.ipv6_address
+
+            if not mac:
+                # can't write a DHCP entry for this system
+                continue
+
+            # In many reallife situations there is a need to control the IP address and hostname for a specific
+            # client when only the MAC address is available. In addition to that in some scenarios there is a need
+            # to explicitly label a host with the applicable architecture in order to correctly handle situations
+            # where we need something other than ``pxelinux.0``. So we always write a dhcp-host entry with as much
+            # info as possible to allow maximum control and flexibility within the dnsmasq config.
+
+            systxt = "dhcp-host=net:" + distro.arch.value.lower() + "," + mac
+
+            if host != "":
+                systxt += "," + host
+
+            if ip_address != "":
+                systxt += "," + ip_address
+            if ipv6 != "":
+                systxt += f",[{ipv6}]"
+
+            systxt += "\n"
+
+            dhcp_tag = interface.dhcp_tag
+            if dhcp_tag == "":
+                dhcp_tag = "default"
+
+            if dhcp_tag not in system_definitions:
+                system_definitions[dhcp_tag] = ""
+
+            system_definitions[dhcp_tag] = system_definitions[dhcp_tag] + systxt
+
+        return system_definitions
+
+    def sync_single_system(self, system: "System"):
+        if not self.config:
+            # cache miss, need full sync for consistent data
+            return self.sync()
+
+        system_config = self._gen_system_config(system)
+        self.config = utils.merge_dicts_recursive(
+            self.config,
+            {"date": time.asctime(time.gmtime()), "system_definitions": system_config},
+            str_append=True,
+        )
+        self._write_configs(self.config)
+        return self.restart_service()
 
     def regen_ethers(self):
         """
         This function regenerates the ethers file. To get more information please read ``man ethers``, the format is
         also in there described.
         """
+        with open(ETHERS_FILE, "w", encoding="UTF-8") as ethers_fh:
+            for system in self.systems:
+                ethers_entry = self._gen_single_ethers_entry(system)
+                if ethers_entry:
+                    ethers_fh.write(ethers_entry)
+
+    def _gen_single_ethers_entry(
+        self,
+        system_obj: "System",
+    ):
+        if not system_obj.is_management_supported(cidr_ok=False):
+            self.logger.debug(
+                "%s does not meet precondition: MAC, IPv4, or IPv6 address is required.",
+                system_obj.name,
+            )
+            return
+
+        output = ''
+        for interface in system_obj.interfaces.values():
+            mac = interface.mac_address
+            ip_address = interface.ip_address
+            if not mac:
+                # can't write this w/o a MAC address
+                continue
+            if ip_address != "":
+                output += mac.upper() + "\t" + ip_address + "\n"
+        return output
+
+    def sync_single_ethers_entry(
+        self,
+        system_obj: "System",
+    ):
+        """
+        This adds a new single system entry to the ethers file.
+        """
         # dnsmasq knows how to read this database of MACs -> IPs, so we'll keep it up to date every time we add a
         # system.
-        fh = open("/etc/ethers", "w+")
-        for system in self.systems:
-            if not system.is_management_supported(cidr_ok=False):
-                continue
-            for interface in system.interfaces.values():
-                mac = interface.mac_address
-                ip = interface.ip_address
-                if not mac:
-                    # can't write this w/o a MAC address
-                    continue
-                if ip is not None and ip != "":
-                    fh.write(mac.upper() + "\t" + ip + "\n")
-        fh.close()
-
-    def regen_hosts(self):
+        with open(ETHERS_FILE, "a", encoding="UTF-8") as ethers_fh:
+            ethers_entry = self._gen_single_ethers_entry(system_obj)
+            if ethers_entry:
+                ethers_fh.write(ethers_entry)
+
+    def regen_hosts(self) -> None:
         """
         This rewrites the hosts file and thus also rewrites the dns config.
         """
         # dnsmasq knows how to read this database for host info (other things may also make use of this later)
-        fh = open("/var/lib/cobbler/cobbler_hosts", "w+")
-        for system in self.systems:
-            if not system.is_management_supported(cidr_ok=False):
+        with open(COBBLER_HOSTS_FILE, "w", encoding="UTF-8") as regen_hosts_fd:
+            for system in self.systems:
+                host_entry = self._gen_single_host_entry(system)
+                if host_entry:
+                    regen_hosts_fd.write(host_entry)
+
+    def _gen_single_host_entry(
+        self,
+        system_obj: "System",
+    ):
+        if not system_obj.is_management_supported(cidr_ok=False):
+            self.logger.debug(
+                "%s does not meet precondition: MAC, IPv4, or IPv6 address is required.",
+                system_obj.name,
+            )
+            return
+
+        output = ''
+        for (_, interface) in system_obj.interfaces.items():
+            mac = interface.mac_address
+            host = interface.dns_name
+            ipv4 = interface.ip_address
+            ipv6 = interface.ipv6_address
+            if not mac:
                 continue
-            for (_, interface) in system.interfaces.items():
-                mac = interface.mac_address
-                host = interface.dns_name
-                ip = interface.ip_address
-                ipv6 = interface.ipv6_address
-                if not mac:
-                    continue
-                if host is not None and host != "" and ipv6 is not None and ipv6 != "":
-                    fh.write(ipv6 + "\t" + host + "\n")
-                elif host is not None and host != "" and ip is not None and ip != "":
-                    fh.write(ip + "\t" + host + "\n")
-        fh.close()
-
-    def restart_service(self):
+            if host != "" and ipv6 != "":
+                output += ipv6 + "\t" + host + "\n"
+            elif host != "" and ipv4 != "":
+                output += ipv4 + "\t" + host + "\n"
+        return output
+
+    def sync_single_hosts_entry(
+        self,
+        system_obj: "System",
+    ):
+        """
+        This adds a single system entry to the hosts file
+        """
+        with open(COBBLER_HOSTS_FILE, "a", encoding="UTF-8") as regen_hosts_fd:
+            host_entry = self._gen_single_host_entry(system_obj)
+            if host_entry:
+                regen_hosts_fd.write(host_entry)
+
+    def restart_service(self) -> int:
         """
         This restarts the dhcp server and thus applied the newly written config files.
         """
         service_name = "dnsmasq"
         if self.settings.restart_dhcp:
-            return_code_service_restart = utils.service_restart(service_name)
+            return_code_service_restart = utils.process_management.service_restart(
+                service_name
+            )
             if return_code_service_restart != 0:
                 self.logger.error("%s service failed", service_name)
             return return_code_service_restart
diff --git a/cobbler/modules/managers/isc.py b/cobbler/modules/managers/isc.py
index eebb527918..be35f7cea5 100644
--- a/cobbler/modules/managers/isc.py
+++ b/cobbler/modules/managers/isc.py
@@ -22,11 +22,16 @@
 """
 import shutil
 import time
+from typing import TYPE_CHECKING, Any, Dict, Optional
 
 from cobbler import utils
 from cobbler.enums import Archs
 from cobbler.manager import ManagerModule
 
+if TYPE_CHECKING:
+    from cobbler.items.distro import Distro
+    from cobbler.items.system import System, NetworkInterface
+
 MANAGER = None
 
 
@@ -54,315 +59,263 @@ def __init__(self, api):
         self.settings_file_v4 = utils.dhcpconf_location(utils.DHCP.V4)
         self.settings_file_v6 = utils.dhcpconf_location(utils.DHCP.V6)
 
-    def write_v4_config(self, template_file="/etc/cobbler/dhcp.template"):
-        """
-        DHCPv4 files are written when ``manage_dhcp_v4`` is set in our settings.
+        self.config = {}  # cache config to allow adding systems incrementally
+        self.generic_entry_cnt = 0
 
-        :param template_file: The location of the DHCP template.
+    def sync_single_system(self, system: "System"):
         """
+        Update the config with data for a single system, write it to the filesysemt, and restart DHCP service.
+        :param system: System object to generate the config for.
+        """
+        if not self.config:
+            # cache miss, need full sync for consistent data
+            return self.sync()
+
+        profile = system.get_conceptual_parent()  # TODO: null-check
+        distro = profile.get_conceptual_parent()  # TODO: null-check
+        blend_data = utils.blender(self.api, False, system)
+
+        system_config = self._gen_system_config(system, blend_data, distro)
+        self.config = utils.merge_dicts_recursive(
+            self.config,
+            {"date": time.asctime(time.gmtime()), "dhcp_tags": system_config},
+        )
+        self._write_configs(self.config)
+        return self.restart_service()
+
+    def sync(self):
+        """
+        Generate full config from scratch, write it to the filesystem, and restart DHCP service.
+        """
+        # Reset generic counter and cached config
+        self.generic_entry_cnt = 0
+        self.config = self.gen_full_config()
+        self._write_configs(self.config)
+        return self.restart_service()
+
+    def _gen_system_config(
+        self,
+        system_obj: "System",
+        system_blend_data: Dict[str, Any],
+        distro_obj: Optional["Distro"],
+    ):
+        """
+        Generate DHCP config for a single system.
 
-        blender_cache = {}
-
-        with open(template_file, "r") as f2:
-            template_data = f2.read()
-
-        # Use a simple counter for generating generic names where a hostname is not available.
-        counter = 0
-
-        # We used to just loop through each system, but now we must loop through each network interface of each system.
+        :param system_obj: System to generate DHCP config for
+        :param system_blend_data: utils.blender() data for the System
+        :param distro_object: Optional, is used to access distro-specific information like arch when present
+        """
         dhcp_tags = {"default": {}}
-
-        # FIXME: ding should evolve into the new dhcp_tags dict
-        ding = {}
-        ignore_macs = []
-
-        for system in self.systems:
-            if not system.is_management_supported(cidr_ok=False):
+        processed_system_master_interfaces = set()
+        ignore_macs = set()
+        if not system_obj.is_management_supported(cidr_ok=False):
+            self.logger.debug(
+                "%s does not meet precondition: MAC, IPv4, or IPv6 address is required.",
+                system_obj.name,
+            )
+            return {}
+
+        for iface_name, iface_obj in system_obj.interfaces.items():
+            # TODO: move to own function
+            iface = iface_obj.to_dict()
+            mac = iface_obj.mac_address
+            if (
+                not self.settings.always_write_dhcp_entries
+                and not system_blend_data["netboot_enabled"]
+                and iface["static"]
+            ):
+                continue
+            if not mac:
+                self.logger.warning("%s has no MAC address", system_obj.name)
                 continue
 
-            profile = system.get_conceptual_parent()
-            distro = profile.get_conceptual_parent()
-
-            # if distro is None then the profile is really an image record
-            for (name, system_interface) in list(system.interfaces.items()):
-
-                # We make a copy because we may modify it before adding it to the dhcp_tags and we don't want to affect
-                # the master copy.
-                interface = system_interface.to_dict()
-
-                if interface["if_gateway"]:
-                    interface["gateway"] = interface["if_gateway"]
-                else:
-                    interface["gateway"] = system.gateway
-
-                mac = interface["mac_address"]
-
-                if interface["interface_type"] in ("bond_slave", "bridge_slave", "bonded_bridge_slave"):
-
-                    if interface["interface_master"] not in system.interfaces:
-                        # Can't write DHCP entry; master interface does not exist
-                        continue
-
-                    # We may have multiple bonded interfaces, so we need a composite index into ding.
-                    name_master = "%s-%s" % (system.name, interface["interface_master"])
-                    if name_master not in ding:
-                        ding[name_master] = {interface["interface_master"]: []}
-
-                    if len(ding[name_master][interface["interface_master"]]) == 0:
-                        ding[name_master][interface["interface_master"]].append(mac)
-                    else:
-                        ignore_macs.append(mac)
-
-                    ip = system.interfaces[interface["interface_master"]]["ip_address"]
-                    netmask = system.interfaces[interface["interface_master"]]["netmask"]
-                    dhcp_tag = system.interfaces[interface["interface_master"]]["dhcp_tag"]
-                    host = system.interfaces[interface["interface_master"]]["dns_name"]
-
-                    if ip is None or ip == "":
-                        for (interface_name, interface_object) in list(system.interfaces.items()):
-                            if interface_name.startswith(interface["interface_master"] + ".") \
-                                    and interface_object.ip_address is not None \
-                                    and interface_object.ip_address != "":
-                                ip = interface_object.ip_address
-                                break
-
-                    interface["ip_address"] = ip
-                    interface["netmask"] = netmask
-                else:
-                    ip = interface["ip_address"]
-                    netmask = interface["netmask"]
-                    dhcp_tag = interface["dhcp_tag"]
-                    host = interface["dns_name"]
-
-                if distro is not None:
-                    interface["distro"] = distro.to_dict()
-
-                if mac is None or mac == "":
-                    # can't write a DHCP entry for this system
+            iface["gateway"] = iface_obj.if_gateway or system_obj.gateway
+            if iface["interface_type"] in (
+                "bond_slave",
+                "bridge_slave",
+                "bonded_bridge_slave",
+            ):
+                if iface["interface_master"] not in system_obj.interfaces:
+                    # Can't write DHCP entry: master interface does not exist
                     continue
 
-                counter = counter + 1
-
-                # the label the entry after the hostname if possible
-                if host is not None and host != "":
-                    if name != "eth0":
-                        interface["name"] = "%s-%s" % (host, name)
-                    else:
-                        interface["name"] = "%s" % host
+                master_name = iface["interface_master"]
+                master_iface = system_obj.interfaces[master_name]
+                # There may be multiple bonded interfaces, need composite index
+                system_master_name = f"{system_obj.name}-{master_name}"
+                if system_master_name not in processed_system_master_interfaces:
+                    processed_system_master_interfaces.add(system_master_name)
                 else:
-                    interface["name"] = "generic%d" % counter
-
-                # add references to the system, profile, and distro for use in the template
-                if system.name in blender_cache:
-                    blended_system = blender_cache[system.name]
+                    ignore_macs.add(mac)
+                # IPv4
+                iface["netmask"] = master_iface.netmask
+                iface["ip_address"] = master_iface.ip_address
+                if not iface["ip_address"]:
+                    iface["ip_address"] = self._find_ip_addr(
+                        system_obj.interfaces, prefix=master_name, ip_version="ipv4"
+                    )
+                # IPv6
+                iface["ipv6_address"] = master_iface.ipv6_address
+                if not iface["ipv6_address"]:
+                    iface["ipv6_address"] = self._find_ip_addr(
+                        system_obj.interfaces, prefix=master_name, ip_version="ipv6"
+                    )
+                # common
+                host = master_iface.dns_name
+                dhcp_tag = master_iface.dhcp_tag
+            else:
+                # TODO: simplify _slave / non_slave branches
+                host = iface["dns_name"]
+                dhcp_tag = iface["dhcp_tag"]
+
+            if distro_obj is not None:
+                iface["distro"] = distro_obj.to_dict()
+            if host:
+                if iface_name == "eth0":
+                    iface["name"] = host
                 else:
-                    blended_system = utils.blender(self.api, False, system)
-                    blender_cache[system.name] = blended_system
-
-                interface["next_server_v4"] = blended_system["next_server_v4"]
-                interface["filename"] = blended_system.get("filename")
-                interface["netboot_enabled"] = blended_system["netboot_enabled"]
-                interface["hostname"] = blended_system["hostname"]
-                interface["owner"] = blended_system["name"]
-                interface["enable_ipxe"] = blended_system["enable_ipxe"]
-                interface["name_servers"] = blended_system["name_servers"]
-                interface["mgmt_parameters"] = blended_system["mgmt_parameters"]
-
-                # Explicitly declare filename for other (non x86) archs as in DHCP discover package mostly the
-                # architecture cannot be differed due to missing bits...
-                if distro is not None and not interface.get("filename"):
-                    if distro.arch in [Archs.PPC, Archs.PPC64, Archs.PPC64LE, Archs.PPC64EL]:
-                        interface["filename"] = "grub/grub.ppc64le"
-                    elif distro.arch == Archs.AARCH64:
-                        interface["filename"] = "grub/grubaa64.efi"
-
-                if not self.settings.always_write_dhcp_entries:
-                    if not interface["netboot_enabled"] and interface['static']:
-                        continue
-
-                if dhcp_tag == "":
-                    dhcp_tag = blended_system.get("dhcp_tag", "")
-                    if dhcp_tag == "":
-                        dhcp_tag = "default"
-
-                if dhcp_tag not in dhcp_tags:
-                    dhcp_tags[dhcp_tag] = {
-                        mac: interface
-                    }
-                else:
-                    dhcp_tags[dhcp_tag][mac] = interface
-
-        # Remove macs from redundant slave interfaces from dhcp_tags otherwise you get duplicate ip's in the installer.
-        for dt in list(dhcp_tags.keys()):
-            for m in list(dhcp_tags[dt].keys()):
-                if m in ignore_macs:
-                    del dhcp_tags[dt][m]
-
-        # we are now done with the looping through each interface of each system
-        metadata = {
-            "date": time.asctime(time.gmtime()),
-            "cobbler_server": "%s:%s" % (self.settings.server, self.settings.http_port),
-            "next_server_v4": self.settings.next_server_v4,
-            "dhcp_tags": dhcp_tags
-        }
-
-        self.logger.info("generating %s", self.settings_file_v4)
-        self.templar.render(template_data, metadata, self.settings_file_v4)
-
-    def write_v6_config(self, template_file="/etc/cobbler/dhcp6.template"):
-        """
-        DHCPv6 files are written when ``manage_dhcp_v6`` is set in our settings.
-
-        :param template_file: The location of the DHCP template.
-        """
-
-        blender_cache = {}
-
-        with open(template_file, "r") as f2:
-            template_data = f2.read()
-
-        # Use a simple counter for generating generic names where a hostname is not available.
-        counter = 0
-
-        # We used to just loop through each system, but now we must loop through each network interface of each system.
+                    iface["name"] = f"{host}-{iface_name}"
+            else:
+                self.generic_entry_cnt += 1
+                iface["name"] = f"generic{self.generic_entry_cnt:d}"
+
+            for key in (
+                "next_server_v6",
+                "next_server_v4",
+                "filename",
+                "netboot_enabled",
+                "hostname",
+                "enable_ipxe",
+                "name_servers",
+            ):  # TODO: what if they don't exist? original code has .get("filename"), but shouldn't be needed?
+                iface[key] = system_blend_data[key]
+            iface["owner"] = system_blend_data["name"]
+            # esxi
+            if distro_obj is not None and distro_obj.os_version.startswith("esxi"):
+                iface["filename_esxi"] = (
+                    "esxi/system",
+                    # config filename can be None
+                    system_obj.get_config_filename(interface=iface_name, loader="pxe")
+                    or "",
+                    "mboot.efi",
+                )
+            elif distro_obj is not None and not iface["filename"]:
+                if distro_obj.arch in (
+                    Archs.PPC,
+                    Archs.PPC64,
+                    Archs.PPC64LE,
+                    Archs.PPC64EL,
+                ):
+                    iface["filename"] = "grub/grub.ppc64le"
+                elif distro_obj.arch == Archs.AARCH64:
+                    iface["filename"] = "grub/grubaa64.efi"
+
+            if not dhcp_tag:
+                dhcp_tag = system_blend_data.get("dhcp_tag", "default")
+            if dhcp_tag not in dhcp_tags:
+                dhcp_tags[dhcp_tag] = {mac: iface}
+            else:
+                dhcp_tags[dhcp_tag][mac] = iface
+
+        for macs in dhcp_tags.values():
+            for mac in list(macs.keys()):
+                if mac in ignore_macs:
+                    del macs[mac]
+
+        return dhcp_tags
+
+    def _find_ip_addr(
+        self,
+        interfaces: Dict[str, "NetworkInterface"],
+        prefix: str,
+        ip_version: str,
+    ) -> str:
+        """Find the first interface with an IP address that begins with prefix."""
+
+        if ip_version.lower() == "ipv4":
+            attr_name = "ip_address"
+        elif ip_version.lower() == "ipv6":
+            attr_name = "ipv6_address"
+        else:
+            return ""
+
+        for name, obj in interfaces:
+            if name.startswith(prefix + ".") and hasattr(obj, attr_name):
+                return getattr(obj, attr_name)
+        return ""
+
+    def gen_full_config(self):
+        """Generate DHCP configuration for all systems."""
         dhcp_tags = {"default": {}}
-
-        # FIXME: ding should evolve into the new dhcp_tags dict
-        ding = {}
-        ignore_macs = []
-
+        self.generic_entry_cnt = 0
         for system in self.systems:
-            if not system.is_management_supported(cidr_ok=False):
-                continue
-
             profile = system.get_conceptual_parent()
-            distro = profile.get_conceptual_parent()
-
-            # if distro is None then the profile is really an image record
-            for (name, system_interface) in list(system.interfaces.items()):
-
-                # We make a copy because we may modify it before adding it to the dhcp_tags and we don't want to affect
-                # the master copy.
-                interface = system_interface.to_dict()
-
-                if interface["if_gateway"]:
-                    interface["gateway"] = interface["if_gateway"]
-                else:
-                    interface["gateway"] = system.gateway
-
-                mac = interface["mac_address"]
-
-                if interface["interface_type"] in ("bond_slave", "bridge_slave", "bonded_bridge_slave"):
-
-                    if interface["interface_master"] not in system.interfaces:
-                        # Can't write DHCP entry; master interface does not exist
-                        continue
-
-                    # We may have multiple bonded interfaces, so we need a composite index into ding.
-                    name_master = "%s-%s" % (system.name, interface["interface_master"])
-                    if name_master not in ding:
-                        ding[name_master] = {interface["interface_master"]: []}
-
-                    if len(ding[name_master][interface["interface_master"]]) == 0:
-                        ding[name_master][interface["interface_master"]].append(mac)
-                    else:
-                        ignore_macs.append(mac)
-
-                    ip_v6 = system.interfaces[interface["interface_master"]]["ipv6_address"]
-                    dhcp_tag = system.interfaces[interface["interface_master"]]["dhcp_tag"]
-                    host = system.interfaces[interface["interface_master"]]["dns_name"]
-
-                    if not ip_v6:
-                        for (interface_name, interface_object) in list(system.interfaces.items()):
-                            if interface_name.startswith(interface["interface_master"] + ".") \
-                                    and interface_object.ipv6_address is not None \
-                                    and interface_object.ipv6_address != "":
-                                ip_v6 = interface_object.ipv6_address
-                                break
-
-                    interface["ipv6_address"] = ip_v6
-                else:
-                    ip_v6 = interface["ipv6_address"]
-                    dhcp_tag = interface["dhcp_tag"]
-                    host = interface["dns_name"]
-
-                if distro is not None:
-                    interface["distro"] = distro.to_dict()
-
-                if not mac or not ip_v6:
-                    # can't write a DHCP entry for this system
-                    self.logger.warning("%s has no IPv6 or MAC address", system.name)
-                    continue
-                counter = counter + 1
-
-                # the label the entry after the hostname if possible
-                if host:
-                    if name != "eth0":
-                        interface["name"] = "%s-%s" % (host, name)
-                    else:
-                        interface["name"] = "%s" % host
-                else:
-                    interface["name"] = "generic%d" % counter
-
-                # add references to the system, profile, and distro for use in the template
-                if system.name in blender_cache:
-                    blended_system = blender_cache[system.name]
-                else:
-                    blended_system = utils.blender(self.api, False, system)
-                    blender_cache[system.name] = blended_system
-
-                interface["next_server_v6"] = blended_system["next_server_v6"]
-                interface["filename"] = blended_system.get("filename")
-                interface["netboot_enabled"] = blended_system["netboot_enabled"]
-                interface["hostname"] = blended_system["hostname"]
-                interface["owner"] = blended_system["name"]
-                interface["name_servers"] = blended_system["name_servers"]
-                interface["mgmt_parameters"] = blended_system["mgmt_parameters"]
-
-                # Explicitly declare filename for other (non x86) archs as in DHCP discover package mostly the
-                # architecture cannot be differed due to missing bits...
-                if distro is not None and not interface.get("filename"):
-                    if distro.arch == Archs.PPC:
-                        interface["filename"] = "grub/grub.ppc"
-                    elif distro.arch == Archs.PPC64:
-                        interface["filename"] = "grub/grub.ppc64"
-                    elif distro.arch == Archs.PPC64LE:
-                        interface["filename"] = "grub/grub.ppc64le"
-                    elif distro.arch == Archs.AARCH64:
-                        interface["filename"] = "grub/grubaa64.efi"
-
-                if not self.settings.always_write_dhcp_entries:
-                    if not interface["netboot_enabled"] and interface['static']:
-                        continue
-
-                if dhcp_tag == "":
-                    dhcp_tag = blended_system.get("dhcp_tag", "")
-                    if dhcp_tag == "":
-                        dhcp_tag = "default"
-
-                if dhcp_tag not in dhcp_tags:
-                    dhcp_tags[dhcp_tag] = {
-                        mac: interface
-                    }
-                else:
-                    dhcp_tags[dhcp_tag][mac] = interface
-
-        # Remove macs from redundant slave interfaces from dhcp_tags otherwise you get duplicate ip's in the installer.
-        for dt in list(dhcp_tags.keys()):
-            for m in list(dhcp_tags[dt].keys()):
-                if m in ignore_macs:
-                    del dhcp_tags[dt][m]
+            if profile is None:
+                continue
+            distro: Optional[Distro] = profile.get_conceptual_parent()  # type: ignore
+            blended_system = utils.blender(self.api, False, system)
+            new_tags = self._gen_system_config(system, blended_system, distro)
+            dhcp_tags = utils.merge_dicts_recursive(dhcp_tags, new_tags)
 
-        # we are now done with the looping through each interface of each system
         metadata = {
             "date": time.asctime(time.gmtime()),
+            "cobbler_server": f"{self.settings.server}:{self.settings.http_port}",
+            "next_server_v4": self.settings.next_server_v4,
             "next_server_v6": self.settings.next_server_v6,
-            "dhcp_tags": dhcp_tags
+            "dhcp_tags": dhcp_tags,
         }
+        return metadata
+
+    def _write_config(
+        self,
+        config_data,
+        template_file,
+        settings_file,
+    ) -> None:
+        """DHCP files are written when ``manage_dhcp_v4`` or ``manage_dhcp_v6``
+        is set in the settings for the respective version. DHCPv4 files are
+        written when ``manage_dhcp_v4`` is set in our settings.
+
+        :param config_data:
+        :param template_file: The location of the DHCP template.
+        """
+        try:
+            with open(template_file, "r", encoding="UTF-8") as template_fd:
+                template_data = template_fd.read()
+        except OSError as e:
+            self.logger.error("Can't read dhcp template '%s':\n%s", template_file, e)
+            return None
+        config_copy = config_data.copy() # template rendering changes the passed dict
+        self.logger.info("Writing %s", settings_file)
+        self.templar.render(template_data, config_copy, settings_file)
+
+    def write_v4_config(
+        self, config_data=None, template_file="/etc/cobbler/dhcp.template"
+    ):
+        if not config_data:
+            raise ValueError("No config to write.")
+        self._write_config(config_data, template_file, self.settings_file_v4)
+
+    def write_v6_config(
+        self, config_data=None, template_file="/etc/cobbler/dhcp6.template"
+    ):
+        if not config_data:
+            raise ValueError("No config to write.")
+        self._write_config(config_data, template_file, self.settings_file_v6)
+
+    def _write_configs(self, config_data=None) -> None:
+        if not config_data:
+            raise ValueError("No config to write.")
+
+        if self.settings.manage_dhcp_v4:
+            self.write_v4_config(config_data)
+        if self.settings.manage_dhcp_v6:
+            self.write_v6_config(config_data)
 
-        if self.logger is not None:
-            self.logger.info("generating %s" % self.settings_file_v6)
-        self.templar.render(template_data, metadata, self.settings_file_v6)
+    def write_configs(self) -> None:
+        data = self.gen_full_config()
+        self._write_configs(data)
 
     def restart_dhcp(self, service_name: str) -> int:
         """
@@ -380,12 +333,6 @@ def restart_dhcp(self, service_name: str) -> int:
             self.logger.error("{} service failed".format(service_name))
         return return_code_service_restart
 
-    def write_configs(self):
-        if self.settings.manage_dhcp_v4:
-            self.write_v4_config()
-        if self.settings.manage_dhcp_v6:
-            self.write_v6_config()
-
     def restart_service(self) -> int:
         if not self.settings.restart_dhcp:
             return 0
diff --git a/cobbler/utils.py b/cobbler/utils.py
index 4bef0e8123..bff07e4150 100644
--- a/cobbler/utils.py
+++ b/cobbler/utils.py
@@ -2091,3 +2091,27 @@ def is_str_float(value: str) -> bool:
     except ValueError:
         pass
     return False
+
+
+def merge_dicts_recursive(base_dict, updating_dict, str_append=False) -> Dict:
+    """Merge updating_dict into base_config recursively.
+    :param base_dict: Base dictionary.
+    :param updating_dict: Updating dict, overrides base_dict.
+    :returns dict: Merged dict"""
+    ret = base_dict.copy()
+    for k, v in updating_dict.items():
+        if (
+            k in base_dict
+            and isinstance(v, dict)
+            and isinstance(base_dict.get(k), dict)
+        ):
+            ret[k] = merge_dicts_recursive(base_dict[k], v)
+        elif (
+            str_append
+            and k in ret
+            and isinstance(v, str)
+        ):
+            ret[k] += v
+        else:
+            ret[k] = v
+    return ret

