Resolving Local Domains With Dnsmasq and Caddy
Recently, I’ve been thinking of a better way to use local service addresses. I mean, there’s a reason we don’t use raw IP addresses but instead use human-readable names to access the various sites we use daily. Every now and then, whether it’s for my blog testing environment or accessing my local dev databases like MongoDB and MariaDB, I would sometimes have to look up the port number designated as the default for that service. This shouldn’t necessarily be a hassle, but I’ve come to understand that things you set up yourself—which obviously then come more naturally to you—tend to stick better, long-term memorization-wise. This isn’t to say one cannot just memorize the default port of these commonly used services anyway, but why “cram” when you can use this opportunity to gain better insight, even if it’s a bit shallow, about how things work?
I decided I would look into doing this.
The idea behind this isn’t too difficult. I would potentially need to edit my /etc/hosts
file to let it know certain domain-like names should be resolved to my local machine (i.e., 127.0.0.1
). The proposed config needed to be appended to the already existing configuration would look like this:
127.0.0.1 dev.localhost
127.0.0.1 hugo.localhost
# more subdomains
But there was a problem with this. You see, my hosts file is dynamically generated by hblock
for the sole purpose of getting rid of ads littered around the World Wide Web. Manually modifying it felt wrong as, as far as I know, it could be rewritten any time. Plus, there’s also the fact that the hosts file doesn’t support wildcard syntax (like *.localhost
) to map every possible localhost prefix, which would mean tedious additions.
I started looking for my next best solution. This was when I found dnsmasq
.
I decided to set it up as my local DNS resolver instead of relying on NetworkManager, which was configured to use Google’s DNS (8.8.8.8
) as the nameserver of choice.
With dnsmasq
, I could write a config like this:
address=/.localhost/127.0.0.1
This effectively resolves all *.localhost
address as localhost
or, to be more specific, 127.0.0.1
.
One thing I have learnt playing with Linux is if there is a “drop-in” directory, use that instead of modifying the main configuration file itself.
After installing dnsmasq
, I just needed to create a drop-in config file for NetworkManager:
# /etc/NetworkManager/conf.d/use-dnsmasq.conf
[main]
dns=dnsmasq
This tells NetworkManager to use dnsmasq
as the DNS resolver.
Since I now use dnsmasq
as my local DNS resolver, I needed to give it some config.
Drop-in files again!
This time for dnsmasq
itself:
# /etc/dnsmasq.d/localhost-wildcard.conf
address=/.localhost/127.0.0.1
After restarting the NetworkManager and dnsmasq
services, I thought that was all, as dev.localhost
did resolve to 127.0.0.1
. This was confirmed by testing using the dig
command:
dig dev.localhost
The problem arose when I tried running ping google.com
and it failed to resolve.
It turned out that since I had now designated dnsmasq
as the resolver, I also needed to set up the upstream nameservers it should use to resolve anything that doesn’t fall into my localhost
wildcard.
# /etc/dnsmasq.d/upstream.conf
server=1.1.1.1
server=8.8.8.8
Why 1.1.1.1
instead of 8.8.8.8
as priority? Well, honestly, I have no reason apart from hearsay that Cloudflare’s DNS is faster than Google’s. Not that I think I would ever get any benefits from it, but why not? I’m messing with configs; I might as well try new things anyway.
At this point, everything was working, but this is not all I intended to do. Ultimately, I wanted this:
dev.localhost >>>>>>>>>>>>>>> localhost:3000
hugo.localhost >>>>>>>>>>>>>>> localhost:1313
backend.localhost >>>>>>>>>>>>>>> localhost:8000
mariadb.localhost >>>>>>>>>>>>>>> localhost:3306
etc
So next was to set up a reverse proxy. The go-to usually is Nginx, but I find that Caddy is pretty much easier to work with.
You guessed it – drop-in files for Caddy!
# /etc/caddy/conf.d/localhost.conf
http://dev.localhost {
reverse_proxy localhost:3000
}
http://hugo.localhost {
reverse_proxy localhost:1313
}
http://backend.localhost {
reverse_proxy localhost:8000
}
http://mariadb.localhost {
reverse_proxy localhost:3306
}
Whilst I was successful setting up the HTTP routes, I couldn’t quite do the same for the last entry, which maps to the database. That’s because, ultimately, they use different protocols (mysql://
!== http://
), so an HTTP reverse proxy isn’t a direct fit for that kind of raw database connection.
Ultimately, I wasn’t able to achieve exactly what I planned to do, as I was more particular about routes like that of the database I use in development—an HTTP reverse proxy isn’t quite the right tool for different protocols like mysql:// when you’re trying to proxy a raw database connection. But regardless, it was still a fun rabbit hole to go down! Besides, I think the little knowledge acquired here will be useful when I eventually decide to set up my homelab in the near future.
Till next time!