Skip to content
Home » How to Show and Manage the macOS Routing Table

How to Show and Manage the macOS Routing Table

Every Mac keeps a routing table: the kernel’s list of where to send each outgoing packet. The system builds it from your network settings, so most of the time you never have to touch it. When you do need to look inside — to check which route an address uses, add one to reach a subnet, or push some traffic through a VPN — macOS hands the job to a few BSD command-line tools.

Three of them cover almost everything. netstat shows the table, route changes the live one, and networksetup makes a change survive a reboot. This guide walks through each in turn, starting with the task most people come here for: showing the routing table.

Showing the routing table

The one command to remember is netstat -rn. It prints the IPv4 and IPv6 tables together. -r selects the routing display, and -n prints numeric addresses instead of resolving hostnames. That is both faster and clearer:

Routing tables

Internet:
Destination        Gateway            Flags               Netif Expire
default            192.168.1.1        UGScg                 en0
192.168.1/24       link#12            UCS                   en0      !
192.168.1.1        aa:bb:cc:dd:ee:ff  UHLWIir               en0   1165
127                127.0.0.1          UCS                   lo0
127.0.0.1          127.0.0.1          UH                    lo0
224.0.0/4          link#12            UmCS                  en0      !

The gateway address and the MAC address above are placeholders. Everything else is a real run — the columns, the flag combinations, and the layout are untouched. To show a single address family, add -f:

netstat -rn -f inet    # IPv4 only
netstat -rn -f inet6   # IPv6 only

How many rows you see depends on the machine. A plain Mac shows only a handful: the default route, loopback, and the local network. Install VPN clients or container tools and the count climbs, because each one adds its own interfaces and routes.

Reading the columns and flags

Each row is one route. It carries a destination, the gateway or interface that reaches it, a set of status flags, and the interface name. The flags hold most of the meaning. man netstat documents 23 of them, far more than the handful most guides mention. That is enough to decode a packed string like UGScg:

FlagMeaning
URoute is up (RTF_UP)
GRequires forwarding through a gateway (RTF_GATEWAY)
HRoute is to a single host, not a network (RTF_HOST)
SManually added, static route (RTF_STATIC)
CGenerates a new route on use (RTF_CLONING)
cProtocol-cloned from a parent route (RTF_PRCLONING)
LValid link-layer (MAC) address (RTF_LLINFO)
ITied to a specific interface scope (RTF_IFSCOPE)
WGenerated by cloning (RTF_WASCLONED)
RRejects on match, host unreachable (RTF_REJECT)
gRoute to the global internet, a policy hint (RTF_GLOBAL)
mRepresents a multicast address (RTF_MULTICAST)
rHost is a default router (RTF_ROUTER)
BSilently discard packets, a blackhole (RTF_BLACKHOLE)

Read left to right, UGScg on the default route means five things at once. It is up, uses a gateway, is static, is protocol-cloned, and is tagged for the global internet. The route -n get command below spells the same flags out in full words when a letter is unclear.

The route for one destination

Scanning the whole table to find one entry is slow. Instead, route -n get asks the kernel which route applies to a single address. It only reads, so nothing changes:

$ route -n get default
   route to: default
destination: default
       mask: default
    gateway: 192.168.1.1
  interface: en0
      flags: <UP,GATEWAY,DONE,STATIC,PRCLONING,GLOBAL>
 recvpipe  sendpipe  ssthresh  rtt,msec    rttvar  hopcount      mtu     expire
       0         0         0         0         0         0      1500         0

The gateway here is a placeholder; the rest is a real run. Every metric on the last line reads zero, because nothing has forced TCP to record round-trip data for this route yet. The flags line is the same properties as the letters above, written out in full. That makes it a handy way to check a combination you do not recognise.

Adding a static route

Adding a route to a whole network looks like this:

sudo route -n add -net 192.168.50.0 -netmask 255.255.255.0 192.168.1.1

-net marks the destination as a network. For a single address, use -host instead. These commands are not run against this machine, since that would edit a live routing table. The syntax is what man route documents.

One modifier trips people up: -interface. On Wi-Fi or Ethernet it does not take the port name. Per route(8), when the destination needs no gateway, -interface wants the Mac’s own address on that network. The bare interface name is only accepted “if the interface is point to point.” So on a broadcast link it is the local IP, not en0:

sudo route -n add -net 192.168.1.0/24 -interface 192.168.1.50

Here 192.168.1.50 is the Mac’s own address on that network. An earlier version of this page used the bare en0 in this spot, which the man page does not support on Wi-Fi or Ethernet. The VPN section below is where a bare interface name is genuinely correct.

Removing and changing routes

route also documents delete, the blunter flush, and change for editing one property of an existing route without removing it:

sudo route -n delete -net 192.168.50.0 -netmask 255.255.255.0 192.168.1.1

sudo route -n change -net 192.168.50.0 -netmask 255.255.255.0 192.168.1.254

Be careful with flush. It clears every route in the table, the default route included. It is for resetting the whole table, not for removing one entry. To drop a single static route, use delete.

When add succeeds but nothing answers

route add performs one check, and only one. Per route(8), its single documented failure for add is Network is unreachable: the gateway “was not on a directly-connected network.” That is all it verifies. It checks whether the gateway sits on a connected network, not whether any host answers there.

Gateway addressroute add outcome
Not on any directly-connected networkNetwork is unreachable — refused
On a connected network, nothing answers thereRoute is installed anyway

The second case is not a bug. It is the same behaviour that lets -reject and -blackhole drop traffic on purpose. It is also why you can pre-install a route before its gateway comes online. add checks the address, never the host behind it.

Routes that survive a reboot

This is the most common follow-up. You add a route, reboot, and it is gone. That is expected, not a bug. route(8) calls itself a tool to “manually manipulate the routing tables” through a routing socket. There is no config file involved. It edits the kernel’s live state, so a fresh boot rebuilds that state and the manual entry is simply not there.

For a route that has to persist, Apple documents networksetup. Its man page calls it the “configuration tool for network settings in System Preferences.” In other words, it writes to the same store the Network pane uses, not just the running table:

# destination, subnet mask, gateway — in that order
sudo networksetup -setadditionalroutes "Wi-Fi" 192.168.50.0 255.255.255.0 192.168.1.1

# check what is configured
networksetup -getadditionalroutes "Wi-Fi"

# clear it (call with no route tuples)
sudo networksetup -setadditionalroutes "Wi-Fi"

The service name — “Wi-Fi” here — has to match one from networksetup -listallnetworkservices. The route belongs to that service, not to the system as a whole. For an IPv6 destination, use -setv6additionalroutes, which takes a prefix length instead of a subnet mask.

Split-tunneling through a VPN

Sending one network’s traffic through a VPN is the other frequent case. It is also where the point-to-point exception matters. macOS VPN clients create a utun interface, and utun interfaces are point-to-point. So here the bare interface name is correct, not a mistake:

# send one network's traffic through the tunnel
sudo route -n add -net 10.0.0.0/8 -interface utun0

# everything else keeps the existing default route

The tunnel number — utun0, utun1, and so on — is assigned when the client connects, and it is not fixed. Check ifconfig -a or netstat -rn after connecting to see which one is active. And per the reboot rule above, a route added this way lasts only for that session. Most VPN clients re-add their own routes on each connect, so this rarely needs scripting.

Windows and Linux habits

route print, the Windows way to dump the table, has no macOS equivalent. Pass the keyword and route simply rejects it:

$ route print
route: bad keyword: print
usage: route [-dnqtv] command [[modifiers] args]

Linux is closer, but still different. Both the older route -n and the newer ip route print a table there. However, the names and flag syntax do not carry over to the BSD-derived tools on a Mac. The Destination / Gateway / Flags / Netif layout above is specific to netstat -rn, and ip route output looks nothing like it.

One last habit is worth keeping: check the exit status. Say a route added with sudo route add does not appear in netstat -rn afterward. route did not fail silently. It exits non-zero and prints a reason — Network is unreachable, routing table overflow, and others listed in the route(8) DIAGNOSTICS section.

Quick reference

TaskCommand
Show the routing tablenetstat -rn
IPv4 or IPv6 onlynetstat -rn -f inet / -f inet6
Route for one addressroute -n get ADDR
Add a network routesudo route -n add -net DEST -netmask MASK GW
Delete a routesudo route -n delete -net DEST -netmask MASK GW
Change a route’s gatewaysudo route -n change -net DEST -netmask MASK GW
Make a route persistsudo networksetup -setadditionalroutes "Wi-Fi" DEST MASK GW
Route through a VPNsudo route -n add -net DEST -interface utunN

The short version: netstat -rn shows the routing table, route edits the live one, and networksetup is what makes a route outlast a reboot. If you came from Windows expecting route print, that keyword does not exist here, and netstat -rn is its replacement.

Leave a Reply

Your email address will not be published. Required fields are marked *