When working on local development projects (especially Rails apps using subdomains), macOS's handling of DNS can be frustrating. The system clears /etc/resolv.conf
when offline and ignores manual edits. This breaks local DNS servers like Dnsmasq that rely on these settings.
Standard approaches don't work:
- Editing
/etc/resolv.conf
- gets overwritten - Network Preferences GUI - disabled when offline
scutil
commands - often fail without network
Create a custom configuration for macOS's DNS resolver:
sudo mkdir -p /etc/resolver
sudo tee /etc/resolver/local <<EOF
nameserver 127.0.0.1
domain local
search_order 1
EOF
Here's a complete local development setup:
# Install Dnsmasq
brew install dnsmasq
# Configuration
echo 'address=/.test/127.0.0.1' >> /usr/local/etc/dnsmasq.conf
echo 'listen-address=127.0.0.1' >> /usr/local/etc/dnsmasq.conf
# Start the service
sudo brew services start dnsmasq
Verify it works offline:
# Turn off all network interfaces
networksetup -setairportpower airport off
networksetup -setnetworkserviceenabled "Thunderbolt Ethernet" off
# Test resolution
dig anyname.test @127.0.0.1
ping -c 1 anyname.test
For projects needing many subdomains:
# In dnsmasq.conf
address=/.localhost/127.0.0.1
address=/.lvh.me/127.0.0.1
address=/.xip.io/127.0.0.1
This persists through network changes and works offline.
When working on local development (especially with Rails subdomains), macOS aggressively clears /etc/resolv.conf
during network interface changes. This breaks local DNS solutions like Dnsmasq when going offline.
For Rails apps using wildcard subdomains (*.yourapp.local
), /etc/hosts
can't help because:
# Doesn't work for wildcards
127.0.0.1 account1.yourapp.local
127.0.0.1 account2.yourapp.local
# ...but impractical for dynamic subdomains
First, ensure proper Dnsmasq configuration:
# /usr/local/etc/dnsmasq.conf
address=/.yourapp.local/127.0.0.1
listen-address=127.0.0.1
Create a launchd plist to enforce DNS settings regardless of network state:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>local.dns.enforcer</string>
<key>ProgramArguments</key>
<array>
<string>/usr/sbin/networksetup</string>
<string>-setdnsservers</string>
<string>Wi-Fi</string>
<string>127.0.0.1</string>
</array>
<key>StartInterval</key>
<integer>30</integer>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
For newer macOS versions, modify the mDNSResponder config:
sudo mkdir -p /etc/resolver
sudo tee /etc/resolver/local <<EOF
nameserver 127.0.0.1
domain yourapp.local
search_order 1
EOF
Check active DNS configuration:
scutil --dns | grep 'nameserver'
dig test.yourapp.local @127.0.0.1
Create a test controller to verify subdomain handling:
# Rails controller example
class SubdomainTestController < ApplicationController
def check
render plain: "Serving from: #{request.subdomain}"
end
end
Then test with:
curl http://random123.yourapp.local:3000/subdomain_test/check
# Should return "Serving from: random123"