How to Set a Persistent DNS Server on macOS When Offline for Local Development


2 views

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"