This article shows you how to install, configure, and run dnsmasq as your local DNS cache on Ubuntu Linux, on an AWS EC2 server. This way you’re not running too many DNS lookups from, say, your web app to your managed AWS RDS database, and seeing weird errors like “Temporary failure in name resolution”…

Update - Consider Using Unbound Instead!

After I wrote this article, I discovered Unbound and it’s much easier, and it just works. Consider reading this article on Unbound first.

Run all of the following in order

  • It’s pretty much a script, but best to do it one line at a time
  • The following works for Ubuntu, unlike the instructions here: https://repost.aws/knowledge-center/dns-resolution-failures-ec2-linux

Check whether this is to run on VPC (default) or EC2 classic and set NAMESERVER accordingly

INTERFACE=$(curl --silent http://169.254.169.254/latest/meta-data/network/interfaces/macs/ | head -n1)
IS_IT_CLASSIC=$(curl --write-out %{http_code} --silent --output /dev/null http://169.254.169.254/latest/meta-data/network/interfaces/macs/${INTERFACE}/vpc-id)

if [[ $IS_IT_CLASSIC == '404' ]]
then
  NAMESERVER="172.16.0.23"
else
  NAMESERVER="169.254.169.253"
fi

echo "IS_IT_CLASSIC = $IS_IT_CLASSIC (old Kapua 200 response, new reserved instance, 401)"
echo "NAMESERVER = $NAMESERVER"

You should see something like NAMESERVER = 169.254.169.253 (the main AWS Route53 nameserver)

Install the dnsmasq package and DHCP client

sudo apt update && sudo apt install -y dnsmasq isc-dhcp-client

Create the required User and Group

groupadd -r dnsmasq
useradd -r -g dnsmasq dnsmasq

Set dnsmasq.conf configuration

echo -e "# Server Configuration\n\
listen-address=127.0.0.1\n\
port=53\n\
bind-interfaces\n\
user=dnsmasq\n\
group=dnsmasq\n\
# I think /var/run/dnsmasq.pid is for a different flavour of AWS Linux EC2\n\
#pid-file=/var/run/dnsmasq.pid\n\
pid-file=/run/dnsmasq/dnsmasq.pid\n\n\
# Name resolution options\n\
resolv-file=/etc/resolv.dnsmasq\n\
cache-size=500\n\
neg-ttl=60\n\
domain-needed\n\
bogus-priv" | sudo tee /etc/dnsmasq.conf > /dev/null

Check it

cat /etc/dnsmasq.conf

Populate /etc/resolv.dnsmasq

sudo bash -c "echo 'nameserver ${NAMESERVER}' > /etc/resolv.dnsmasq" && \
echo "/etc/resolv.dnsmasq contents:" && cat /etc/resolv.dnsmasq

Create /etc/dhcp3/dhclient.conf

sudo mkdir -p /etc/dhcp3
sudo touch /etc/dhcp3/dhclient.conf || true
echo -e "
#supersede domain-name "fugue.com home.vix.com";\n\
prepend domain-name-servers 127.0.0.1;\n\
request subnet-mask, broadcast-address, time-offset, routers,\n\
domain-name, domain-name-servers, host-name,\n\
netbios-name-servers, netbios-scope;" | sudo tee /etc/dhcp3/dhclient.conf > /dev/null

Check it

cat /etc/dhcp3/dhclient.conf

Make the localhost 127.0.0.1 the main (local) DNS resolver

echo "Old /etc/systemd/resolved.conf contents: " && cat /etc/systemd/resolved.conf
sudo sed -i 's/^#DNS=.*$/DNS=127.0.0.1/' /etc/systemd/resolved.conf

Enable DNS security (highly recommended)

sudo sed -i 's/^#DNSSEC=yes.*$/DNSSEC=yes/' /etc/systemd/resolved.conf

Use dnsmasq instead of systemd-resolved (i.e. we don’t want systemd-resolved to be the DNS Stub Listener)

sudo sed -i 's/^#DNSStubListener=yes.*$/DNSStubListener=no/' /etc/systemd/resolved.conf
sudo sed -i 's/^#DNSStubListener=no.*$/DNSStubListener=no/' /etc/systemd/resolved.conf

echo "New /etc/systemd/resolved.conf contents: " && cat /etc/systemd/resolved.conf
sudo systemctl reload-or-restart systemd-resolved
sudo systemctl status systemd-resolved

Check if it works at this time

dig aws.amazon.com

For dnsmasq to work, iptables mustn’t block the DHCP port

sudo ufw allow bootps

Create the PID directory and file

sudo mkdir -p /run/dnsmasq
sudo touch /run/dnsmasq/dnsmasq.pid
sudo chown -R dnsmasq:dnsmasq /run/dnsmasq
ls -la /run/dnsmasq
cat /run/dnsmasq/dnsmasq.pid

Enable and Start dnsmasq service

sudo systemctl status dnsmasq.service
sudo systemctl enable  dnsmasq.service 
sudo systemctl reload-or-restart dnsmasq.service

Test the service and configure dhclient accordingly. Set the dnsmasq DNS cache as the default DNS resolver. Note: You must suppress the default DNS resolver that DHCP provides. To do this, change or create the /etc/dhcp/dhclient.conf file.

echo "supersede domain-name-servers 127.0.0.1, ${NAMESERVER};" | sudo tee /etc/dhcp/dhclient.conf > /dev/null 

Quick check to see if DNS is working right now

dig aws.amazon.com @127.0.0.1

Apply the change (or… sudo systemctl restart network)

sudo dhclient

By default, systemd-resolved creates a symbolic link at /etc/resolv.conf that points to a local DNS stub (127.0.0.53). You need to remove this link and replace it with a standard /etc/resolv.conf file.

echo "/etc/resolv.conf contents:" && cat /etc/resolv.conf
ls -la /etc/resolv.conf
cat /etc/resolv.conf

Make the file not immutable, so we can delete, move, or change it.

sudo chattr -i /etc/resolv.conf
# sudo unlink /etc/resolv.conf 
sudo mv /etc/resolv.conf /etc/resolv.conf.bak-Sep-11-2024

Create a new /etc/resolv.conf file manually, specifying a DNS server (e.g., Google’s public DNS or another one you prefer):

sudo bash -c 'echo "nameserver ${NAMESERVER}" > /etc/resolv.conf'

Prevent systemd or other services from modifying your new /etc/resolv.conf file, you can set it as immutable

sudo chattr +i /etc/resolv.conf

To undo the above and make the file mutable again, run sudo chattr -i /etc/resolv.conf

Check the permissions, and whether it’s a file. Previously it was a symlink.

ls -la /etc/resolv.conf

Ensure both services are enabled

sudo systemctl enable systemd-resolved
sudo systemctl enable dnsmasq.service

Reload or restart them for good measure

sudo systemctl reload-or-restart systemd-resolved
sudo systemctl reload-or-restart dnsmasq.service

Verify dnsmasq works correctly

dig aws.amazon.com @127.0.0.1

Cheers,
Sean