Accéder au contenu principal

3min.

How to Configure DNS for Developing Webapp with Dnsmaqs once for all

When developing web applications locally, managing domain names quickly becomes a tedious chore. Whether you are running multiple projects simultaneously or using tools that require specific domain names (such as multi-tenant applications), you need a way to map local domains to your machine.

For years, the standard approach was to use dnsmasq to automatically route a specific TLD (like .test or .localhost) to 127.0.0.1. However, modern Ubuntu releases changed how DNS is handled out of the box, leading to a major port conflict.

Here is a complete guide on why we use dnsmasq, what goes wrong on modern systems, and how to configure a clean, modern solution.

Section intitulée the-goal-dynamic-local-domainsThe Goal: Dynamic Local Domains

In a basic setup, developers often rely on the /etc/hosts file to map a domain to localhost:

127.0.0.1 my-awesome-app.test
127.0.0.1 api.my-awesome-app.test

While this works, it does not scale. Every time you create a new project, add a subdomain, or change a local domain, you have to edit this system file manually with root privileges.

This is where dnsmasq comes in.

dnsmasq is a lightweight DNS forwarder. Instead of hardcoding every single domain name, it allows you to define a wildcard rule. For example, you can tell your system: “Any query that ends with .test should automatically resolve to 127.0.0.1.”

Once configured, you can access anything.test, project-a.test, or sub.project-b.test instantly without ever touching a configuration file again.

Section intitulée the-problem-enter-systemd-resolvedThe Problem: Enter systemd-resolved

Historically, setting up dnsmasq involved making it the primary DNS resolver for your entire system.

However, modern Ubuntu releases now rely on systemd-resolved to handle network-level DNS queries. systemd-resolved runs a local DNS stub listener that binds to 127.0.0.53 on Port 53.

When you install dnsmasq and try to start it, the service will crash with the following error:

failed to create listening socket for port 53: Address already in use

Because dnsmasq attempts to bind to Port 53 on all available network interfaces by default, it collides directly with systemd-resolved.

In the past, many workarounds suggested disabling systemd-resolved entirely. However, doing so is highly discouraged as it often breaks DNS resolution when switching Wi-Fi networks, connecting to VPNs, or dealing with captive portals.

Section intitulée the-solution-elegant-cohabitationThe Solution: Elegant Cohabitation

The most robust and modern solution is to let both services cohabitate. We will restrict dnsmasq to only listen on the standard loopback address (127.0.0.1) and instruct systemd-resolved to forward queries for our development TLD to it.

Section intitulée 1-install-dnsmasq1. Install dnsmasq

If you haven’t already, install dnsmasq via your package manager. Note that the service might fail to start immediately after installation due to the port conflict—this is normal, and we will fix it in the next steps.

sudo apt update && sudo apt install dnsmasq

Section intitulée 2-configure-and-restrict-dnsmasq2. Configure and restrict dnsmasq

Create or edit your local configuration file (for example, /etc/dnsmasq.d/dev-domains.conf).

First, we force dnsmasq to only bind to the local loopback interface (127.0.0.1), leaving the rest of the network stack alone. Second, we define our wildcard routing rule for .test:

sudo tee -a /etc/dnsmasq.d/dev-domains.conf <<EOF
# Restrict dnsmasq to the local loopback address
listen-address=127.0.0.1
interface=lo
bind-interfaces

# Configure the wildcard TLD
address=/.test/127.0.0.1
EOF

Section intitulée 3-configure-routing-in-systemd-resolved3. Configure routing in systemd-resolved

Now we need to tell systemd-resolved that it should delegate any DNS queries ending in .test to our local dnsmasq instance running on 127.0.0.1.

Create a new configuration directory and file:

sudo mkdir -p /etc/systemd/resolved.conf.d/
sudo tee -a /etc/systemd/resolved.conf.d/dnsmasq.conf <<EOF
[Resolve]
DNS=127.0.0.1
Domains=~test
EOF

Note: The ~ symbol before test is critical. It defines a “routing domain”. This tells systemd-resolved to use the specified DNS IP (127.0.0.1) only for queries matching that specific domain. All other internet traffic will continue to use your standard network DNS servers.

Section intitulée 4-restart-the-services4. Restart the services

Apply the changes by restarting both system daemons:

sudo systemctl restart dnsmasq systemd-resolved

Section intitulée conclusionConclusion

By configuring a routing domain, you get the best of both worlds. You no longer need to manually edit or override the system’s /etc/resolv.conf symlink.

Your local development domains resolve instantly, and your system’s network configuration remains stable and untouched across updates, VPN connections, and network changes.

Commentaires et discussions