Quickly switch network profiles on macOS

My network infrastructure is, thanks to my homelab, probably a bit more involved than for the average user. I maintain a VPN network, custom DNS servers, and both Wi-Fi and Ethernet connections. When switching workplaces I constantly have to switch network settings. macOS offers Network Locations which first looked like the answer, but they are too limited for my needs. So I built a small script around the existing macOS CLIs.

What I needed

My typical network contexts each require a different combination of settings:

  • Home: Wi-Fi on DHCP, DNS pointing at my homelab resolvers, VPN off

  • Work: Wi-Fi on DHCP, system default DNS, VPN off

  • Home Ethernet: Ethernet on DHCP, custom DNS, Wi-Fi off

  • Modem Management: Ethernet with a manual static IP for accessing my modem's admin interface, no custom DNS

  • VPN: Wi-Fi on DHCP, VPN on

Network Locations can store some of this but not all of it. DNS overrides and VPN toggling are outside what they handle cleanly.

The script

I created a script called netprofile. It wraps three macOS tools: networksetup for IP and DNS configuration, scutil for VPN control, and service validation using networksetup -listallnetworkservices.

The script is built around a set of composable low-level functions:

set_dns "Wi-Fi" "192.168.1.10" "192.168.1.11"    # set DNS servers
set_dns "Wi-Fi" "empty"                          # clear DNS back to default
set_wifi "Wi-Fi" dhcp                            # enable Wi-Fi with DHCP
set_wifi "Wi-Fi" off                             # turn Wi-Fi off
set_ethernet "Ethernet USB" manual "192.168.254.99" "255.255.255.0" "192.168.254.1"
set_vpn "homelab" on
set_vpn "homelab" off

Each function handles only one concern. set_wifi calls networksetup -setairportpower and optionally networksetup -setdhcp or networksetup -setmanual. set_vpn uses scutil --nc start and scutil --nc stop, always stopping before starting to ensure a clean state.

The profiles themselves are just case branches in apply_profile that call these primitives:

Home)
    set_wifi "$WIFI_SERVICE" dhcp
    set_ethernet "$ETH_SERVICE" off
    set_dns "$WIFI_SERVICE" "$DNS01" "$DNS02"
    set_vpn "$VPN_NAME" off
    ;;

Service validation

Before applying any profile the script checks that the named services actually exist on the current machine:

check_service_exists() {
  local service="$1"
  if ! networksetup -listallnetworkservices | sed '1d' | grep -Eq "^\*?$service$"; then
    err "Network service '$service' not found."
    exit 1
  fi
}

The sed '1d' strips the header line from networksetup output. The regex accounts for inactive services, which networksetup prefixes with an asterisk.

Usage

The script supports three modes:

netprofile              # interactive select menu
netprofile Home         # apply profile directly
netprofile --list       # print available profiles

The interactive mode uses bash's built-in select which presents a numbered menu. This is useful when running from a launcher or when I forget the exact profile name.

The service names (Wi-Fi, Ethernet USB) and the VPN name (homelab) are defined as variables at the top of the script. Adjusting for a different machine means changing those four lines.

Alfred integration

Typing netprofile Home in a terminal is already fast, but I wanted to trigger it without leaving whatever I was doing. Alfred is my launcher for everything, so the natural step was to build a small workflow around the script.

An image showing an Alfred workflow for switching between different ssh hosts.

The workflow uses a List Filter as its input. Each profile name (Home, Work, Home Ethernet, Modem Management, VPN) is an entry in the list with a matching keyword and subtitle. Alfred's fuzzy matching means typing hom is enough to surface both Home and Home Ethernet instantly.

The List Filter passes the selected profile title to a Run Script action:

/usr/local/bin/netprofile "$1"

Using the full path to the script avoids any PATH issues since Alfred does not source the shell environment. The script runs, applies the profile, and exits silently. A Post Notification action at the end of the chain sends a brief macOS notification confirming which profile was activated, so there is visible feedback without having to open a terminal.

The result is: open Alfred, type two or three characters, press Enter, and the network switches. The whole interaction takes about two seconds.

Posted in macos