Max Schmitt

April 8 2019

Local subdomains on macOS with Dnsmasq and Caddy

If you ever needed to create a system so that you could create user-specific dynamic subdomains like username.myapp.com, you will have run into that problem that getting this kind of thing working locally is tough.

Sure, you can edit your hosts file but it doesn't allow for wildcard subdomains (e.g. *.myapp.com), so it's only a viable solution if your address is static (e.g. api.myapp.com).

Here is how you can get wildcard subdomains running on your local system in 5 minutes:

1. Install Dnsmasq

Dnsmasq is a local DNS server. That means it tells your OS which IP address to go to for a given domain name. We will use this to point any *.test-domain to our local machine, localhost.

Terminal

$ brew install dnsmasq

2. Configure Dnsmasq to route *.test-domains to localhost

First, figure out where the Dnsmasq config file lives:

Terminal

$ brew --prefix
/usr/local

So in my case, it will be in /usr/local/etc/dnsmasq.conf.

/usr/local/etc/dnsmasq.conf

# Route all *.test addresses to localhost
address=/test/127.0.0.1
# Don't read /etc/resolv.conf or any other configuration files.
no-resolv
# Never forward plain names (without a dot or domain part)
domain-needed
# Never forward addresses in the non-routed address spaces.
bogus-priv

3. Run Dnsmasq now and anytime we restart the system

Terminal

$ sudo brew services start dnsmasq

Note: The sudo here is important!

4. Make macOS use our local DNS server (Dnsmasq) for *.test-addresses

Create the following file at /etc/resolver/test:

/etc/resolver/test

nameserver 127.0.0.1

With that, you can go to subdomain.test in your browser and it will point to localhost. So if your app is running on port 3000, simply go to subdomain.test:3000.

But what if you're looking for the following setup?

myapp.test -> localhost:8000
api.myapp.test -> localhost:3000
*.api.myapp.test -> localhost:3000

This we can solve with Caddy which is a super-awesome simple HTTP server!

5. Install Caddy

Terminal

$ brew install caddy

6. Configure Caddy

In my case (because my brew --prefix is /usr/local), my global Caddyfile is at /usr/local/etc/Caddyfile. This file we can setup to act as a simple reverse proxy for our individual apps running on different ports:

/usr/local/etc/Caddyfile

http://myapp.test {
proxy / localhost:8000
}
http://api.myapp.test, http://*.api.myapp.test {
proxy / localhost:3000 {
transparent
}
}

If you're using wildcard subdomains, you will want to use the transparent prefix for the proxy directive so it forwards the original hostname to your app like in the example above. You can find more info about that in Caddy's excellent docs.

7. Run Caddy now and every time your system restarts

Terminal

$ brew services start caddy

That's it! If you change your Caddyfile, you will need to brew services restart caddy.

Thanks to Jason Kulatunga's article Local Development with Wildcard DNS for making it easy to get started with Dnsmasq.

I hope this was helpful to you. If you have any questions, feel free to reach out to me on Twitter!