How to Set Up a Local DNS Server with Dnsmasq in Ubuntu 24.04 (Fast & Easy)

When you set up local machines for testing purposes, you might be familiar with editing /etc/hosts on each machine so that you can use domains instead of remembering IP addresses. That seems to be frustrated, right? Whether you’re running a home lab, managing an internal office network, or building dev environments. Dnsmasq might be exactly what you need.

In this guide, I’ll walk you through setting up Dnsmasq as a local DNS server — with examples for dev machines, Docker, and more.

What is Dnsmasq?

You can easily find the definition on the internet. Basically, Dnsmasq is just a free software that provides DNS, DHCP, PXE, and TFTP services for a server to run on a small to medium-sized private network.

Real-world Use Cases:

It might be ideal for developers, sysadmins, or anyone interested in technology who needs a quick solution for a home lab, local testing, DHCP, DNS caching, etc.

Even if your system is running in production, such as on AWS services like EC2 or EKS, and relies on Route53 or other DNS providers that are generally reliable, there are still situations where having a local DNS server within your private network is valuable. In rare but critical cases, your domains may become unreachable due to unexpected issues. These could include domains temporarily expiring because of missed billing, or administrative errors by your DNS provider that result in your domains being placed on hold for several days. I’ve encountered these kinds of real-world scenarios firsthand, and that’s when I feel a local DNS server becomes essential, as it becomes a backup solution, and makes the operation and other servers continue working without being broken. In a normal situation, maybe it just helps speed up DNS resolution, but I think it would be good to have it in play as a backup option.

Why it’s still relevant in 2025

I like Dnsmasq because of its lightweight nature, flexibility, and simplicity. You can quickly run a setup on your local network, which provides rich enough functions for you to use. Even though there are a lot of other modern alternatives that exist, I think Dnsmasq just works out of the box without too much learning curve, and the most important thing is that it solves our problems quickly. For example, we just need a lightweight DNS running locally. That makes Dnsmasq stand out from other modern tools and makes it a reliable choice.

Comparison: Dnsmasq vs BIND, systemd-resolved.

We have a lot of DNS solutions on the market, I will compare Dnsmasq with other 2 popular solutions which are BIND and systemd-resolved.

dnsmasqBINDsystemd-resolved
Primary functionsDNS and DHCP services A full-fledged DNS serverDNS stub resolution, LLMNR/MulticastDNS resolution
Suitable forSmall to medium-sizedLarge networks, production environments, and complex DNS configurationsModern Linux systems, integrates with systemd
Key featuresLightweight, easy to configure, supports local DNS records, can be used as DHCPRobust, configurable, and has a large featureDNS stub resolver, DNS over TLS/DNSSEC support (more security), integrates well with systemd
Use caseshomelab, local testingA corporate network with multiple subdomainsis designed to be the default resolver on modern Linux systems

Set up Dnsmasq on Ubuntu 24.04

Prepares a Ubuntu 24.04:

HostnameIP addressRole
web-01.srv.local192.168.68.116DNS server

If you prefer to use Docker instead, please check out section 2 below. However, it’s still good to read the explanation below for more understanding.

Installation

Since 18.04 enables systemd-resolved service by default. This service runs on 127.0.0.1:53 and occupies port 53, which prevents you from starting dnsmasq service. We need to turn systemd-resolved service off before installing dnsmasq. I’ll turn this web-01.srv.local server into a DNS server. On web-01 server, run:

sudo systemctl disable systemd-resolved
sudo systemctl stop systemd-resolved

# Remove default config managed by systemd-resolved
sudo unlink /etc/resolv.conf

# Create a new /etc/resolv.conf file. Set localhost as nameserver makes all queries to be sent to dnsmasq
echo "nameserver 127.0.0.1" | sudo tee /etc/resolv.conf

And then, run:

sudo apt update
sudo apt install dnsmasq

Configurations:

  • Location of the main config file: /etc/dnsmasq.conf
  • Recommended: split the config into /etc/dnsmasq.d/ (modular setup)

I would recommend creating a separate config in /etc/dnsmasq.d/ so that we don’t touch the main config file. Here is a basic configuration that makes dnsmasq work.

Run sudo nano /etc/dnsmasq.d/my-custom-dns.conf and add the following content:

# no-resolv means dnsmasq does not needlessly read /etc/resolv.conf 
# which only contains the localhost addresss of itself
no-resolv

# Set dnsmasq to use Google Public DNS. You could also replace it 
# with a DNS resolver you trust.
# Note: if you don't set this "server" parameter, you may not connect to 
# the internet and just be able to talk to other servers in local network.
# The reason because DNS queries will be resolved with dnsmasq, only 
# checking external servers if it cannot answer the query from its cache.
server=8.8.8.8

# Never forward plain names (without a dot or domain part) domain-needed
# Never forward addresses in the non-routed address spaces
bogus-priv

# (Optional): If don't want to use /etc/hosts, specify your custom here.
# addn-hosts=/path/to/your/etc/hosts

# Add other name servers here, with domain specs if they are for non-public domains.
# This force your local domain to use nameservers with IP address(es) defined here.
address=/srv.local/127.0.0.1
address=/srv.local/192.168.68.116


# Uncomment expand-hosts to add the custom domain to hosts entries:
# Without this setting, you will have to add the domain to entries of /etc/hosts
# E.g. if expand-hosts is set, you define /etc/hosts with:
#    192.168.1.10 test
# "test" will be added with domain part ".srv.local" -> "test.srv.local"
# if expand-hosts is not set, you define /etc/hosts with: 
#    192.168.1.10 test.srv.local
expand-hosts

# Provides the domain part for "expand-hosts". 
# As I want all machines in my "local" network has domain "srv.local". 
# I've enabled here. If you don't use domain, just comments it.
domain=srv.local

# Normally responses which come from /etc/hosts and the DHCP lease
# file have Time-To-Live set as zero. We can also extend time-to-live (in seconds) here
local-ttl=15

Press Ctrl + O and press Enter to save the content. Then press Ctrl + X to exit.

Restart dnsmasq service to make new configuration take effect:

sudo systemctl restart dnsmasq.service

Now, still on web-01 server, we add our list custom domains to /etc/hosts. Run sudo nano /etc/hosts and add your domains, I’ll add my ones:

127.0.0.1 localhost
192.168.68.117 puppet-master
192.168.68.116 web-01

Press Ctrl + O and press Enter to save the content. Then press Ctrl + X to exit.

Testing

To verify that our DNS server on the web-01 server works, we need to configure your local machine to use the web-01 server as the DNS server.

If you are using Ubuntu:

Add this line below into /etc/resolv.conf. It should be in the first order, which overrides other nameservers, it looks like:

nameserver 192.168.68.116 # You should replace with IP of your DNS server
nameserver <your-default-dns-server >

If you use Windows:

  • Windows 10/11 – Windows key + I to open Settings, then navigate to Network & Internet -> search Wifi Settings -> Select “Hardware Properties” -> Edit “DNS server assignment” and change it to Manual and add the IP of your own DNS server (dnsmasq). Alternate DNS could be the same as your default DNS or 8.8.8.8
  • Older Windows – Should change in Control Panel -> Network and Internet -> Network Connections -> Right Click on your Wifi Network Adapter -> select Properties -> Click on Internet Protocal Version 4 (TCP/IPv4) -> Click Properties -> select Use the following DNS server addresses -> and add your own DNS server -> Click OK.

Testing by running:

ping web-01

Output:

Run a simple query on either your local DNS server or your machine pointing to your local DNS server:

dig srv.local

Output

Query the FQDN of one of the domain:

$ dig web-01.srv.local

Test a reverse IP lookup:

Congratulation! You now have your own local DNS server. If you are familiar with Docker, you can continue reading the next section to build a quick dnsmasq running on a container. That’s where you don’t have to build a virtual machine yourself as above.

(Optional) Set up Dnsmasq with Docker

We can actually build a dnsmasq service faster by using Docker instead of building a virtual machine or a separate physical machine to install it. I’ll walk you through the setup steps.

Install Docker

In my example below, I installed Docker on my Windows OS (including Docker Compose as well) and used the Windows Subsystem for Linux to interact with Docker. You can choose a version that fits with your OS platform

Create Dockerfile, compose file, and necessary files for dnsmasq

On your machine, create a folder to store all the files that we need to build the dnsmasq container. In my machine, I create /opt/docker-dnsmasq and /opt/docker-dnsmasq/build folders by running:

sudo mkdir -p /opt/docker-dnsmasq/build

In the /opt/docker-dnsmasq/build folder, create Dockerfile with content:

FROM alpine:3.22.2

# Install dnsmasq and tzdata
RUN apk update && apk add dnsmasq tzdata --no-cache

# Set the desired timezone (replace 'Europe/London' with your local timezone)
ENV TZ=Pacific/Auckland

# Create a directory to store 'hosts' file with custom hosts
# This file is mounted through real hosts file on the host machine
# We will add an option to dnsmasq.conf to make dnsmasq use /opt/homelab/etc/hosts instead of the default /etc/hosts
RUN mkdir -p /opt/homelab/etc/

COPY dnsmasq.conf /etc/dnsmasq.conf

# Start dnsmasq in the foreground
ENTRYPOINT [ "dnsmasq", "-k" ]

Noted: I’m using alpine image instead of ubuntu image. The reason is because it’s lighter, after setting up dnsmasq, it just takes you about 10-15MB for the image size. With ubuntu image, it could take more than 100MB

Next, still in /opt/docker-dnsmasq/build folder, create dnsmasq.conf file with content:

# no-resolv means dnsmasq does not needlessly read /etc/resolv.conf 
# which only contains the localhost addresss of itself
no-resolv

# Set dnsmasq to use Google Public DNS. You could also replace it
# with a DNS resolver you trust.
# Note: if you don't set this "server" parameter, you may not connect to
# the internet and just be able to talk to other servers in local network.
# The reason because DNS queries will be resolved with dnsmasq, only
# checking external servers if it cannot answer the query from its cache.
server=8.8.8.8

# Never forward plain names (without a dot or domain part) domain-needed
# Never forward addresses in the non-routed address spaces
bogus-priv

# Change this to your corresponding path that you defined in Dockerfile
addn-hosts=/opt/homelab/etc/hosts


# Add other name servers here, with domain specs if they are for non-public domains.
# This force your local domain to use nameservers with IP address(es) defined here.
address=/srv.local/127.0.0.1
address=/srv.local/192.168.68.116


# Uncomment expand-hosts to add the custom domain to hosts entries:
# Without this setting, you will have to add the domain to entries of /etc/hosts
# E.g. if expand-hosts is set, you define /etc/hosts with:
# 192.168.1.10 test
# "test" will be added with domain part ".srv.local" -> "test.srv.local"
# if expand-hosts is not set, you define /etc/hosts with:
# 192.168.1.10 test.srv.local
expand-hosts

# Provides the domain part for "expand-hosts".
# As I want all machines in my "local" network has domain "srv.local".
# I've enabled here. If you don't use domain, just comments it.
domain=srv.local

# Normally responses which come from /etc/hosts and the DHCP lease
# file have Time-To-Live set as zero. We can also extend time-to-live (in seconds) here
local-ttl=15

Next, in the /opt/docker-dnsmasq folder, create a compose.yml file with content:

services:
  dnsmasq:
    container_name: dnsmasq-server
    build: 
      context: ./build
    hostname: dnsmasq-server
    ports:
      - "53:53/udp"
      - "53:53/tcp"
    deploy:
      # Adjust memory/cpu to your expectation. 
      resources:
        limits:
          cpus: '0.1'
          memory: 256M
    volumes:
      # Remember to change /opt/docker-dnsmasq/hosts to your real path you created on your host machine
      - /opt/docker-dnsmasq/hosts:/opt/homelab/etc/hosts
    cap_add:
      - NET_ADMIN

We mounted /opt/docker-dnsmasq/hosts on the real host machine to /opt/homelab/etc/hosts inside the container. Every time we add a new host (e.g. test-01.srv.local 192.168.68.11) to /opt/docker-dnsmasq/hosts, we need to restart dnsmasq-server container to let dnsmasq have the DNS record updated for the newly added host.

Create a /opt/docker-dnsmasq/hosts file with content (please change the host to your expected hosts):

test-01.srv.local 192.168.68.11

Basically, after creating all files, you will have these files under your directory, which is /opt/docker-dnsmasq in my case:

.
├── build
│   ├── Dockerfile
│   └── dnsmasq.conf
├── compose.yml
├── hosts

Next, we’ll go to the /opt/docker-dnsmasq, and start building and running the dnsmasq-server container, we run:

cd /opt/docker-dnsmasq
docker compose up -d

Waiting for the build to finish…

On your machine, run:

docker ps

Output looks like below:

Ok, that’s all we need. Now, you will have a DNS service running locally. You can follow the Testing step in section 1 above to test the DNS for your hosts.

I hope this article will help you in some ways. Thanks for reading.


Discover more from Turn DevOps Easier

Subscribe to get the latest posts sent to your email.

By Binh

Leave a Reply

Your email address will not be published. Required fields are marked *

Content on this page