Setting up dnsmasq - a lightweight DHCP and DNS server

Peter Boy, Emmmanuel Seyman Version F35-F44 Last review: 2026-05-11
The Fedora Server Edition recommends using the lightweight dnsmasq program to provide a server and a small to medium-sized local network with DHCP, DDNS and DNS caching services. Fedora Server has already preconfigured it as a NetworkManager plugin to ensure seamless integration of the components.

Status: checked and ready for final review (2026-05-11)

Introduction

By default, Fedora Server uses dnsmasq to provide local DNS and DHCP services for private or public subnets. It is preconfigured as a NetworkManager plug-in to ensure seamless integration of the components.

The DHCP component provides dynamic DNS for a DHCP-assigned IP address, offering a temporary DNS entry for the device’s hostname. It also supports static hostnames. Another common use case is to provide DHCP for a public subdomain, while an official public DNS server provides name resolution for the subdomain. Devices with such an address cannot, of course, be found via DNS. These addresses are primarily used for initial system installation, network-supported booting (PXE), and for dynamically assigning a specific IP address to machines identified by their MAC address. Another capability is providing a DNS caching service. However, since release 33, Fedora has used systemd-resolved as the DNS client, which includes versatile caching. Therefore, dnsmasq is no longer required for this purpose.

Each dnsmasq component is optional. A system can use the DHCP component alone, the DNS component alone, the DHCP caching component alone, or any combination of these. Each component is configured separately.

The target is a small to medium-sized subnet. Typically, a server performs this task as an additional responsibility, alongside its main duties. It is practically impossible to determine the upper limit with any degree of accuracy. However, as a rule of thumb, dnsmasq can handle over 100 machines with ease. Significantly larger networks require better management and structuring capabilities. In this case, Kea, the ISC DHCP Server would be a more suitable choice.

For additional information, see the Fedora Magazine article Using the NetworkManager’s DNSMasq plugin (2019).

Prerequisites

All the necessary interfaces have been installed and fully configured. This includes assigning the correct firewall zones.

[…]# firewall-cmd --permanent --zone=<zone_name> --change-interface=<interface_name>
[…]# firewall-cmd --reload

The system should automatically forward between the interfaces. Check the forwarding status and adjust it if necessary.

[…]# cat /proc/sys/net/ipv4/ip_forward
[…]# cat /proc/sys/net/ipv6/conf/default/forwarding

In both cases a value of 1 indicates an active forwarding.

Otherwise, enable it immediately and configure it permanently.

[…]# echo 1 > /proc/sys/net/ipv4/ip_forward
[…]# echo 1 > /proc/sys/net/ipv6/conf/all/forwarding

[…]# vim /etc/sysctl.d/50-enable-forwarding.conf
# local customizations
#
# enable forwarding for dual stack
net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1

Installation

The NetworkManager dnsmasq plugin included by default provides a basic configuration skeleton, but does not install the dnsmasq package. Thus, it avoids to uselessly occupy space and to introduce a superfluous and unused binary in case dnsmasq is not going to be in use on the particular server.

In case dnsmasq is not already installed

 […]# dnf install dnsmasq

Do not use systemctl directly on dnsmasq! It is used as a NetworkManager plugin, therefore NetworkManager starts and manages dnsmasq and adjusts resolv.conf accordingly. It uses its own set of parameters and ignores the packages' configuration file /etc/dnsmasq.conf.

Calling systemctl directly would be ineffective and would rather start yet another dnsmasq instance, which leads to conflicts.

Basic configuration

NetworkManager takes care of the dnsmasq plugin operation. Configuration files in the /etc/NetworkManager/dnsmasq.d directory specify the custom configuration requirements, preferably one configuration file per task. The only exception in this example is the file containing the IP - hostname mapping of static DNS names, /etc/dnsmasq.hosts.

NetworkManager reads all files in that directory, independantly of the file extension. So you can’t temporarily deactivate a configuration by renaming it.

The example here uses 2 interfaces, an external public interface enp1s0 (public.tld) and an internal private interface enp2s0 (internal.lan). You may add any number of additional interfaces by adding corresponding config files as in the examples here.

  1. Activate the dnsmasq NetworkManager plugin

     […]# vim /etc/NetworkManager/conf.d/00-use-dnsmasq.conf
    
     # /etc/NetworkManager/conf.d/00-use-dnsmasq.conf
     # This enables the dnsmasq plugin.
     [main]
     dns=dnsmasq
  2. Configuration of the name resolution (DNS) for the internal private network (internal.lan)

     […]# vim /etc/NetworkManager/dnsmasq.d/01-DNS-<INTERNAL>.conf
    
     # /etc/NetworkManager/dnsmasq.d/01-DNS-<INTERNAL>.conf
     # This file sets up DNS for the private local net domain '<INTERNAL>.lan'
     local=/<INTERNAL>.lan/
     # file where to find the list of IP - hostname mapping
     addn-hosts=/etc/dnsmasq-<INTERNAL>.hosts
    
     domain-needed
     bogus-priv
    
     # Automatically add <domain> to simple names in a hosts-file.
     expand-hosts
    
     # interfaces to listen on
     interface=lo
     interface=<ENPxyz>
     # in case of a bridge don't use the attached server virtual ethernet interface here!
    
     # Upstream public net DNS server (max.three)
     no-poll
     server=<uuu.vv.xx.yy>
     server=<www.vv.xx.zz>
     server=<2001:www:xxx:yyy::zz>

    Provide an empty host file

    […]# touch dnsmasq-<INTERNAL>.hosts
  3. Configuration of the DHCP service for the internal private network (<INTERNAL>.lan)

     […]# vim /etc/NetworkManager/dnsmasq.d/02-DHCP-<INTERNAL>.conf
     # etc/NetworkManager/dnsmasq.d/02-DHCP-<INTERNAL>.conf
     # This file sets up DHCP for the private local net domain '<INTERNAL>.lan'
    
     # The domain the DHCP part of dnsmasq is responsible for:
     domain=<INTERNAL>.lan,<uuu.vv.xx.y/24>,local
    
     # interfaces to listen on (redundant, same as for DNS)
     interface=<ENPxyz>
    
     # general DHCP stuff (options, see RFC 2132)
     # 1: subnet masq
     # 3: default router
     # 6: DNS server
     # 12: hostname
     # 15: DNS domain (unneeded with option 'domain')
     # 28: broadcast address
    
     dhcp-authoritative
     dhcp-option=1,<255.255.255.24>
     dhcp-option=3,<www.xxx.yy.zz>
     dhcp-option=6,<www.xx.yy.z>
    
     # Assign fixed IP addresses based on MAC address
     # dhcp-host=00:1a:64:ce:89:4a,NAME01,www.xx.yy.zz,infinite
     # dhcp-host=52:54:00:42:6a:43,NAME02,www.xx.yy.zz,infinite
    
     # Assign dynamically IP addresses to interface to listen on
     # Range for distributed addresses, tagged <int> for further references
     dhcp-range=tag:<ENPxyz>,<vvv.ww.xx.y,vvv.ww.xx.z>,24h
  4. Configuration of the DHCP service for the public network (<PUBLIC.TLD>)

    […]# vim /etc/NetworkManager/dnsmasq.d/03-DHCP-<PUBLIC>.conf
     # etc/NetworkManager/dnsmasq.d/03-DHCP-<PUBLIC>.conf
     # This file sets up DNCP for the public '<PUBLIC.TLD>' domain interface
    
     # The domain the DHCP part of dnsmasq is responsible for:
     domain=<PUBLIC.TLD>,<uuu.vv.ww.xx/24>
    
     # the public interfaces to listen on
     interface=<ENPuvw>
    
     # general DHCP stuff (options, see RFC 2132)
     # 1: subnet masq
     # 3: default router
     # 6: DNS server
     # 12: hostname
     # 15: DNS domain (unneeded with option 'domain')
     # 28: broadcast address
    
     ##dhcp-authoritative
     ## we just send the bare minimum, e.g. no DNS server
     ##dhcp-option=1,<255.255.255.0>
     dhcp-option=tag:<ENPuvw>,option=router,<uuu.vv.ww.zz>
    
     # Assign fixed IP addresses based on MAC address
     # dhcp-host=00:1a:64:ce:89:4a,thootes,10.10.10.50,infinite
     # dhcp-host=52:54:00:42:6a:43,apollon,10.10.10.51,infinite
     # Assign dynamically IP addresses to interface to listen on
     # Range for distributed addresses, tagged <int> for further references dhcp-range=tag:<ENPuvw>,<uuu.vvv.w.x,uuu.vvv.w.y6,1h

    There is no DNS configuration for the external interface following, assuming that a official public DNS server is used to resolve all public facing interfaces of the domain public.tld.

  5. Test the dnsmasq configuration

    […]# dnsmasq --test
  6. Adjusting the firewall

    Allow ports for DHCP and DNS (53) service on the public interface.

     […]# firewall-cmd --get-services
     […]# firewall-cmd --zone=<YOUR_ZONE> --permanent --add-service=dhcp
     […]# firewall-cmd --zone=<YOUR_ZONE> --permanent --add-service=dns
     […]# firewall-cmd --reload
     […]# firewall-cmd --list-all
  7. Restart NetworkManager to start dnsmasq

    […]# systemctl restart NetworkManager
    […]# ps -ef | grep dnsmasq

    NetworkManager should have started dnsmasq shown by the 'ps' command above.

  8. Restart systemd-resolved

    […]# systemctl restart systemd-resolved
    […]# resolvectl status

    The systemd-resolved should recognize the dnsmasq nameserver attached to interfaces as configured.

  9. Test the installation

    1. Test DHCP in the public using a machine without IP address

       […]# ip a   # no IPv4 address associated with interface
       […]# dhclient -4 -1 -v eth0
       […]# ip a  # expect new IPv4 address associated with interface
       […]# dhclient -4 -1 -r -v eth0  # expected: no IPv4 again
       […]# ip a  # expect no IPv4 address associated with interface again
    2. Try on an other server

       […]# dig app1 @10.10.10.1
       […]# nslookup app1 10.10.10.1
       […]# dhclient -v -d -s 10.10.10.1 enp6s0

Masquerading / NAT

If machines in the private network need access to the public network, add masquerading / NAT to the firewall.

  1. Enabling masquerading for the public zone and for the internal (trusted) trusted zone

    […]# firewall-cmd --zone=FedoraServer --add-masquerade --permanent
    success
    […]# firewall-cmd --zone=trusted --add-masquerade --permanent
    success
    […]# firewall-cmd --reload
    
    […]# firewall-cmd --zone=FedoraServer --query-masquerade
    yes
    […]# firewall-cmd --zone=trusted --query-masquerade
    yes
  2. Allowing forwarding from the internal, private network to the external interface and further to the public network.

    1. A commonly used way to accomplish this is to set 'rules' in the firewall configuration. Corresponding tutorials are very widespread. And those who are familiar with it may want to continue using it.

      […]# firewall-cmd --get-active-zones
      FedoraServer
          interfaces: enp1s0
      trusted
          interfaces: vbr2s0 enp2s0
      […]# firewall-cmd --direct --add-rule ipv4 nat POSTROUTING 0 -o enp1s0 -j MASQUERADE
      success
      […]# firewall-cmd --direct --add-rule ipv4 filter FORWARD 0 -i vbr2s0 -o enp2s0 -j ACCEPT
      success
      […]# firewall-cmd --direct --add-rule ipv4 filter FORWARD 0 -i enp1s0 -o vbr2s0 -m state --state RELATED,ESTABLISHED -j ACCEPT
      success
    2. Fedora’s firewall daemon, however, offers with release 35 and beyond a more elegant option, so-called 'policies'. These abstract typical targets previously configured by rules.

      […]# firewall-cmd --get-active-zones
      FedoraServer
          interfaces: enp1s0
      trusted
          interfaces: vbr2s0 enp2s0
      […]# firewall-cmd --permanent --new-policy trustedToExt
        success
      […]# firewall-cmd --permanent --policy trustedToExt --add-ingress-zone trusted
        success
      […]# firewall-cmd --permanent --policy trustedToExt --add-egress-zone FedoraServer
        success
      […]# firewall-cmd --permanent --policy trustedToExt --set-target ACCEPT
        success
      […]# firewall-cmd --reload
        success

      This method is much clearer, improves maintainability and reduces sources of potential errors. The documentation of the upstream project provides more information.

Integrate libvirt’s virtual interface

In case libvirt and virtualization including a virtual network for the virtual machines, libvirt installs and configures its own dnsmasq instance. In most cases it is just convenient, instead of replacing the libvirt default network to integrate it in NetworkManagers dnsmasq plugin. Thus, two instances of dnsmasq operate along each other.

To make it work, just add another configuration file. The example uses libvirt.lan as the libvirt virtual network domain name. Adjust as appropriate.

We just add the name resolution (DNS) for the libvirt virtual network (libvirt.lan), leaving the DHCP functionality untouched.

 […]# vim /etc/NetworkManager/dnsmasq.d/30-DNS-libvirt.conf

 # /etc/NetworkManager/dnsmasq.d/30-DNS-libvirt.conf

 # This file directs dnsmasq to forward any request to resolve
 # names under the .libvirt.lan domain to 192.168.122.1, the
 # local libvirt DNS server default address.
 server=/libvirt.lan/192.168.122.1

Managing static DNS Entries

  1. Edit the dnsmasq host file

    The format is the same as /etc/hosts .

    […]# vim /etc/dnsmasq.hosts
  2. Restart NetworkManager to read the modified file.

    […]# systemctl restart NetworkManager
  3. Test the modification

    […]# nslookup {NAME}
    […]# nslookup {NAME}.example.lan