Skip to content

Instantly share code, notes, and snippets.

@justicefreed
Created May 13, 2025 17:21
Show Gist options
  • Select an option

  • Save justicefreed/ccbae2c82b25a664e73b8f1c2f6b805b to your computer and use it in GitHub Desktop.

Select an option

Save justicefreed/ccbae2c82b25a664e73b8f1c2f6b805b to your computer and use it in GitHub Desktop.
Fix VLAN MTU Bug on OSX (eg HTTPS pages won't load)

Bug / Issue Description

OSX does not correctly fragment packets on VLAN virtual interfaces for default MTU settings. Additionally, manually setting interface MTU settings do not always persist across interface disconnection, sleep, and reboot in some OSX versions.

What is supposed to happen

  1. A virtual interface of type "VLAN" has been created and attached to a parent interface
  2. You try and make a network request of any kind using that VLAN / virtual interface that expects a response larger than 1472 Bytes, like an HTTPS web page (HTTP may not exceed this).
  3. Because the request is larger than the interface MTU, it is split into fragments. Each fragment should be at most the size of the parent MTU MINUS the 4 bytes for the VLAN header(s)
  4. The request succeeds and gets a reply which is itself fragmented appropriately.

How the issue is triggered

  1. A virtual interface of type "VLAN" has been created and attached to a parent interface
  2. You try and make a network request of any kind using that VLAN / virtual interface that expects a response larger than 1472 Bytes, like an HTTPS web page (HTTP may not exceed this).
  3. Because the request is larger than the interface MTU, it is split into fragments. The packet ignores any configured MTU for the VLAN virtual interface and instead is fragmented based on the parent interface's MTU, AND WITHOUT accounting for the extra space needed by the VLAN headers.
  4. The VLAN and other headers are added to the packet, causing the total size to now be 4 bytes LARGER than the parent interface's MTU (in this case, 1504, which is larger than 1500).
  5. Because the packet's size is larger than the MTU, the parent interface silently rejects the packet, never sending it.

Workarounds If Your MTU Settings Persist Properly

These did not previously work for me, but sometime between my original tests and updating OSX to 15.3.2, the network interface MTU setting appears to now be persisting properly across sleep, interface disconnection, and reboot based on my initial testing. May be worth updating your OS if this aspect is not working. Else, see the other workaroundss.

Option #1 - VLAN Interface(s) MTU

This is potentially ideal because theoretically if you change which parent interface the VLAN is attached to, as long as it still supports at least the prior max value, it should follow the change. Do this for each VLAN virtual interface.

  1. In System Settings -> Network, select the VLAN virtual interface.
  2. Click the details button, and then select the Hardware sub-menu
  3. Change the value for "Configure" to "Manually", and then set the MTU to "Custom"
  4. Type in an MTU 4 Bytes greater than the standard one (usually type 1504)
  5. Click OK

Option #2 - Parent Interface MTU

If you have lots of VLANs or constantly create / remove them, this may be preferred as you only have to set this once.

  1. In System Settings -> Network, select the parent interface that you have VLANs attached to.
  2. Click the details button, and then select the Hardware sub-menu
  3. Change the value for "Configure" to "Manually", and then set the MTU to "Custom"
  4. Type in an MTU 4 Bytes greater than the standard one (usually type 1504)
  5. Click OK

Workaround If Your MTU Settings DON'T Persist Properly

The only way I found to do this is to automatically re-apply the proper MTU setting any time the network interface reverted to the wrong / default MTU. I chose to implement this with a simple bash script that checks to see if any MTU settings need to be fixed and fixes them if so. This script is triggered by a LaunchDaemon that watches the network interface directories / files for changes as a way to know if any network settings have changed. To determine if any MTU setting should be changed, it looks for all interfaces that support the VLAN_MTU feature, pulls the maximum MTU supported by that interface, and then sets the MTU to the max if it isn't already.

You can create and apply the necessary files by running the following bash script in terminal with sudo.

This is generally tested and working for me, but I can't guarantee it will work perfectly for you. If there are any bugs in my script code let me know and I can see if there's a quick fix.

#! /bin/bash
set -e
SCRIPTFILEPATH='/Users/Shared/scripts/handlesystemnetifchange.sh'
SCRIPTFILEPATH='/Users/Shared/scripts/handlesystemnetifchange.sh'
LAUNCHDAEMONPATH='/Library/LaunchDaemons/net.interface.change.sys.plist'
echo "Make sure fixer script directy exists for path $SCRIPTFILEPATH"
mkdir -p "$(dirname "${SCRIPTFILEPATH}")"
echo "Writing the fixer script to $SCRIPTFILEPATH"
sudo tee "$SCRIPTFILEPATH" <<'EOF'
#!/bin/bash
IFCHANGELIST=$(ifconfig -a inet | grep -B 1 "VLAN_MTU" | grep "mtu")
while IFS= read -r line || [[ -n $line ]]; do
{
IFNAME=${line%:*}
CURRENTMTU=$(echo "$line" | grep -o "\w*$")
MTURANGELINE=$(networksetup -listValidMTURange "$IFNAME")
MAXMTU=${MTURANGELINE#*-}
if [[ "$CURRENTMTU" != "$MAXMTU" ]]; then
echo "Current MTU of $CURRENTMTU less than max of $MAXMTU, setting to max"
networksetup -setMTU "$IFNAME" "$MAXMTU"
fi
}
done < <(printf '%s' "$IFCHANGELIST")
EOF
echo "Making the fixer script executable by all"
sudo chmod 755 "$SCRIPTFILEPATH"
echo "Run the fixer script once to get things started"
sudo bash "$SCRIPTFILEPATH"
echo "Write the LaunchDaemon plist file to watch the network interface changes"
sudo tee "$LAUNCHDAEMONPATH" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>net.interface.change.sys</string>
<key>ProgramArguments</key>
<array>
<string>$SCRIPTFILEPATH</string>
</array>
<key>WatchPaths</key>
<array>
<string>/etc/resolv.conf</string>
<string>/Library/Preferences/SystemConfiguration</string>
</array>
</dict>
</plist>
EOF
echo "Enable the LaunchDaemon"
sudo launchctl load -w "$LAUNCHDAEMONPATH"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment