macOS Networking Reference computer networks — the ideas, and every tool for working with them from a Mac
Half textbook, half man page. The first half is the theory a working programmer actually needs — layers, addresses, routing, TCP, DNS, firewalls, TLS — keyed to two free university textbooks and to the macOS command that lets you see each idea on a live machine. The second half is the tool index: every networking command Apple ships, its useful flags, and the traps — checked against the man pages of macOS 26.6.2 (build 25G83) on Apple silicon. macOS is a BSD underneath: ifconfig, netstat and pf rather than ip, ss and iptables, plus a layer of Apple-only plumbing (configd, mDNSResponder, networksetup) that decides what the BSD tools are allowed to remember.
Apple-onlyBSD / POSIX classicBundled open sourceHomebrew / third-partyRemoved / deprecated
The Map
What a network is made of, layer by layer, and which macOS command shows you each layer on a live machine. Every guide card ends with pointers into the two free textbooks this sheet leans on: Peterson & Davie's Computer Networks: A Systems Approach (P&D) and Dordal's An Introduction to Computer Networks (Dordal).
Layers, and the command that shows each one
the hourglass
The Internet is an hourglass: many link technologies at the bottom, many applications at the top, and one thing in the middle that everything agrees on — IP. Each layer only has to talk to the layer above and below, which is why you can swap Wi‑Fi for Ethernet without Safari noticing. The standard five-layer picture, with the Mac's view of each:
Two ideas carry most of the weight. Encapsulation: each layer wraps the one above in its own header — a web request is bytes inside a TCP segment inside an IP packet inside an Ethernet frame — and tcpdump -e -v -X shows all four headers in one line. The end-to-end argument: reliability, ordering and security are done at the ends (TCP, TLS) rather than in the middle, so routers stay simple and dumb. The same argument explains why NAT and middleboxes are considered rude.
Anything with an IP address. Your Mac is a host; a router is a node that forwards.
link
One hop: the wire, fibre or radio between two nodes. A LAN is one broadcast domain of links (one Ethernet switch, one Wi‑Fi SSID).
subnet / prefix
The set of addresses reachable without a router, written CIDR-style 192.168.1.0/24. "On-link" = same prefix = ARP for it directly.
gateway / default route
The router you hand everything to when the destination is not on-link. route -n get default.
interface
A kernel object for one attachment point: en0 (Wi‑Fi on a laptop), lo0 (loopback), utun3 (a VPN tunnel). Has addresses, a MAC, an MTU and a media state.
MTU
Largest packet a link carries — 1500 on Ethernet and Wi‑Fi, 16384 on lo0, 9000 "jumbo". Bigger packets fragment (IPv4) or are dropped with an ICMP "too big" (IPv6, and IPv4 with DF set).
bandwidth vs latency
Bits per second vs seconds per bit. A fat link is high bandwidth; a long link is high latency; bandwidth × RTT (round-trip time) is how many bytes are in flight — what TCP's window must cover.
socket
The OS handle for one endpoint: (protocol, local addr, local port, remote addr, remote port). A TCP connection is the pair of them. lsof -i lists yours.
port
16-bit transport-layer address. 0–1023 well-known (root to bind), 1024–49151 registered, 49152–65535 ephemeral — the Mac hands out net.inet.ip.portrange.first=49152 upward for outbound connections.
unicast / broadcast / multicast
One receiver / everyone on the link (255.255.255.255, ff:ff:ff:ff:ff:ff) / a subscribed group (224.0.0.0/4, ff02::/16; Bonjour uses 224.0.0.251 and ff02::fb). IPv6 has no broadcast — it multicasts.
stateful
A device that remembers connections — your NAT router, pf with keep state, any TCP endpoint. Stateless things (plain routers, UDP) forget every packet.
Numbers to keep in your head
Quantity
Value
Ethernet frame
14-byte header + 46–1500 payload + 4-byte CRC; 1518 max, 64 min. Preamble and 12-byte gap on the wire.
IPv4 header
20 bytes (no options). TTL starts at 64 on macOS (net.inet.ip.ttl), 128 on Windows, 255 on Cisco.
IPv6 header
40 bytes fixed; extension headers chain after it. Hop limit 64.
TCP header
20 bytes + options (MSS = max segment size, SACK = selective acknowledgement, timestamps, window scale — ≈ 12–20 more). MSS on a 1500 MTU = 1460 (v4) / 1440 (v6).
UDP header
8 bytes. Max datagram the Mac will send by default: net.inet.udp.maxdgram = 9216.
Speed of light in fibre
≈ 200,000 km/s → ~5 µs/km → ~40 ms one way across the US, ~70 ms transatlantic RTT floor.
Bandwidth-delay product
1 Gbit/s × 50 ms = 6.25 MB in flight. TCP's window must be that big (the Mac autotunes from net.inet.tcp.recvspace=128 KB up).
Wi‑Fi signal
RSSI (received signal strength) −30 dBm excellent · −60 good · −70 the edge · −80 unusable. Noise floor ≈ −90. SNR = RSSI − noise; >25 dB is what you want.
Ephemeral ports
49152–65535 (16,384 of them) — ~2 minutes in TIME_WAIT each (2×MSL, net.inet.tcp.msl=15000 ms) bounds connection churn.
DNS
UDP 53, falls back to TCP on truncation (>512 bytes, or the larger size EDNS advertises). DNS over TLS (DoT) is TCP 853, DNS over HTTPS (DoH) is 443.
A Packet's Journey
What actually happens between typing curl https://example.com and bytes coming back — with the command that exposes each step. Every failure is then "which step broke?"
Eleven steps, eleven commands
#
Step
Watch it happen
1
Link up. The Wi‑Fi card associates with an access point (or Ethernet negotiates a speed). Until then nothing else can start.
ifconfig en0 → status: active; sudo wdutil info for RSSI/channel
2
Get an address. DHCP: the Mac broadcasts DISCOVER, a server OFFERs, the Mac REQUESTs, the server ACKs — address, mask, router, DNS, lease time. IPv6 does it with Router Advertisements + SLAAC (stateless address autoconfiguration) instead (and optionally DHCPv6).
ipconfig getpacket en0 (the ACK), ipconfig getra en0 (the RA), ipconfig getsummary en0
3
Resolve the name. The app calls getaddrinfo() → mDNSResponder checks /etc/hosts, its cache, then asks the configured resolver over UDP 53 (or DoH/DoT if a profile set it up). Gets back A and AAAA records with TTLs.
scutil --dns (who is asked), dns-sd -G v4v6 example.com (what the app sees), dig example.com (ask a server directly)
4
Pick a source and a route. Kernel does longest-prefix match on the destination in the routing table: on-link → deliver directly; otherwise → the default gateway. The interface chosen gives the source address.
route -n get example.com, netstat -rn
5
Find the next hop's MAC. ARP (v4) or Neighbor Discovery (v6): "who has 192.168.1.1?" broadcast, reply cached ~20 min.
arp -an, ndp -an, sudo tcpdump -i en0 arp
6
TCP handshake. SYN → SYN/ACK → ACK; the Mac picks an ephemeral port ≥49152, agrees MSS/window scale/SACK/timestamps; optional TCP Fast Open and ECN (explicit congestion notification).
Leave the house via NAT. Your router rewrites source 192.168.1.20:52001 → its public address:port and remembers the mapping. Inbound connections don't work unless a port forward exists — this is why "my server isn't reachable" is usually the router.
curl -s ifconfig.me (your public address) vs ipconfig getifaddr en0 (your private one)
8
Cross the Internet. Each router decrements TTL, does its own longest-prefix match (BGP built those tables), forwards. ~10–20 hops is normal.
traceroute -n example.com; mtr (Homebrew) for a live version
9
TLS handshake. ClientHello (SNI = the hostname, sent in clear; ALPN = the application protocols offered, h2/http1.1) → server certificate chain → key exchange (X25519) → encrypted from here. TLS 1.3 does it in one round trip.
Send HTTP, get bytes. Request line + headers; response status + headers + body. HTTP/2 multiplexes streams on one TCP connection; HTTP/3 does it over QUIC/UDP.
Tear down. FIN/ACK each way; the side that closes first sits in TIME_WAIT for 2×MSL (30 s on macOS) so late packets can't confuse a new connection on the same port.
netstat -an | grep -c TIME_WAIT
Steps 1–2 are the link and network setup (P&D ch 2–3), 3 is naming (P&D §9.3, Dordal ch 10), 4–5 and 8 are routing (P&D §3.4, §4.1; Dordal ch 9, 13–15), 6 and 11 are TCP (P&D §5.2; Dordal ch 17), 9 is security (P&D ch 8; Dordal ch 28–29).
Watch one connection end to end
Everything in the table, live, in two terminals. Terminal 1 captures; terminal 2 makes one request.
sudo tcpdump -i en0 -nn -ttt 'host example.com or port 53 or arp'terminal 1 — DNS, ARP (if the gateway entry expired), SYN/SYN-ACK/ACK, TLS ClientHello, data, FINscurl -sv -o /dev/null -w '\ndns %{time_namelookup}s tcp %{time_connect}s tls %{time_appconnect}s first-byte %{time_starttransfer}s total %{time_total}s\n' https://example.com/terminal 2 — the same journey as a timing breakdown; each figure is cumulative from the start
A typical home result: DNS 20 ms, TCP +15 ms (one RTT), TLS +30 ms (one more RTT for 1.3, two for 1.2), first byte +20 ms. If dns is large, the resolver is slow or a search domain is being tried first. If tcp − dns is large, the path is long or a firewall is dropping SYNs (compare nc -zv host 443). If tls − tcp is large, the certificate chain is long or an OCSP revocation check is being made. If only first-byte is slow, it's the server.
Addressing
IPv4 and IPv6 addresses, what the special ranges mean when you see them in ifconfig, and enough subnet arithmetic to read a mask without a calculator.
IPv4 in one table
32 bits, written as four decimal octets. A prefix (/24) says how many leading bits identify the network; the rest identify the host. /24 = mask 255.255.255.0 = 256 addresses, of which the first (network) and last (broadcast) are reserved — 254 usable. macOS prints masks in hex: netmask 0xffffff00.
Prefix
Mask
Hosts
Prefix
Mask
Hosts
/32
255.255.255.255
1 (a host route)
/24
255.255.255.0
254
/30
255.255.255.252
2 (point-to-point)
/22
255.255.252.0
1022
/29
255.255.255.248
6
/20
255.255.240.0
4094
/28
255.255.255.240
14
/16
255.255.0.0
65,534
/27
255.255.255.224
30
/12
255.240.0.0
1,048,574
/26
255.255.255.192
62
/8
255.0.0.0
16,777,214
/25
255.255.255.128
126
/0
0.0.0.0
everything (the default route)
Rule of thumb: the "interesting octet" is the first one that isn't 255; its block size is 256 − mask value. 255.255.255.192 → blocks of 64: .0–.63, .64–.127, .128–.191, .192–.255. Two addresses are on the same subnet iff a & mask == b & mask.
Ranges you'll recognise
Range
Meaning
10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
Private (RFC 1918). Never routed on the Internet; behind NAT. Home routers love 192.168.0/1.x.
127.0.0.0/8
Loopback — lo0, this machine. All 16M addresses work, not only 127.0.0.1.
169.254.0.0/16
Link-local / APIPA. The Mac self-assigns one when DHCP fails — seeing this in ifconfig means "no DHCP answer".
100.64.0.0/10
Carrier-grade NAT (RFC 6598). Also what Tailscale hands out.
128 bits, eight groups of four hex digits, :: collapses one run of zeros, leading zeros drop. Every interface has several addresses at once, and that is normal. ifconfig en0 on a typical Mac shows: one link-local fe80::…%en0, a global autoconf address (SLAAC, from the router's prefix + a random/secured interface ID), one or more temporary ones (privacy addresses, RFC 8981: preferred for a day, valid for seven, chosen for new outbound connections — net.inet6.ip6.temppltime / tempvltime / prefer_tempaddr), and possibly a dynamic DHCPv6 one. Prefix length is almost always /64.
Range
Meaning
::1/128
Loopback.
::/0
Default route. :: alone = "any" (unspecified).
fe80::/10
Link-local. Every interface has one, automatically, always. Ambiguous across interfaces, hence the scope id suffix: fe80::1%en0. ping6 fe80::1%en0 works; without %en0 it doesn't.
2000::/3
Global unicast — the public Internet. Your ISP delegates you a /56 or /64.
fc00::/7 (in practice fd00::/8)
Unique local (ULA) — the private-address equivalent. Random 40-bit global ID, so two sites rarely collide.
ff00::/8
Multicast. ff02::1 all nodes on link, ff02::2 all routers, ff02::fb mDNS, ff02::1:ffXX:XXXX solicited-node (used by NDP instead of broadcast).
::ffff:a.b.c.d
IPv4-mapped. What an IPv4 client looks like to a dual-stack AF_INET6 listening socket. netstat shows them.
64:ff9b::/96
NAT64 well-known prefix. IPv6-only networks (many mobile carriers) reach IPv4 through it; macOS's getaddrinfo synthesises these automatically.
2001:db8::/32
Documentation prefix.
What replaced what: ARP → Neighbor Discovery (ICMPv6 types 133–137, seen with ndp -an); DHCP's "who is my router?" → Router Advertisements (ipconfig getra en0); broadcast → multicast; fragmentation by routers → only by the sender (so Path MTU discovery is mandatory and blocking ICMPv6 breaks IPv6); header checksum → gone (the link and transport layers have their own); NAT → unnecessary, every host is globally addressable (your firewall does the blocking instead). Happy Eyeballs: apps try v6 and v4 nearly simultaneously and keep whichever answers first, so a broken IPv6 path shows up as a mysterious 50–250 ms delay rather than a failure.
Administratively enabled. RUNNING = link actually detected (cable in / associated). UP without RUNNING = no link.
SIMPLEX
Can't hear its own transmissions (normal for Ethernet/Wi‑Fi). SMART and CHANNEL_IO are Apple driver flags; PROMISC appears while tcpdump runs.
options=
Hardware offloads: TSO (TCP segmentation), checksums. Why tcpdump on the sender shows bad checksums — the NIC fills them in later.
ether
The MAC. On Wi‑Fi this may be a per-network private address (System Settings → Wi‑Fi → Private Wi‑Fi address) — not the hardware one, which is networksetup -getmacaddress en0.
secured
Apple's stable-privacy interface ID (RFC 7217) instead of one derived from the MAC.
temporary / deprecated
Privacy addresses (RFC 8981): rotated; deprecated ones are still valid for existing connections but no longer chosen for new ones.
nd6 options
PERFORMNUD = neighbour unreachability detection, DAD = duplicate address detection, IFDISABLED = IPv6 turned off on this interface.
media / status
Negotiated link. Ethernet shows e.g. 1000baseT <full-duplex>; Wi‑Fi says autoselect. status: inactive = no link.
Interface names on a Mac: lo0 loopback · en0 Wi‑Fi on laptops, built-in Ethernet on desktops · en1… Thunderbolt/USB Ethernet, Thunderbolt bridge members · bridge0 Thunderbolt bridge · bridge100+ Internet Sharing and VM/container shared networking (the vmnet framework) · vmenetN one running guest's tap, a member of that bridge — both appear and vanish with the guest · awdl0 Apple Wireless Direct Link (AirDrop, Sidecar) · llw0 low-latency WLAN (AWDL sibling) · ap1 the Wi‑Fi card as an access point (Internet Sharing / hotspot) · anpi0/1 Apple debug/"network-over-USB" pipes · utunN tunnels (VPNs, iCloud Private Relay, Tailscale — several exist on a Mac with no VPN installed) · gif0, stf0 legacy tunnel stubs · pktapN capture pseudo-interface · vlanN, bondN, fethN (fake Ethernet pair for testing) as created.
Link Layer & Wi‑Fi
Ethernet, ARP, switches and VLANs; then 802.11 — the one link where the physics leaks into your day. Most "the network is slow" reports on a laptop are a link-layer problem.
Ethernet, ARP, switches
An Ethernet frame is destination MAC, source MAC, EtherType (0x0800 IPv4, 0x86DD IPv6, 0x0806 ARP, 0x8100 VLAN tag), payload, CRC. MACs are 48 bits, the first 24 an OUI identifying the vendor (3c:22:fb = Apple). A switch learns which port each source MAC appeared on and forwards frames only there; unknown destinations and broadcasts flood every port. A hub (extinct) repeated everything everywhere; Wi‑Fi behaves like a hub with a polite shared radio.
ARP maps an on-link IPv4 address to a MAC: broadcast "who-has 192.168.1.1", unicast "is-at aa:bb:…". Cached for net.link.ether.inet.max_age=1200 s. Poison it and you own the LAN — hence ARP spoofing, and hence the arp -s … reject options. IPv6's NDP does the same job with multicast solicitations to ff02::1:ff… and adds router discovery and duplicate address detection (DAD — the Mac probes for its own address before using it).
VLANs (802.1Q) put a 4-byte tag with a 12-bit VLAN ID in the frame so one physical switch carries many logical LANs. A Mac joins a tagged VLAN with networksetup -createVLAN (creates vlan0 on top of en1). Link aggregation (802.3ad/LACP) bonds several Ethernet ports into bond0. A bridge (bridge0) makes the Mac behave as a switch between its members — how Thunderbolt Bridge and Internet Sharing work.
arp -anthe ARP cache: who's on the LAN (that you've talked to)sudo arp -d -aflush it (after changing a router/NIC)ndp -anIPv6 neighbour cachendp -rnwhich router(s) sent RAssudo tcpdump -i en0 -e -nn arp or icmp6watch discovery live; -e prints MACsifconfig bridge0members and learned MACs (needs -v for the table)networksetup -listVLANs; networksetup -listBondspersistent virtual links
Wi‑Fi is a shared half-duplex radio: only one station on a channel transmits at a time, everyone waits for silence (CSMA/CA), and every frame is acknowledged. That makes three things true. Airtime is the scarce resource — a slow far-away device drags everyone down. Latency jitters (retries, contention). And advertised rates are physical-layer rates; throughput is at best ~60% of them.
Field (wdutil info)
Meaning
RSSI
Received signal, dBm (negative; closer to 0 is stronger). −50 is excellent, −67 is the usual "good enough for voice/video" line, −75 you'll roam, −85 you'll drop.
Noise
Background RF, dBm; −90 to −95 is clean. Microwave ovens, neighbours and Bluetooth raise it.
SNR
RSSI − noise. Determines the modulation (MCS) the card dares to use: <15 dB = lowest rates, >30 dB = top rates.
Channel / width
2.4 GHz: 1–13, only 1/6/11 don't overlap, 20 MHz. 5 GHz: 36–165, 20/40/80/160 MHz; DFS channels (52–144, radar-detecting) must vacate when radar appears. 6 GHz (Wi‑Fi 6E/7): 1–233, up to 320 MHz, clean but short range.
PHY mode
802.11n = Wi‑Fi 4 · ac = 5 · ax = 6/6E · be = 7. Later = more bits per symbol, wider channels, more spatial streams, OFDMA (ax) so many small clients share one transmission.
Tx rate / MCS
Current negotiated PHY rate and its modulation-coding index. Drops as SNR drops. 1 stream 80 MHz ax ≈ 600 Mb/s max; 2 streams ≈ 1200.
CCA
Clear-channel-assessment busy %; how much of the airtime someone else is using. >50% and it doesn't matter how strong your signal is.
Security
WPA2 Personal (AES-CCMP), WPA3 Personal (SAE — no offline dictionary attack), Enterprise (802.1X/EAP with a RADIUS server, per-user credentials). WEP and TKIP are broken; macOS warns.
Roaming is the client's decision, not the AP's: the Mac scans when RSSI sinks past about −75 and jumps if another access point (BSSID) is ≥ ~12 dB better. 802.11k/v/r help it find and pre-authenticate. Private Wi‑Fi address (default on) gives each SSID its own random MAC — expect a different ether per network and DHCP reservations keyed on the hardware MAC to fail.
sudo wdutil infoeverything above, plus power/IPv4/IPv6/DNS summarysystem_profiler SPAirPortDataTypesame fields, no sudo, plus every other visible networknetworksetup -setairportpower en0 off; networksetup -setairportpower en0 onthe fastest resetnetworksetup -listpreferredwirelessnetworks en0the remembered-networks list, in priority ordersudo wdutil diagnosethe full report bundle (what Apple support asks for)
SSID from the shell.airport was removed in 14.4. Since 15, networksetup -getairportnetwork says "not associated" and wdutil info prints <redacted> unless the calling app has Location Services (an SSID locates you). On 26.6, ipconfig getsummary en0 | awk '/ SSID/{print $NF}' and system_profiler -json SPAirPortDataType (jq path in the Wi‑Fi index) still return it; otherwise grant the terminal app Location Services.
Longest prefix wins. A packet to 192.168.1.50 matches both default (/0) and 192.168.1 (/24); the /24 is more specific, so it's delivered on-link via en0. Anything else falls to default and the gateway's MAC. Routes whose gateway is link#N are directly connected networks (no next hop — ARP for the destination itself). Rows with a MAC as gateway are ARP-cache entries surfaced as host routes (L), with an Expire countdown in seconds.
Flag
Meaning
U
Up / usable
G
Gateway — forward to the next hop rather than delivering directly
H
Host route (a single address, /32)
S
Static — added manually or by configd, not learned from a protocol
C / c
Cloning: this route spawns per-host entries on use (the ARP/route cache); c = protocol-cloned
L
Link-layer address present (an ARP/NDP entry)
W
Was cloned — created from a C route
I
Interface-scoped (-ifscope): only used by sockets bound to that interface. i = with an interface-scoped gateway
g
Global — the primary default route among several (multi-homed Macs have one UGScg and other defaults marked I)
m
Multicast
R / r
Reject / router: R route returns "host unreachable"; lowercase r = the entry is a router's
B / b
Blackhole (drop silently) / broadcast
!
(Expire column) never expires
macOS is unusual in having per-interface scoped routes: with Wi‑Fi and Ethernet both up you'll see two defaults; the g one belongs to the top service in networksetup -listnetworkserviceorder. That service order — not metrics — decides which link ordinary traffic uses. route -n get 8.8.8.8 is the definitive answer to "which interface and gateway will this packet use?"
Your Mac's table is trivial: on-link prefixes from its addresses, a default from DHCP/RA, plus whatever a VPN pushed. Routers run routing protocols to build theirs:
Distance vector (RIP, and Bellman-Ford in the books): each router tells neighbours "I can reach X at cost N"; slow to converge, count-to-infinity problem. Nobody serious runs RIP any more.
Link state (OSPF, IS-IS): every router floods a map of its links; each runs Dijkstra on the whole map. Used inside an organisation (an autonomous system, AS).
Path vector (BGP): between ASes. Advertises prefixes with the full AS path; routing is by policy (money, contracts) more than distance. ~1M IPv4 prefixes in the global table; a BGP mistake is how whole countries fall off the Internet. whois AS15169 tells you who an AS is.
ICMP is IP's error and diagnostic channel: echo request/reply (ping), destination unreachable (with codes: net, host, port, fragmentation needed — Path MTU discovery depends on this one), time exceeded (traceroute's trick: send with TTL=1, 2, 3… and read who complains), redirect ("use the other router"; macOS drops incoming IPv4 redirects by default — net.inet.icmp.drop_redirect=1 — but accepts IPv6 ones, net.inet6.icmp6.rediraccept=1; net.inet.ip.redirect only governs sending them when forwarding). Firewalls that drop all ICMP break PMTU and make debugging miserable.
NAT isn't routing but lives on the same box. Port-address translation keeps a table (private ip:port ↔ public ip:port, plus destination for symmetric NATs). Consequences: inbound needs a forward or UPnP; two hosts behind different NATs need NAT traversal — STUN to learn their public mappings, hole-punching, a TURN relay as last resort (FaceTime, WebRTC, Tailscale all do this); the NAT drops idle UDP mappings after 30–120 s and TCP after minutes-to-hours, which is why SSH sessions die and ServerAliveInterval exists.
traceroute -n 1.1.1.1UDP probes, TTL 1..30; * = no ICMP back from that hopsudo traceroute -n -P icmp 1.1.1.1ICMP echo probes — gets through more firewallstraceroute6 -n 2606:4700:4700::1111the v6 path (often different)sudo route -n add -net 10.8.0.0/16 192.168.1.5static route; gone at rebootnetworksetup -setadditionalroutes Wi-Fi 10.8.0.0 255.255.0.0 192.168.1.5the persistent versionsudo sysctl net.inet.ip.forwarding=1make the Mac a router (Internet Sharing does this + pf NAT)route -n monitorwatch route/interface changes as they happen (VPN debugging)
Ports, the handshake, the state machine you see in netstat, and why TCP slows down on purpose. The one layer where the textbook diagrams map exactly onto kernel counters you can read.
UDP vs TCP, and what QUIC changed
UDP is IP plus ports and a checksum: 8-byte header, no connection, no ordering, no retransmission, no flow control. Perfect when the application would rather drop than wait — DNS, mDNS, DHCP, NTP, VoIP, games — and as a substrate for protocols that do their own reliability (QUIC, WireGuard). A UDP "connection" in netstat is just a bound socket.
TCP turns IP into a reliable, ordered byte stream. It numbers every byte (sequence numbers), acknowledges cumulatively, retransmits on timeout or three duplicate ACKs (fast retransmit), and slices the stream into segments of at most the MSS (1460 on Ethernet). Two independent brakes limit how much it sends: the receiver's advertised window (flow control — don't overrun the peer) and the sender's congestion window (congestion control — don't overrun the network). Throughput ≈ min(cwnd, rwnd) / RTT.
QUIC (HTTP/3) is TCP's ideas rebuilt on UDP inside TLS 1.3: streams that don't head-of-line-block each other, 0-RTT resumption, connection migration when your IP changes (Wi‑Fi → cellular), and encrypted headers so middleboxes can't ossify it. Safari, Chrome and networkQuality -f h3 all use it; tcpdump udp port 443 shows it.
Well-known ports you'll see
Port
Service
Port
Service
22/tcp
SSH, SFTP, scp
443/tcp+udp
HTTPS (udp = HTTP/3/QUIC)
53/udp+tcp
DNS
445/tcp
SMB (file sharing)
67/68 udp
DHCP server / client
548/tcp
AFP (legacy Apple file sharing)
80/tcp
HTTP
853/tcp
DNS over TLS
123/udp
NTP (macOS uses timed + sntp)
2049/tcp
NFS
137–139 udp/tcp
NetBIOS (SMB discovery, legacy)
3283/tcp+udp
Apple Remote Desktop
5353/udp
mDNS / Bonjour
5900/tcp
VNC / Screen Sharing
7000, 7100/tcp
AirPlay
49152–65535
Ephemeral (client side) range on macOS
/etc/services holds 13,900 lines of these; grep -w 445 /etc/services. Ports below 1024 need root to bind (EACCES).
Server socket waiting for SYNs. *.22 = all addresses.
Normal. lsof -iTCP -sTCP:LISTEN -nP to see who.
SYN_SENT
Client sent SYN, no SYN/ACK yet.
Firewall dropping, host down, or port filtered. Times out after ~75 s (net.inet.tcp.keepinit).
SYN_RCVD
Server got SYN, sent SYN/ACK, waiting for the ACK.
Many = SYN flood or asymmetric routing.
ESTABLISHED
Handshake done, data flows.
What you want.
FIN_WAIT_1 / FIN_WAIT_2
We closed first; waiting for their FIN.
Stuck in FIN_WAIT_2 = peer never closed its side (leaky server).
CLOSE_WAIT
Peer closed; our app hasn't called close() yet.
Piling up = a bug in the local program (fd leak).
LAST_ACK
We sent our FIN after theirs; waiting for the final ACK.
Brief.
TIME_WAIT
Closed; lingering 2×MSL = 30 s on macOS so stray segments die.
Thousands are fine; they're the price of being the side that closed first.
CLOSED
Gone; rarely displayed.
—
The flags in a capture tell the same story: S SYN, S. SYN/ACK, . ACK, P. data pushed, F. FIN, R reset. A RST in reply to a SYN = "nothing listening" (ECONNREFUSED); silence = "dropped" (ETIMEDOUT). That single difference tells you whether a firewall is in the way.
netstat -an -p tcp | awk '{print $6}' | sort | uniq -chistogram of statesnetstat -anL -p tcplisten-queue depth per listener (backlog full ⇒ dropped SYNs)sudo netstat -s -p tcp | grep -Ei 'retrans|dup|out-of-order|timeout'the counters that mean trouble (root, or they read 0)nettop -m tcp -d -j rtt_avg,re-txper-connection RTT and retransmits, livesysctl net.inet.tcp | grep -E 'msl|keep|cubic|sendspace|recvspace|ecn'the knobs; macOS uses CUBIC
Congestion control in a paragraph
The network gives no explicit signal that it's full (except ECN, when enabled — macOS turns it on, net.inet.tcp.ecn_initiate_out=1), so TCP infers congestion from loss and, in newer algorithms, from delay. Classic Reno: start with a tiny window and double it every RTT (slow start) until loss; then halve the window and grow it by one MSS per RTT (additive increase, multiplicative decrease). The sawtooth you see in throughput graphs is this. CUBIC (macOS and Linux default) grows the window as a cubic function of time since the last loss, so it fills fat pipes faster and is fair to Reno. BBR (Google) instead models bandwidth and RTT and paces to them, ignoring random loss — better on lossy Wi‑Fi. Apple's L4S (Low Latency, Low Loss, Scalable throughput — networkQuality -f L4S; net.inet.tcp.l4s, off by default on 26.6) is the ECN-based successor: routers mark instead of drop, and senders react in one RTT.
Bufferbloat is the failure mode you'll actually hit: a big queue in your router or cable modem absorbs the burst instead of dropping, so TCP never sees loss, the queue stays full, and every other flow (your video call) waits behind it — 500 ms+ of latency only while a download is running. networkQuality reports exactly this as responsiveness (RPM): round trips per minute under load; <1000 is bufferbloat. The fix is a smart queue (fq_codel / CAKE) in the router.
Nagle and delayed ACK: Nagle coalesces tiny writes; delayed ACK waits ~40 ms to piggyback an ACK. Together they cause the famous 40–200 ms stall on request/response protocols that write headers and body separately — hence TCP_NODELAY on every interactive protocol, and net.inet.tcp.delayed_ack on macOS.
How a name becomes an address — on the Internet, and on a Mac, where the answer is "not the way Linux does it".
DNS: the protocol
A distributed, hierarchical, cached database. Names are read right to left: www.example.com. — the trailing dot is the root. Thirteen root server clusters know who runs com; the com servers know who runs example.com; its authoritative servers hold the records. Your recursive resolver (your router, your ISP, 1.1.1.1, 8.8.8.8, 9.9.9.9) walks that chain on your behalf and caches each answer for its TTL. Your Mac's stub resolver (mDNSResponder) caches too. dig +trace shows the walk.
Record
Holds
A / AAAA
IPv4 / IPv6 address. The pair is why "dual-stack" works.
CNAME
Alias to another name (a chain, resolved by the server). Can't coexist with other records at the same name — hence no CNAME at a zone apex.
MX
Mail exchangers, with priority.
NS
Authoritative name servers for a zone (the delegation).
SOA
Zone metadata: primary server, admin, serial, refresh/retry/expire, negative-cache TTL.
TXT
Arbitrary text — SPF, DKIM, DMARC, domain-verification tokens.
PTR
Reverse lookup, under in-addr.arpa / ip6.arpa. Also used by DNS-SD for service browsing.
SRV
Service location: priority, weight, port, target. Used by SIP, XMPP, LDAP, Minecraft, and every Bonjour service.
HTTPS / SVCB
Newer: tells clients about HTTP/3 support and ECH keys before they connect. Apple platforms query it.
CAA · DNSKEY · RRSIG · DS
Which CAs may issue certs · DNSSEC signing keys, signatures and delegation hashes.
Transport is UDP 53, with TCP 53 for answers over the EDNS size and for zone transfers (AXFR). DoT (TCP 853) and DoH (HTTPS) encrypt the resolver hop; macOS supports both via a configuration profile or an app's NEDNSSettings — there's no networksetup switch. Response codes: NOERROR, NXDOMAIN (name doesn't exist), SERVFAIL (resolver failed), REFUSED. A NOERROR answer with zero records means the name exists but has no record of that type.
On Linux, libc reads /etc/resolv.conf and /etc/nsswitch.conf. On macOS neither exists in a form that matters. Every getaddrinfo() call — Safari, curl, ssh, ping, Python — goes over XPC (Apple's local IPC) to mDNSResponder, which is at once the multicast (Bonjour) responder and the unicast stub resolver and cache. It consults, in effect:
/etc/hosts — still honoured, read by mDNSResponder itself.
Its cache.
A list of resolver "clients", each scoped to a domain suffix and/or interface: the primary one from DHCP or Network Settings; /etc/resolver/<domain> files you create; resolvers pushed by a VPN for its domains (split DNS); the local client that turns *.local into multicast. The query goes to the client whose domain matches the most trailing labels. scutil --dns prints exactly this list — it is the truth.
Search domains (from DHCP or networksetup -setsearchdomains), appended only to single-label names by default.
The trap:dig, host and nslookup are BIND tools that read /etc/resolv.conf (a generated symlink to /var/run/resolv.conf holding only the primary resolver) and talk to a server directly. They bypass everything above: no /etc/hosts, no /etc/resolver/*, no VPN split DNS, no .local, no cache. So dig pi.local fails while ping pi.local works, and "dig says X but Safari goes to Y" is expected. Use dig to interrogate a server; use dns-sd -G v4v6 name or dscacheutil -q host -a name name to see what an app will get.
scutil --dnsevery resolver client, its servers, domain, interface and search listdns-sd -G v4v6 example.comresolve the way apps do (Ctrl-C to stop)dscacheutil -q host -a name example.comsame, one-shotdig +short example.com; dig @1.1.1.1 example.comask the configured server / ask a specific serversudo dscacheutil -flushcache; sudo killall -HUP mDNSResponderthe cache flush (both halves)printf 'nameserver 192.168.1.1\n' | sudo tee /etc/resolver/home.arparoute *.home.arpa to the home router's DNS; appears in scutil --dns within secondsnetworksetup -setdnsservers Wi-Fi 1.1.1.1 9.9.9.9override DHCP's DNS for this service (persistent); "empty" to revertlog stream --predicate 'process == "mDNSResponder"' --infowatch it think
Bonjour: mDNS + DNS-SD
Zero-configuration networking, in two RFCs. mDNS (RFC 6762): DNS packets sent to multicast 224.0.0.251:5353 / ff02::fb; every host answers for its own hostname.local. No server. DNS-SD (RFC 6763): a naming convention on top — browse PTR records for _ssh._tcp.local, get instance names like nas._ssh._tcp.local, resolve each to an SRV (host + port) and a TXT (key=value metadata). Works over ordinary unicast DNS too ("wide-area Bonjour") if someone publishes the records.
Everything Apple discovers this way: printers (_ipp._tcp), AirPlay (_airplay._tcp, _raop._tcp), file shares (_smb._tcp, _afpovertcp._tcp), screen sharing (_rfb._tcp), SSH (_ssh._tcp, which is why Remote Login hosts appear in Finder), HomeKit (_hap._tcp), Time Machine (_adisk._tcp), Sleep Proxy (_sleep-proxy._udp — the Apple TV that answers ARP for a sleeping Mac). Your Mac's own .local name is scutil --get LocalHostName.
dns-sd -B _services._dns-sd._udpevery service type on this LAN (the meta-query)dns-sd -B _ssh._tcpbrowse one type; -L "name" _ssh._tcp local to resolve an instancedns-sd -R "My Site" _http._tcp . 8000 path=/advertise a service (shows in Safari's Bonjour bookmarks); Ctrl-C withdrawsdns-sd -q nas.localmDNS lookup, stays open for changesping6 -I en0 ff02::1make every IPv6 host on the link answer (crude inventory)sudo tcpdump -i en0 -nn udp port 5353watch the multicast chatter
mDNS is link-local: it doesn't cross routers (a "Bonjour gateway" or mDNS reflector is needed across VLANs), and Wi‑Fi APs with "client isolation" or multicast filtering silently kill it — the usual reason a printer or Chromecast "disappears". Names collide gracefully: a second studio.local becomes studio-2.local.
Local Network privacy. Since macOS 15, an app's first unicast or multicast send to a LAN address raises a Local Network prompt — a TCC decision (Apple's privacy-consent database), listed under System Settings → Privacy & Security → Local Network; apps state their reason in NSLocalNetworkUsageDescription. Command-line tools inherit the grant of the terminal app that launched them, so a script that sees no Bonjour results while Safari does usually means Terminal (or whatever spawned it) was denied.
Config Plumbing
Why ifconfig changes vanish, who owns DNS, where the settings really live, and the three commands (networksetup, scutil, ipconfig) that Linux has no equivalent for.
configd and the two stores
On macOS the kernel's interfaces, addresses and routes are derived state. The source of truth is configd, the System Configuration daemon, which keeps two stores:
Setup: — your preferences: locations ("sets"), services (Wi‑Fi, Ethernet, VPN…), each with IPv4/IPv6/DNS/proxy config. Persisted in /Library/Preferences/SystemConfiguration/preferences.plist. Edited by System Settings and networksetup.
State: — what is actually in effect right now: per-interface addresses, the DHCP lease, the active DNS list, the primary interface, reachability. Rebuilt from Setup: plus DHCP/RA/VPN input by configd's agents (IPConfiguration, IPMonitor, KernelEventMonitor, InterfaceNamer). Read with scutil.
Whenever anything in Setup: changes — you switch location, plug in Ethernet, a VPN comes up, DHCP renews — configd re-derives State: and pushes it into the kernel, which is why a hand-made ifconfig en0 alias, route add or hostname change disappears "randomly". They were never in Setup:. The hierarchy of durability:
Want to change…
Transient (kernel now)
Persistent (survives reboot & reconfig)
Address / DHCP
ifconfig en0 inet …, sudo ipconfig set en0 DHCP
networksetup -setmanual / -setdhcp
Extra routes
route add
networksetup -setadditionalroutes
DNS servers, search domains
— (configd owns them)
networksetup -setdnsservers / -setsearchdomains, or /etc/resolver/* per domain
a LaunchDaemon that runs sysctl at boot (no /etc/sysctl.conf is read)
Which link wins
—
networksetup -ordernetworkservices (service order, not metrics)
Three names, three purposes: HostName is what hostname and SSH see; LocalHostName is the Bonjour .local name; ComputerName is the pretty one in Finder and AirDrop. DHCP can override HostName unless you set it explicitly.
DHCP on a Mac: ipconfig
Not the Windows command. Apple's ipconfig talks to configd's IPConfiguration agent — the DHCP/BOOTP/SLAAC/DHCPv6 client. It's the place to see the actual lease and the actual Router Advertisement, and to force a renewal.
ipconfig getifaddr en0my IPv4 on en0 (empty if none)ipconfig getpacket en0the DHCP ACK: server, lease time, router, DNS, domain, every optionipconfig getoption en0 router; ipconfig getoption en0 domain_name_serverone optionipconfig getra en0the last IPv6 Router Advertisement (prefix, lifetimes, flags)ipconfig getsummary en0everything: lease, RA, DHCPv6, and (still, on 26.6) the Wi‑Fi SSID/BSSIDsudo ipconfig set en0 DHCPforce a new lease now (creates a temporary service; the next reconfig restores Setup:)sudo ipconfig set en0 NONE-V6kill IPv6 on en0 until the next reconfig — the quick Happy-Eyeballs testsudo scutil --renew en0ask configd to re-evaluate the interface (gentler than down/up)
The DHCP client identifies itself by the interface MAC — which, on Wi‑Fi with Private Address on, differs per SSID. Reservations on your router must use the address shown in ifconfig en0 | grep etherwhile connected to that network. A 169.254.x.x address means four DISCOVERs went unanswered; sudo tcpdump -i en0 -nn port 67 or port 68 shows whether anything replies.
macOS has no docker0, no veth pairs and no network namespaces. Every VM and every container on a Mac is a guest of Virtualization.framework, and its networking comes from vmnet — the same framework Internet Sharing uses. In the usual shared (NAT) mode vmnet gives the host a bridge, bridge100, puts the gateway address on it, answers DHCP and DNS there, and NATs the guests out through whatever the primary interface is. Each guest's tap joins that bridge as vmenetN. Both exist only while a guest is running: start one container and ifconfig -l grows vmenet0 bridge100; stop the last one and they disappear again. That is why they are missing when you go looking for them, and why bridge100 can belong to Internet Sharing one day and to a container the next.
Apple's container (brew install container; 1.3.1 here) is the unusual one: it boots one lightweight Linux VM per container rather than one VM shared by all of them. So each container is a separate host on the vmnet subnet with its own MAC and IP, and the Mac reaches it directly — no port publishing, no exec gymnastics to poke at a service:
container lsthe IP column is the real address — 192.168.64.2, .3, .4… one per running containerping 192.168.64.2 ; curl http://192.168.64.2/straight from the Mac, unpublished ports includedifconfig bridge100192.168.64.1, with each running container's vmenetN as a member and its MAC in the address cachecontainer network inspect defaultsubnet, gateway, IPv6 prefix, mode: nat, plugin: container-network-vmnetcontainer inspect <name>that container's MAC, v4/v6 address, gateway and MTUcontainer run -p 18080:80 …publish to the host — a userspace listener inside the container process (lsof -nP -iTCP:18080), not a pf rdr rule
Where
What it is on 1.3.1 / macOS 26.6
The network
default, 192.168.64.0/24, gateway 192.168.64.1, plus a ULA IPv6 /64. NAT mode, vmnet plugin. container network create adds more; --internal makes one host-only, --subnet/--subnet-v6 pick the ranges.
Host side
bridge100 holds 192.168.64.1; members vmenet0, vmenet1… one per running container, flagged LEARNING,DISCOVER,VIRTIO. Nothing lands in netstat -rn beyond the on-link route.
Guest side
eth0 with the /24 address, default route to .1, /etc/resolv.conf = nameserver 192.168.64.1, its own name in /etc/hosts, MTU 1280 (raise it with --network default,mtu=1500).
Who reaches whom
Mac → container: any port, directly. Container → Internet: NAT, works out of the box. LAN → container: no — that is what -p is for, and -p binds * unless you give it a host IP.
Names
A container's name resolves only inside itself. sudo container system dns create <domain> registers a local domain so the Mac can resolve containers by name.
Each piece is a launchd agent, so launchctl list | grep container is a census: com.apple.container.apiserver, …container-network-vmnet.default, and one …container-runtime-linux.<name> per running container. container system status says whether the API server is up ("not running and not registered with launchd" before the first container system start); container system stop parks the lot. Docker Desktop, Colima and podman machine are the other shape: one Linux VM, every container on a bridge inside it and invisible from the Mac, reachable only through published ports on localhost — and reaching back to you as host.docker.internal. A tutorial that tells you to publish a port just to curl your own container is written for that shape.
Firewalls
Two of them. pf is the BSD packet filter, off by default, driven by rules; the Application Firewall is the one in System Settings, driven by which program may accept connections. They stack.
The two firewalls
pf (packet filter)
Application Firewall (ALF)
Decides by
addresses, ports, protocol, interface, direction, TCP flags, state
the code signature of the program that owns the socket
Direction
in and out
inbound only — it never blocks an app from connecting out
Default
loaded at boot (com.apple.pfctl), not enabled; Apple services enable it on demand with reference counts
enabled on most Macs; "Block all incoming" and Stealth mode are its options
Control
sudo pfctl, /etc/pf.conf, anchors under /etc/pf.anchors/
/usr/libexec/ApplicationFirewall/socketfilterfw, System Settings → Network → Firewall
Used by Apple for
Internet Sharing NAT, AirDrop, VPN, ALF's own rules — all injected into anchors under com.apple/
the GUI firewall
Logs
pflog0 interface (create it, then tcpdump -i pflog0)
pf rule evaluation: last matching rule wins, unless a rule says quick. Every pass keeps state by default, so replies to allowed traffic are allowed automatically — you write rules for the first packet. Translation (nat, rdr) happens before filtering. Never pfctl -f a file that drops Apple's anchor lines — you'd yank NAT out from under Internet Sharing. Put your rules in your own anchor.
# /etc/pf.anchors/local — block all inbound on Wi‑Fi except SSH and ping, allow all outbound
ext_if = "en0"
set block-policy drop
block in log on $ext_if all
pass out on $ext_if all keep state
pass in on $ext_if proto tcp to ($ext_if) port 22 flags S/SA keep state
pass in on $ext_if inet proto icmp icmp-type echoreq
# rate-limit SSH brute force: >5 connections in 30 s ⇒ into the table, blocked
table <bruteforce> persist
block in quick from <bruteforce>
pass in on $ext_if proto tcp to ($ext_if) port 22 keep state (max-src-conn-rate 5/30, overload <bruteforce> flush global)
sudo pfctl -nf /etc/pf.anchors/localsyntax checksudo pfctl -a local -f /etc/pf.anchors/localload into anchor "local" (main ruleset untouched); add anchor "local" + load anchor lines to /etc/pf.conf to persistsudo pfctl -Eenable with a reference (note the token); sudo pfctl -X token to releasesudo pfctl -si | head -3; sudo pfctl -a '*' -srstatus; every rule in every anchorsudo pfctl -ss | grep :22; sudo pfctl -t bruteforce -T showstates; table contentssudo ifconfig pflog0 create; sudo tcpdump -n -e -ttt -i pflog0see what "log" rules caught
Application Firewall, Internet Sharing, and what "stealth" means
/usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate --getblockall --getstealthmode --getallowsignedcurrent posture (no sudo to read)sudo … --setglobalstate on --setstealthmode onhardensudo … --add /opt/homebrew/bin/node --unblockapp /opt/homebrew/bin/nodelet a Homebrew binary accept connections without the popup… --listappsevery registered program and its Allow/Blocksystem_profiler SPFirewallDataTypethe same, as a report
Stealth mode drops ICMP echo and unsolicited probes — your Mac stops answering ping and looks absent to nmap. It also stops traceroute to your Mac from finishing and confuses "is it up?" checks; turn it off while debugging. Signed software is auto-allowed by default (--getallowsigned): the popup you get for a fresh python3 server is because ad-hoc-signed binaries don't count.
Internet Sharing (System Settings → General → Sharing) turns the Mac into a NAT router: net.inet.ip.forwarding=1, a nat on rule loaded into a com.apple pf anchor, bootpd as the DHCP server on bridge100 (192.168.2.0/24 by default), and — when sharing to Wi‑Fi — the card in AP mode as ap1. It rewrites /etc/bootpd.plist every start; fixed leases go in /etc/bootptab. sudo pfctl -a 'com.apple/*' -sn shows the NAT rule it installed.
Security & VPN
TLS as it appears in curl -v, the certificate chain and where macOS keeps trust, SSH keys, and what a VPN actually does to your routing table.
TLS in the time it takes to read curl -v
TLS gives a TCP (or QUIC) connection three things: confidentiality (symmetric encryption, AES-GCM or ChaCha20), integrity (an authentication tag — AEAD — on every record), and authentication of the server (a certificate chain to a root you trust) — optionally of the client too. The handshake, TLS 1.3 style: ClientHello carries the supported versions/ciphers, a key share (X25519), the SNI (server name, in clear — this is how one IP hosts many sites and what Encrypted Client Hello (ECH) hides), and ALPN (h2 / http/1.1 / h3). ServerHello picks, sends its certificate chain and a signature over the transcript, and both sides derive keys. One round trip; TLS 1.2 needed two.
A certificate binds a public key to names (the Subject Alternative Names — CN alone hasn't counted since 2017), signed by an intermediate CA, itself signed by a root CA whose self-signed cert ships in the OS trust store. Validation checks: the chain of signatures, the dates, the hostname against a SAN, revocation (OCSP, often stapled), and policy (Apple rejects leaf certs valid > 398 days from public CAs, and > 825 days for private ones). Let's Encrypt made 90-day certs normal; expiry is the #1 cause of "suddenly nothing works".
Where macOS keeps trust: the System Roots keychain (Apple's list), the System keychain (admin-added), and your login keychain (user-added). Apple's own TLS stack (Safari, curl — built on SecureTransport, URLSession) honours all three; Homebrew tools do not — OpenSSL-based curl, Python, Node use /etc/ssl/cert.pem or their own bundle. So a private CA must be added twice: security add-trusted-cert for Apple's stack, SSL_CERT_FILE/--cacert for the rest.
curl -v https://example.com -o /dev/null 2>&1 | grep -E 'SSL|TLS|subject|issuer|expire|ALPN'which version, cipher, cert and ALPN were negotiatedopenssl s_client -connect host:443 -servername host -showcerts </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -datesthe leaf cert's issuer and expiry; for its names add -text | grep -A1 'Subject Alternative' (-ext subjectAltName is OpenSSL 3 only)security verify-cert -v https://example.comevaluate with the macOS trust store, exactly as Safari wouldopenssl x509 -in cert.pem -noout -checkend 2592000 || echo 'expires within 30 days'monitoring one-lineropenssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -days 825 -keyout key.pem -out cert.pem -subj '/CN=pi.local' -addext 'subjectAltName=DNS:pi.local,IP:192.168.1.50'self-signed cert with a SAN for a LAN devicesudo security add-trusted-cert -d -r trustRoot -p ssl -k /Library/Keychains/System.keychain ca.pemtrust a private CA machine-wide (Apple stack); Homebrew tools still need SSL_CERT_FILE
SSH is TLS's older cousin with a different trust model: no CAs (by default), just known_hosts — trust on first use, then scream if the host key changes. Authentication is by password or by a key pair; the private key never leaves your machine, the agent signs challenges with it. Use ed25519 keys (short, fast, no parameter choices; ssh-keygen's default). OpenSSH 10.3 puts the post-quantum hybrid mlkem768x25519-sha256 first in its key-exchange list (ssh -Q kex; ssh -G host | grep kex shows what applies) and warns when a server can't do it — WarnWeakCrypto no-pq-kex silences that. UpdateHostKeys is on by default, so a server rotating its host keys updates known_hosts quietly instead of triggering the changed-key warning.
The Mac specifics. An agent is already running — launchd starts ssh-agent on demand and sets SSH_AUTH_SOCK to /var/run/com.apple.launchd.*/Listeners; running eval $(ssh-agent) yourself creates a second, keychain-unaware one. Passphrases can live in the Keychain: ssh-add --apple-use-keychain ~/.ssh/id_ed25519 once, then UseKeychain yes + AddKeysToAgent yes in ~/.ssh/config and you never type it again. The server side ("Remote Login") is sshd under launchd as com.openssh.sshd, toggled by sudo systemsetup -setremotelogin on; its config is /etc/ssh/sshd_config plus drop-ins in sshd_config.d/ (Apple's 100-macos.conf sets UsePAM and the SFTP subsystem; put yours in 000-local.conf — first value wins).
ssh-keygen -t ed25519 -C "you@laptop 2026"make a key (asks for a passphrase)ssh-copy-id -i ~/.ssh/id_ed25519.pub user@hostinstall it remotelyssh-add --apple-use-keychain ~/.ssh/id_ed25519; ssh-add -lpassphrase into Keychain; list loaded keysssh -L 8080:localhost:80 hostlocal port 8080 → host's :80 (reach a remote-only web UI)ssh -D 1080 -N hostSOCKS proxy through host; curl --socks5-hostname localhost:1080 …ssh -R 2222:localhost:22 vpsreverse tunnel: vps:2222 reaches this Mac's sshdssh -J bastion innerjump hostssh -G host | grep -i identitywhich config actually appliesssh-keygen -R hostforget a changed host key (after you've confirmed why it changed)
VPNs, tunnels and utun
A VPN is a virtual interface (utunN on macOS) whose "wire" is an encrypted tunnel to a peer, plus routes that steer traffic into it and usually DNS changes. That's all. Full-tunnel VPNs install a more-specific pair of routes (0.0.0.0/1 and 128.0.0.0/1) that beat your default route without replacing it; split-tunnel ones add only the corporate prefixes and push a scoped resolver for the corporate domains (visible in scutil --dns). Debugging a VPN is therefore netstat -rn + scutil --dns + route get — nothing exotic.
Type
Notes
IKEv2 / IPsec
Built into macOS (Network Extension); configured by profile or System Settings. scutil --nc list / start / status drives it. Kernel IPsec shows in sysctl net.inet.ipsec.
L2TP/IPsec, Cisco IPsec
Legacy; still uses racoon (IKEv1), launched on demand. Don't build new things on it.
WireGuard
App Store app or brew install wireguard-tools + wireguard-go. Single UDP port, static keys, appears as utunN.
Tailscale / ZeroTier
WireGuard-based meshes with NAT traversal and their own 100.64.0.0/10 addresses; tailscale status, tailscale ping.
iCloud Private Relay
Not a VPN but looks like one: Safari and DNS via two-hop QUIC proxies, a utun, and your public IP changes. networkQuality -p tests through it.
SSH
-D SOCKS, -L/-R port forwards, -w for a real tun; sshuttle (brew) turns SSH into a poor-man's VPN with no server setup.
scutil --nc list; scutil --nc status "Work"configured VPNs and their stateifconfig | grep -A3 utunwhich tunnels exist and their addressesnetstat -rn | grep utunwhat is routed into themroute -n get 10.0.0.5"will this go through the VPN?"curl -s ifconfig.me; curl -s --interface en0 ifconfig.mepublic IP with vs without the tunnel
Diagnosis
Bottom-up. Each step assumes the one below it works; the first failing step is the fault.
The ladder
Layer
Question
Command
Healthy
If not
1 Link
Is the interface up with a link?
ifconfig en0 | grep -E 'status|flags'
RUNNING, status: active
Wi‑Fi off / not associated / cable. networksetup -setairportpower en0 off; …on. sudo wdutil info for RSSI.
2 Address
Do I have a real address?
ipconfig getifaddr en0
Something not 169.254.x
DHCP failed. sudo ipconfig set en0 DHCP; sudo tcpdump -ni en0 port 67 or 68 to see if anyone answers.
3 Gateway
Can I reach the router?
route -n get default then ping -c3 <gateway>
Replies, <5 ms wired, <20 ms Wi‑Fi
No default route ⇒ DHCP gave none. Timeouts ⇒ bad link / wrong VLAN / arp -an shows (incomplete).
4 Internet by IP
Does the world exist?
ping -c3 1.1.1.1, ping6 -c3 2606:4700:4700::1111
Replies
Router's uplink or ISP. traceroute -n 1.1.1.1 shows where it stops. Captive portal? curl -sI http://captive.apple.com/hotspot-detect.html should return "Success".
4xx/5xx is the server's problem; slow first byte = server; slow everything = path (mtr).
9 Performance
Is it slow, or just loaded?
networkQuality -v, ping -c 50 -i .2 gateway
RPM > 1000; jitter < 10 ms
High loss/jitter to the gateway = Wi‑Fi (channel, RSSI, CCA). Low RPM only under load = bufferbloat.
Remember scope: if one site fails, it's steps 5–8 for that site; if everything fails, start at 1. If only Bonjour/LAN things fail, it's multicast filtering on the AP or client isolation. If only .local names fail but IPs work, it's mDNS. If a site works in Safari but not in curl, it's DNS scope (step 5, the dig-vs-system split) or a Keychain-only CA. If LAN hosts appear in Safari but not to a script, it's Local Network privacy (Bonjour card).
One-liners that answer one question
ifconfig -luwhich interfaces are upnetworksetup -listallhardwareportsmap service names ⇄ enN ⇄ MACscutil --nwithe primary interface and reachability, as configd sees itroute -n get default | grep -E 'gateway|interface'who and whereipconfig getpacket en0 | grep -E 'lease|server|router|domain_name_server'the lease in four linesarp -an | grep -v incompletewho I've talked to on the LANlsof -iTCP -sTCP:LISTEN -nPwhat this Mac is servinglsof -iTCP -sTCP:ESTABLISHED -nP | awk '{print $1}' | sort | uniq -c | sort -rn | headwhich apps hold the most connectionsnettop -P -d -l 3who is using bandwidth right nowsudo tcpdump -nn -k -c 50 not port 2250 packets with process names attached (pktap)dns-sd -B _services._dns-sd._udpeverything advertising on the LANcurl -s ifconfig.me; curl -6 -s ifconfig.mepublic IPv4 / IPv6system_profiler SPNetworkDataType | grep -E '^\s+(Type|BSD Device|IPv4 Addresses|Router):'every service's essentialssudo sysdiagnose -p -f ~/Desktopthe full network snapshot bundle, quick mode
Performance
Bandwidth is rarely the problem; latency, loss and queueing are. What to measure, and what the numbers mean.
Measure the right thing
Symptom
Measure
Tool
"The Internet is slow"
Capacity and responsiveness under load
networkQuality -v — Mbps up/down, RPM, idle latency. Low RPM with fine Mbps = bufferbloat.
Video calls stutter
Jitter and loss to the first hop
ping -c 100 -i 0.2 -q gateway; stddev > 10 ms or any loss on Wi‑Fi = channel/RSSI problem. sudo wdutil info.
One site is slow
Where the time goes
curl -w timing breakdown (see A Packet's Journey); mtr -n host (brew) for per-hop loss.
LAN transfers are slow
Raw TCP throughput between two machines
iperf3 -s on one, iperf3 -c host -P 4 on the other (brew). Wi‑Fi 6 ≈ 500–900 Mb/s; gigabit Ethernet ≈ 940. -R for the other direction, -u -b 100M for UDP loss/jitter.
Big packets fail, small work
Path MTU
ping -D -s 1472 host (1472 + 28 = 1500). If it fails, step down; VPNs often need 1400. networksetup -setMTU en0 1400 or the VPN's own setting.
netstat -anL (queue full?), kern.ipc.somaxconn (128 default), ulimit -n, ab -n 2000 -c 50 -k url to load it.
Bandwidth-delay product decides whether one TCP flow can fill a link: a 1 Gb/s path with 50 ms RTT needs 6 MB in flight; macOS autotunes socket buffers up from 128 KB so a single flow usually can, but a single flow across the Atlantic won't — that's why iperf3 -P 4 and download managers use parallel streams. Wi‑Fi numbers are half-duplex PHY rates: a "1200 Mb/s" link delivers ~600 of TCP, less with any other client active. Jumbo frames (MTU 9000) help storage traffic on a wired LAN only if every switch and NIC in the path agrees; a mismatch produces silent black holes.
612 net.* MIB entries on this machine. Nearly all should be left alone; these are the ones worth knowing exist. Changes are transient — persist with a LaunchDaemon, never a /etc/sysctl.conf (not read).
Link-local auto-config and RA acceptance · privacy addresses on and preferred for new connections · preferred one day, valid seven · none generated for unique-local prefixes.
kern.ipc.somaxconn (128)
Max listen() backlog. Raise for busy servers.
kern.ipc.maxsockbuf (8388608)
Ceiling on any socket buffer.
sysctl -a | grep '^net\.inet\.tcp\.' | wc -lhow many TCP knobs (129 on 26.6)sysctl -d net.inet.tcp.delayed_ackdescription of onesudo sysctl net.inet.ip.forwarding=1set (until reboot)sysctl -W net.inet.iponly the writable ones
Linux ⇄ macOS
The translation table. macOS is BSD: the iproute2 and systemd tools don't exist, and a few Apple tools have no Linux counterpart at all.
No network namespaces — a container on a Mac is a VM guest. Apple's container gives each one its own IP on 192.168.64.0/24, reachable from the Mac without publishing.
Peter L. Dordal, Loyola University Chicago. Second edition (2.0.11, HTML at intronetworks.cs.luc.edu/current2/html/). Bottom-up, TCP/IP-centred, generous with worked examples and exercises, and unusually good on the parts that bite in practice: Wi‑Fi internals (ch 4), the IPv4 routing table (§9.5), NAT (§9.7), the IPv4 companions ARP/DHCP/ICMP/DNS (ch 10), IPv6 (ch 11–12), TCP dynamics and bufferbloat (ch 20–23). Chapters 30–32 (Mininet, ns-2, ns-3) are hands-on simulation labs. If you read one, read this one.
Larry Peterson and Bruce Davie; the open-source sixth edition (v6.2-dev) at book.systemsapproach.org, one HTML page per chapter with sub-pages. The classic top-down-ish "why is it designed this way" text: each chapter opens with the problem and closes with a perspective on where the field is going (feature velocity, the cloud, HTTP as the new narrow waist). Stronger than Dordal on architecture, congestion control theory and the application layer; lighter on hands-on detail. Companion mini-books (5G, SDN, TCP congestion control, private 5G) live on the same site.
The man pages themselves — Apple's are current and often contain the only documentation of a flag: man 8 ifconfig, man 5 pf.conf, man 5 resolver, man 8 networksetup, man 1 dns-sd, man 8 wdutil, man 8 networkQuality, man 4 inet6, man 4 tcp. Section numbers matter: resolver(3) is the C API, resolver(5) is the config file; bind(1) is a shell builtin, bind(2) is the syscall.
RFCs that are readable end to end: 791 (IP), 793/9293 (TCP), 768 (UDP), 826 (ARP), 1034/1035 (DNS), 2131 (DHCP), 4861 (IPv6 ND), 4862 (SLAAC), 6762/6763 (mDNS/DNS-SD), 8446 (TLS 1.3), 9000 (QUIC), 9293 (TCP, 2022 consolidation).
Apple: Platform Security Guide (TLS, Private Relay, Wi‑Fi security); Network framework docs for what modern apps use instead of BSD sockets; the WWDC sessions on responsiveness/RPM and L4S.
Beej's Guide to Network Programming — the sockets API, if you're writing C.
ifconfig
Kernel-level interface configuration. Reads for everyone; changes need sudo and last only until configd next re-evaluates the network. Persistent versions of most of this are in networksetup.
ifconfig — listing
ifconfig(8)
ifconfig is a tool that shows and sets the kernel's view of every network interface — its addresses, MAC, MTU, flags and link state. No arguments = every interface. Output is BSD-style: flags=, ether, inet/inet6 lines, media, status. Masks print in hex unless you ask for CIDR.
ifconfig en0One interface, all families.
-aAll interfaces (implied with no args).
-lNames only, one line. -lu up ones, -ld down ones. Exclusive with everything except -u/-d.
-u / -dOnly interfaces that are up / down.
-X 'en[0-9]'Filter by regex on the name (with -a or -l).
-m en0Capabilities and every supported media type/option for the interface.
-LShow IPv6 address lifetimes (preferred/valid) as time offsets.
-vVerbose; with -a shows a bridge's learned-address table and more driver detail.
-rRoute-reference counts per interface.
-CList interface cloners — the pseudo-device types you can create; on 26.6: rd feth bridge bond vlan pflog gif iptap droptap pktap.
inet / inet6 / linkRestrict to one address family; ether and lladdr are synonyms for link.
ifconfig — addresses & state
sudo
ifconfig is also the tool that assigns addresses and link parameters directly to an interface, taking effect immediately and lasting only until configd reconfigures the network.
en0 inet 192.0.2.10 netmask 255.255.255.0Set the primary IPv4 address. CIDR works too: 192.0.2.10/24. Interface goes up automatically.
en0 inet 192.0.2.45/28 aliasAdd a second address (add = alias). Same-subnet aliases need a 0xffffffff mask to avoid conflicting routes.
en0 inet 192.0.2.45 -aliasRemove it (delete, remove also work).
en0 inet6 2001:db8::1 prefixlen 64 aliasAdd an IPv6 address; prefixlen defaults to 64. anycast marks a router anycast address.
en0 inet6 2001:db8::1/64 deleteRemove an IPv6 address. Never delete the auto link-local one — "the kernel acts very odd".
en0 up / downAdministratively enable / disable. down; up is the crude "reset the interface".
en0 mtu 9000Set MTU (jumbo frames; every hop must agree). networksetup -listValidMTURange en0 shows what the driver allows.
en0 ether 02:11:22:33:44:55Change the MAC (lladdr, link). Bounces the link. Wi‑Fi may refuse while associated; Private Wi‑Fi Address does this for you per SSID.
en0 media 1000baseT mediaopt full-duplexForce Ethernet speed/duplex; media autoselect to return. -mediaopt clears an option.
en0 metric 10Interface metric (higher = less preferred). Apple's service order matters more.
en0 arp / -arpEnable / disable ARP on the interface.
en0 rxcsum txcsum tso lroHardware offload toggles (prefix - to disable). Disabling tso makes tcpdump checksums look right at the cost of CPU.
en0 tbr 50MbpsToken-bucket egress rate limiter; tbr 0 removes it. Handy for simulating a slow link.
en0 inet6 ifdisabled / -ifdisabledTurn IPv6 off / on for the interface. Also nud, dad, insecure (disable Secure Neighbor Discovery), replicated.
ifconfig — virtual interfaces
sudo
ifconfig is also the tool that creates and configures virtual interfaces — bridges, VLANs, bonds and tunnels — on top of physical ones. Transient. For VLANs and bonds that survive reboot use networksetup -createVLAN / -createBond; for a persistent bridge use Thunderbolt Bridge or Internet Sharing.
bridge
bridge createCreate bridgeN (prints the name). bridge0 destroy to remove.
bridge0 addm en1 addm en2 upAdd members (they're put in promiscuous mode); deletem removes. The Mac now switches frames between them.
bridge0 addrShow the learned MAC table. flush / flushall clear it; static en1 aa:bb:… pins an entry.
bridge0 stp en1 / -stp en1Spanning tree per member (off by default); tunables maxage, fwddelay, hellotime, priority, ifpriority, ifpathcost.
bridge0 maxaddr 100 timeout 240Cache size and entry timeout (0 = never expire). discover/learn toggles per member.
bridge0 hostfilter en1 aa:bb:cc:dd:ee:ffOnly allow that MAC (run again with an IP) on a member — VM/container isolation.
vlan · bond · gif
vlan0 create; vlan0 vlan 10 vlandev en1802.1Q VLAN 10 on top of en1. Tag and parent must be set in the same command. -vlandev unbinds.
bond0 create; bond0 bonddev en1 bonddev en2Link aggregation; bondmode lacp (default, needs a partner switch) or static. Members must be physical Ethernet, not in a VLAN. Bond takes the first member's MAC.
gif0 create; gif0 tunnel 192.0.2.1 198.51.100.1Generic IP-in-IP tunnel; then give gif0 inner addresses. -tunnel / deletetunnel to undo.
feth0 create; feth1 create; feth0 peer feth1Fake-Ethernet pair for testing (like Linux veth). Not documented in the man page; ifconfig -C lists it.
utunNTunnel interfaces owned by VPN apps / Network Extensions via PF_SYSTEM control sockets. You don't create these by hand.
networksetup
The CLI behind System Settings → Network. Everything it does is persistent, per location, and applied by configd. Operates on services ("Wi-Fi", "Ethernet" — quote names with spaces) or devices (en0). Most setters need admin; a password argument of - reads from stdin.
Inventory & services
networksetup(8)
networksetup is a tool that reads and writes the persistent network configuration System Settings uses — services, ports, their order and names.
-listallhardwareportsEvery port with its device (en0) and MAC — the Rosetta stone between "Wi‑Fi" and en0.
-listallnetworkservicesService names in order; * prefix = disabled.
-listnetworkserviceorderServices with their ports and devices, numbered by priority; * = inactive. The top active one owns the default route and DNS.
-ordernetworkservices "Ethernet" "Wi-Fi" …Change the priority order (this is how you prefer wired over wireless).
-getinfo "Wi-Fi"IP, mask, router, IPv6 config and MAC for a service in plain English.
-getmacaddress en0Hardware MAC of a port (the real one, not the private Wi‑Fi address).
-detectnewhardwareRescan for new ports (a USB adapter that didn't appear).
-setnetworkserviceenabled "Wi-Fi" offDisable / enable a service (-getnetworkserviceenabled to query).
-createnetworkservice Name en5New service on a port; -renamenetworkservice, -duplicatenetworkservice, -removenetworkservice (can't remove the last one on a port — disable it).
-getcomputername / -setcomputernameThe ComputerName (same as scutil --set ComputerName).
-printcommands / -help / -versionList every subcommand (about 100).
IPv4, IPv6, DNS, routes
networksetup is the tool that persistently sets a service's IPv4/IPv6 addressing method, DNS servers, search domains and static routes.
-setdhcp "Wi-Fi" [clientid]Use DHCP (optionally with a client ID). -setbootp for BOOTP.
-getadditionalroutes "Wi-Fi"Persistent static routes on a service.
-setadditionalroutes "Wi-Fi" 10.8.0.0 255.255.0.0 192.168.1.5Set them (dest mask gateway, repeated); no tuples = clear. -setv6additionalroutes for IPv6 (dest prefixlen gw).
-create6to4service / -set6to4automatic / -set6to4manual6to4 tunnel service (obsolete; relays are gone).
Wi‑Fi, proxies, locations
networksetup is also the tool that controls Wi‑Fi power and networks, system proxies, network locations, MTU and media, VLANs, bonds and PPPoE from the command line.
Wi‑Fi (argument is the device, en0)
-getairportpower en0 / -setairportpower en0 on|offWi‑Fi radio state. Off-then-on is the fastest reset.
-getairportnetwork en0Current SSID — says "not associated" on 15+ unless the caller has Location Services. Use ipconfig getsummary en0 or system_profiler SPAirPortDataType.
-setairportnetwork en0 SSID [password]Join a network (password - = stdin).
-listpreferredwirelessnetworks en0Remembered networks in priority order.
-addpreferredwirelessnetworkatindex en0 SSID 0 WPA2 [pw]Add at a priority (types: OPEN WPA WPA2 WPA/WPA2 WPAE WPA2E WPAE/WPA2E WEP 8021XWEP); password goes to the keychain.
-removepreferredwirelessnetwork en0 SSIDForget one; -removeallpreferredwirelessnetworks en0 forgets all.
Proxies
-getwebproxy / -setwebproxy "Wi-Fi" host 8080 [on user pw]HTTP proxy; -setwebproxystate "Wi-Fi" off toggles without forgetting. Same trio for securewebproxy (HTTPS) and socksfirewallproxy (SOCKS — pair with ssh -D).
-setautoproxyurl "Wi-Fi" http://…/proxy.pacPAC file (enables it). -setproxyautodiscovery "Wi-Fi" on for WPAD.
-setproxybypassdomains "Wi-Fi" *.local 169.254/16 …Exceptions list (Empty clears). scutil --proxy shows the effective config.
Locations, MTU/media, VLAN/bond, PPPoE
-listlocations / -getcurrentlocationLocations ("sets") — whole alternative network configs.
-createlocation Work populate; -switchtolocation Work; -deletelocation WorkMake one (copying the current config with populate), switch, delete. scselect is the older switcher.
The configd side: read the dynamic store, set the three hostnames, watch reachability, drive VPNs, inspect DHCP, toggle Remote Login.
scutil
scutil(8)
scutil is a tool that talks to configd — reading the live network state (DNS, proxies, reachability), setting the machine's three names and driving VPN configurations.
scutil --dnsThe live resolver configuration: every client with servers, domain scope, interface, search domains, search_order. The only true answer to "which DNS am I using?"
sudo scutil --set HostName mac.example.comSet one persistently (value from stdin if omitted). This is what hostname x isn't.
scutil -r apple.com [-W]Reachability: Reachable · Not Reachable · Transient Connection · Connection Required · Local Address · Directly Reachable. -W watches for changes. Also -r local remote.
scutil --nc list / status "Work" / start "Work" / stop "Work"VPN configurations (Network Connection). --nc help for the rest (statistics, show, select…).
sudo scutil --renew en0Ask configd to re-evaluate an interface's configuration (renew DHCP etc.).
scutil -w State:/Network/Global/IPv4 -t 30Block until a dynamic-store key exists (scripts that must wait for the network). -t 0 = forever.
echo 'show State:/Network/Global/IPv4' | scutilInteractive store shell, piped: primary interface and router. list, list State:/Network/Interface/.*/IPv4, show Setup:/Network/Service/….
scutil --prefsInteractive raw editor for the persistent preferences (SCPreferences). Dangerous; use networksetup.
scselect · hostname · systemsetup
scselect is a tool that switches the active network location; hostname is a tool that prints (and temporarily sets) the host name; systemsetup is a tool that toggles machine-level settings such as Remote Login, network time, wake-on-LAN and sleep.
scselectList locations (name + UUID), mark the active one.
scselect WorkSwitch location now (reconfigures immediately — wipes transient ifconfig/route/ipconfig changes). -n defers to next boot.
hostname [-f|-s|-d]Print the FQDN / short name / domain. sudo hostname x sets it until reboot only.
sudo systemsetup -getremotelogin / -setremotelogin onSSH server (Remote Login). Needs Full Disk Access for the terminal; -f skips the confirmation when turning off.
-getremoteappleevents / -setremoteappleevents onRemote Apple Events (osascript from another Mac).
-getwakeonnetworkaccess / -setwakeonnetworkaccess onWake on LAN / Bonjour Sleep Proxy.
-getusingnetworktime / -setnetworktimeserver time.apple.comNTP on/off and server (rewrites /etc/ntp.conf). -setusingnetworktime on.
-getlocalsubnetname / -setlocalsubnetnameAnother way at LocalHostName.
-setsleep Never / -setcomputersleep 30Not networking, but the reason your Mac stopped serving files at 3 a.m.
ipconfig (Apple's DHCP client tool)
ipconfig(8)
ipconfig is a tool that inspects and overrides each interface's DHCP/BOOTP/SLAAC state — the lease, the Router Advertisement, the options — through configd's IPConfiguration agent. The man page calls it "test and debug only" — which in practice means it's the best DHCP inspector on any platform. set and setverbose need root.
getifaddr en0First IPv4 address of the interface, bare — ideal in scripts (ip=$(ipconfig getifaddr en0)). Empty if none.
getiflist / ifcountInterfaces IPConfiguration manages / how many.
getoption en0 routerOne option by name (bootpd's names: subnet_mask, router, domain_name_server, domain_name, lease_time, server_identifier) or number. First value only.
getsummary en0Everything IPConfiguration knows for the interface — including, on 26.6, SSID, BSSID, Security. The current workaround for SSID redaction.
getdhcpduid / getdhcpiaid en0DHCPv6 identifiers.
sudo ipconfig set en0 DHCPForce a fresh DHCP cycle now. Creates a temporary service that vanishes at the next reconfiguration (which restores the persistent setting).
sudo ipconfig set en0 MANUAL 10.0.0.5 255.255.255.0Temporary static IPv4. Also BOOTP, INFORM ip mask (static + DHCP INFORM for options), NONE.
sudo ipconfig set en0 NONE-V6 | AUTOMATIC-V6 | MANUAL-V6 addr plenTemporary IPv6 mode. 6TO4 needs an stf interface.
sudo ipconfig setverbose 1Persistent verbose logging of IPConfiguration (read with log stream --predicate 'subsystem == "com.apple.IPConfiguration"').
waitallBlock until all interfaces are configured or time out (legacy boot-script sync).
route · arp · ndp
The routing table and the two neighbour caches. Reads are free; changes need root and are transient.
route
route(8)
route is a tool that queries and edits the kernel routing table — which gateway and interface a destination will use.
route -n get defaultWhich gateway and interface the default route uses (plus MTU, flags). route -n get 8.8.8.8 answers "how would I reach this?" — the definitive check.
route -n get -inet6 2001:4860:4860::8888Same for IPv6.
sudo route add default 192.168.1.1Set a default route (default = -net 0.0.0.0). delete default removes it. change modifies.
sudo route add -net 10.0.0.0/8 192.168.1.254Static network route via a gateway. Host route: -host 10.1.1.1 gw. Short forms: -net 128.32 = 128.32.0.0.
sudo route add -net 10.1.0.0/16 -interface en1Directly-connected route out an interface (no gateway).
sudo route add -ifscope en1 default 10.9.0.1Interface-scoped route (Apple): used only by sockets bound to en1. Required to touch the I-flagged entries you see in netstat -rn.
sudo route -n flush [-inet|-inet6]Delete every gateway route. Recover with sudo scutil --renew en0 or by toggling the interface.
-n / -v / -q / -tNumeric / verbose / quiet / test mode (don't touch the kernel). "Network is unreachable" on add = gateway not on a directly connected net.
arp (IPv4 neighbours)
arp(8)
arp is a tool that displays and edits the IPv4 ARP cache mapping on-link IP addresses to MAC addresses.
arp -a [-n] [-i en0]Dump the ARP cache (-n no DNS, -i one interface). (incomplete) = no reply yet. -l adds link-reachability info, -x extended.
arp 192.168.1.1One entry.
sudo arp -d 192.168.1.50Delete an entry; sudo arp -d -a flushes everything (after swapping a router or NIC).
sudo arp -s 192.168.1.9 auto pub onlyProxy-ARP: answer ARP for .9 with this Mac's MAC (pub; only = don't use it ourselves).
reject / blackhole / ifscope en0Entry modifiers: drop and notify, drop silently, per-interface.
ndp (IPv6 neighbours & routers)
ndp(8)
ndp is a tool that displays and edits IPv6 Neighbor Discovery state — the neighbour cache, learned prefixes and default routers.
ndp -anNeighbour cache. States: R reachable, S stale, D delay, P probe, I incomplete, N nostate; flag R = router. -A 2 repeats every 2 s, -t timestamps.
ndp -pPrefix list learned from RAs (flags A autoconf, L on-link; valid/preferred lifetimes). -P flushes.
ndp -rnDefault router list with expiry. -R flushes.
sudo ndp -cFlush the neighbour cache. -d fe80::1%en0 deletes one.
ndp -i en0Per-interface ND flags (nud, disabled, proxy_prefixes, insecure); set with sudo ndp -i en0 -- -nud. disabled is set automatically after a DAD failure.
ndp -I [en0|delete]Show/set the default interface used when no router is known.
sudo rtadvd en1Become an IPv6 router: send RAs for en1's prefixes (pair with net.inet6.ip6.forwarding=1). -s static prefixes from /etc/rtadvd.conf.
sysctl
Read and set kernel variables (the MIB, a tree of named tunables). The network ones live under net.; the important values are tabled in the Performance guide card above.
sysctl
sysctl(8)
sysctl is a tool that reads and sets kernel tunables, including every network-stack parameter under net..
sysctl net.inet.ip.forwardingRead one. -n value only, -N name only, -e as name=value.
sudo sysctl net.inet.ip.forwarding=1Set one. Lost at reboot; -w is accepted and ignored. No /etc/sysctl.conf is read — persist with a LaunchDaemon.
sysctl -a | grep ^net.inet.tcpEverything under a prefix (129 TCP entries on 26.6). sysctl net.inet.tcp without -a works too.
sysctl -W net.inet.ipOnly writable variables.
sysctl -d net.inet.tcp.delayed_ackDescription. -t type, -F format, -l length.
sysctl -f fileLoad name=value pairs from a file. -i ignore unknown names, -q quiet, -h human units.
-o / -x / -bOpaque values as hex (16 bytes / all) / raw binary.
dig/host/nslookup talk to a server directly and ignore the Mac's resolver; dns-sd and dscacheutil go through it. Know which you're holding.
dig
BIND 9.10
dig is a tool that sends a DNS query directly to a name server and prints the raw answer — the standard way to interrogate DNS itself rather than the Mac's resolver. Servers from /etc/resolv.conf unless @server. Query options are +opt/+noopt, abbreviable. Per-user defaults in ~/.digrc. Carries the "macOS NOTICE": results may differ from what apps see.
dig example.comA record, full output (question, answer, authority, additional, stats).
dig +short example.com AAAAJust the data. Types: A AAAA MX NS SOA TXT CNAME PTR SRV HTTPS CAA DNSKEY ANY AXFR. -t type also works.
dig @1.1.1.1 example.comAsk a specific server (IP or name). -p 5353 for another port, -4/-6 transport family, -b addr source.
dig -x 8.8.8.8Reverse (PTR) lookup; builds the in-addr.arpa/ip6.arpa name for you.
+noall +answerPrint only the answer section. Other display flags: +[no]comments +[no]question +[no]authority +[no]additional +[no]stats +multiline +[no]ttlid +[no]crypto +[no]identify +[no]qr.
+traceIterate from the root servers, following referrals — shows delegation and where it breaks. Implies +dnssec.
+dnssecSet the DO bit; return RRSIGs. +cdflag asks the resolver to skip validation; +nocrypto hides the blobs.
+tcpForce TCP (+vc). AXFR always uses it. +bufsize=N sets the EDNS UDP size; +noedns disables EDNS.
+nssearchQuery the SOA from every authoritative server — spot a lagging secondary.
+search / +domain=x / +ndots=NApply resolv.conf's search list (off by default in dig).
+time=T +tries=T +retry=TTimeout (5 s), attempts (3), retries (2). +[no]fail: try next server on SERVFAIL.
+norecurseClear RD — ask an authoritative server exactly what it holds.
+subnet=203.0.113.0/24EDNS Client Subnet — see the geo-answer for another network. +nsid, +cookie, +expire.
-f fileBatch: one query per line. -k keyfile / -y TSIG-sign (dynamic DNS servers).
dig +tcp @ns1 example.com AXFRZone transfer (if allowed). ixfr=N incremental.
host · nslookup · whois
host is a tool that does a quick name-to-address (or reverse) DNS lookup; nslookup is an older interactive DNS query tool; whois is a tool that asks the registries who owns a domain, IP block or AS number.
host
host example.comA, AAAA and MX in three lines. An address argument does PTR automatically.
host -t NS example.com 1.1.1.1One type, from a given server. -a = -v -t ANY.
host -C example.comSOA serial from every authoritative server (consistency check).
-T / -r / -W secs / -R n / -4 / -6TCP · non-recursive · timeout · UDP retries · family. -l lists a zone (AXFR).
nslookup - 9.9.9.9Interactive against a server: server x, set type=TXT, set debug. Half the historical commands (ls, help…) are stubs. Prefer dig in scripts.
whois
whois example.comRegistrar, dates, name servers; starts at IANA and follows referrals (-R; -Q disables).
whois 8.8.8.8 · whois AS15169IP allocation (from the regional registry, RIR) · who owns an AS number.
-a -A -r -f -l -kForce a registry: ARIN, APNIC, RIPE, AfriNIC, LACNIC, KRNIC. -h host -p port explicit server; -c UK ccTLD shortcut; -m RADB route objects; -P PeeringDB.
dns-sd (Bonjour) · dscacheutil · mDNSResponder
dns-sd is a tool that browses, resolves, registers and queries Bonjour (mDNS/DNS-SD) services and names through the system resolver; dscacheutil is a tool that queries and flushes the Directory Services cache that fronts host, user and service lookups; mDNSResponder is the daemon that is macOS's actual DNS resolver and Bonjour responder. dns-sd runs until Ctrl-C and its output format is explicitly unstable — read it, don't parse it.
dns-sd
-B _services._dns-sd._udpBrowse the meta-type: every service type advertised on the LAN. -B _ssh._tcp [domain] browses one type; Add/Rmv events stream live.
-R "My Site" _http._tcp . 8000 path=/Register/advertise a service on this host (TXT keys optional; . = default domain). Ctrl-C withdraws.
-P name _http._tcp "" 80 host.local 192.168.1.9Proxy-advertise a service that lives on another machine (also publishes the host's A record).
-G v4v6 example.comAddress lookup via the system path (honours hosts file, /etc/resolver, VPN DNS, .local) — "what would an app get?" v4/v6 for one family.
-q / -Q name [type [class]]Generic query for any record through mDNSResponder; keeps running and reports changes. -q suppresses answers the system deems unusable on the current network (what apps get); -Q shows everything.
-X udp 8000 8000 0Ask the gateway for a NAT-PMP / PCP port mapping (protocol, internal port, external port, TTL) — tests whether the router will open a port for a LAN service; Internet Sharing answers these via natpmpd.
-O -stdoutDump mDNSResponder's state (cache, registrations, open questions) to stdout (or a file without -stdout); -H prints the complete option list.
-Z _airplay._tcpBrowse and print as zone-file records (PTR/SRV/TXT).
-E / -FEnumerate domains recommended for registering / browsing (normally just local). -V daemon version.
dscacheutil -q host -a name example.comResolve via the system path, one-shot (also -a ip_address x for reverse). Returns nothing, not an error, on failure.
-q user|group|service|protocol|rpc|mount [-a key val]Other Directory Service categories (-q user -a name alice, -q service -a name https). No -a dumps the category.
sudo dscacheutil -flushcacheDrop the whole DS cache ("extreme cases only"). DNS lives in mDNSResponder, so pair it: ; sudo killall -HUP mDNSResponder.
-cachedump [-buckets] [-entries [host]] · -configuration · -statisticsInspect the cache, the directory search order, hit/miss counts.
mDNSResponder · resolver(5)
sudo killall -HUP mDNSResponderFlush its cache. -USR1 toggles extra logging, -USR2 packet logging, -INFO dumps state — to the unified log (log stream --predicate 'process == "mDNSResponder"' --info).
sudo launchctl kickstart -k system/com.apple.mDNSResponderRestart it outright.
/Library/Preferences/com.apple.mDNSResponder.plistBooleans AlwaysAppendSearchDomains (also for multi-label names; discouraged) and NoMulticastAdvertisements; reboot to apply.
/etc/resolver/<domain>Per-domain resolver client: nameserver a.b.c.d (up to 3; a.b.c.d.port for a port), port N, search …, search_order N (tie-break), timeout N, options ndots:n usevc. Picked by longest suffix match; honoured by apps, ignored by dig.
/etc/resolv.conf → /var/run/resolv.confGenerated mirror of the primary resolver for BSD tools. Editing it is futile.
ping · fping · traceroute
ICMP echo and TTL-expiry tracing. None needs sudo for ordinary use; ping's flood and sub-2 ms intervals do. fping 5.1 is bundled — the parallel sweeper.
ping
ping(8)
ping is a tool that sends ICMP echo requests to a host and reports whether replies come back, how fast, and how many are lost.
ping -c 5 hostStop after 5 replies. Exit 0 if any reply, 2 if none. Ctrl-T (SIGINFO) prints running stats.
-i 0.2Interval in seconds (fractional ok; < 0.002 needs root). -f flood (root). -l N preload burst (root).
-t 10Give up after 10 s total. -W 200 per-reply wait in milliseconds. -o exit on first reply ("is it up yet?").
-qSummary only (loss %, min/avg/max/stddev — stddev is jitter). -Q hide ICMP errors, -v show all ICMP.
-nNo reverse DNS. -a bell per reply, -A bell per miss.
-S 10.0.1.9Source address. -I en0 is source for multicast only.
-b en0Bind to an interface — ping out a specific link regardless of the routing table.
-m 5TTL (traceroute-by-hand). -T multicast TTL. -z tos TOS byte. -r bypass routing (direct-attached only).
-G max -g min -h stepSweep payload sizes (find the MTU cliff).
-M mask|timeSend ICMP mask request / timestamp instead of echo. -p ff pad pattern.
-k VO / -K VOTraffic class / net service type (BK BE VI VO…) — test Wi‑Fi priority (WMM) queues. -C never cellular; --apple-time, --apple-connect.
Reply TTL≈255−hops = BSD/macOS/Cisco responder, 64−hops = Linux, 128−hops = Windows. Duplicates on unicast = a flaky link.
fping (bundled, /usr/bin)
fping -a -g 192.0.2.0/24Ping a whole prefix in parallel and print the hosts that answer: -a alive only, -u unreachable only, -g generate targets from a prefix or start end range, -q quiet. The LAN inventory that arp -an can't give you (it only lists hosts you've already talked to). No root needed.
fping -c 20 -q -p 200 host1 host2Loss and min/avg/max per host, one summary line each: -c count, -p ms between pings to one host, -i ms between hosts, -t reply timeout ms, -r retries, -l loop forever, -e show elapsed per reply, -s totals, -C per-ping table.
ping6
ping6(8)
ping6 is a tool that does what ping does over IPv6, with the scope-id and multicast options IPv6 needs.
ping6 -c 3 fe80::1%en0Link-local targets need the %iface scope (or -I en0).
ping6 -I en0 ff02::1All-nodes multicast: every IPv6 host on the link replies — a crude inventory. ff02::2 = all routers.
-B en0Bind to interface (capital; -b is buffer size here).
-h hops · -m · -DHop limit · don't fragment below min MTU · disable fragmentation.
-H / -nDo / don't reverse-resolve (off by default, opposite of ping). -r/-R bell on reply/miss.
-w / -a agl / -tNode-information queries: DNS name, addresses, supported types (most OSes ignore).
-G max[,min[,step]]Size sweep in one option. -k/-K traffic class as ping.
traceroute · traceroute6
traceroute(8)
traceroute is a tool that maps the path packets take to a host by sending probes with increasing TTLs and listing each router that reports back; traceroute6 is its IPv6 counterpart. UDP probes to ports 33434+ by default; each hop that replies "time exceeded" is printed. Three probes per hop; * = no reply within -w. No sudo needed on macOS.
traceroute -n hostNumeric — avoids the per-hop reverse-DNS stalls.
-IICMP echo probes (= -P icmp) — when UDP is filtered.
-P tcp -p 443TCP SYN probes to a real port; passes most firewalls and reaches the actual service. -P gre or a protocol number also allowed.
-q 1 -w 2 -m 201 probe per hop, 2 s wait, 20 hops max — the fast version. -z 500 pauses 500 ms between probes for rate-limited routers.
-f 3Start at TTL 3 (skip your LAN and ISP edge). -M same.
-s addr / -i en1Source address / take source from an interface.
-F · -t tos · -ESet DF · TOS · detect ECN bleaching along the path.
-a / -A serverPrint the AS number of each hop.
-eFixed destination port (don't increment) — firewall evasion. -D hex-diffs your probe vs the quoted copy (finds header-rewriting middleboxes).
host 1400Trailing number = probe size (MTU-ish tests).
traceroute6 -n hostIPv6 path. -I ICMPv6, -T TCP, -U UDP (default), -N no upper header; -l names and numbers; default max 30 hops; no -P/-z/-D.
netstat · lsof · nettop
Sockets, connections, counters. macOS netstat has no process column — that's lsof -i; the live per-process view is nettop.
netstat
netstat(1)
netstat is a tool that prints snapshots of the kernel's network tables — open sockets and their states, the routing table, interface counters and per-protocol statistics. Addresses print as host.port (dot, not colon); *.22 = listening on all. No -p pid, no -t/-u — those are Linuxisms. TCP statistics read as zero without sudo.
netstat -s -p udp / icmp / ip / ip6 / icmp6Same for the others (no root needed for most).
netstat -mmbuf (kernel packet buffer) pool stats; exhaustion = "no buffer space available".
netstat -g [-v] [-s]Multicast group memberships (mDNS's 224.0.0.251 / ff02::fb should be there) — -gs IGMP/MLD stats.
netstat -BOpen BPF (packet-capture) devices: who is capturing right now.
netstat -I en0 -q / -S / -RApple extras: send-queue (active queue management) stats · link status · link-layer reachability.
lsof (network subset)
lsof(8)
lsof is a tool that lists open files, and with -i shows which process owns every network socket — the process column that macOS's netstat lacks. Without sudo you see only your own processes. Selections are ORed unless -a. -n -P (no host/port lookups) makes it fast.
sudo lsof -i -nPEvery Internet socket with command, PID, user, state.
lsof -iTCP -sTCP:LISTEN -nPWho is listening. -sTCP:ESTABLISHED, -sTCP:^CLOSE_WAIT (negate).
lsof -i :8080Who holds port 8080 (either end). Address grammar: -i [46][tcp|udp][@host][:port], e.g. -i tcp@127.0.0.1:5432, -i @10.0.1.5, -i4, -iUDP.
lsof -a -i -p 1234Sockets of one PID (-a is essential or you get sockets OR all of 1234's files). -u user, -u ^user.
-tPIDs only: kill $(lsof -t -i :3000).
-r 2 / +r 2Repeat every 2 s (forever / until nothing matches).
-TqsTCP queue sizes + state after each address (-T f flags, w window).
nettop is a tool that shows live network activity per process and per connection — bytes in and out, RTT, interface — like top for sockets. Byte counts are cumulative since the socket opened unless -d.
nettopEverything, live, 1 s samples. Keys: q quit, d delta, x raw numbers, e/c expand/collapse, j pick columns, p pick processes, l dump and quit, h help.
-m tcp | udp | routeOnly TCP, only UDP, or the routing table with per-route counters.
-P -dPer-process totals, per-interval — "who is using the bandwidth right now".
-p Safari -p "Google Chrome"Filter by process name (exact, as displayed) or PID.
Open a TCP/UDP/Unix connection or listen for one. OpenBSD flavour with Apple TCP options; no -e.
nc
nc(1)
nc (netcat) is a tool that opens an arbitrary TCP or UDP connection, or listens for one, and pipes data through it — the simplest way to test a port or move bytes between two machines.
-G 3 -H 10 -I 5 -J 3Connect timeout 3 s; keepalive idle 10 s, interval 5 s, 3 probes. -L n probes before adaptive timeout; -A SO_RECV_ANYIF; -C no cellular.
-E · -K BK · -a · -O · -FSkip expensive (cellular / hotspot) interfaces · traffic class · allow AWDL · old-style connect() instead of connectx() · no flow advisory. Every one has a --apple-… long form (nc -h).
Packet capture with Apple's pktap extras — the only tcpdump that can print the process name next to each packet. Always sudo (it needs /dev/bpf*); reading a saved file doesn't.
tcpdump — capture
4.99.1 · Apple 158
tcpdump is a tool that captures packets from an interface, prints them decoded, and saves them to or replays them from pcap files.
sudo tcpdump -i en0 -nnCapture on en0, no name or port lookups. Omit -i and macOS uses a pktap over all up interfaces (excluding lo0 and tunnels).
-i pktap,en0,utun3 / -i all / -i iptapExplicit pktap set · everything incl. loopback and tunnels · IP layer only. pktap is never promiscuous and writes pcap-ng.
-D / -L -i en0 / -y typeList interfaces · list link types (-I -L for radio) · force one (-y RAW on pktap = plain pcap).
-T vxlan|rtp|snmp|tftp|radius|domain|iperf3 · -E spi@ip algo:secret · -F fileForce a decoder · decrypt ESP · filter from file. -d dumps the compiled BPF.
tcpdump — Apple metadata
pktap
tcpdump on macOS is also the tool that can tag every captured packet with the process, PID, interface and direction it belongs to, using Apple's pktap pseudo-interface.
-kPrint pktap metadata: I interface, N process name, P pid, S service class, D direction, F flags, U proc UUID, f flow id, A all. Bare -k = all available. Works live on pktap and when reading pcap-ng.
-Q "proc = curl"Metadata filter (overloads -Q): keywords if proc pid svc dir eproc epid dlt; operators = != and or not ( ). -Q "dir = out and svc != BE", -Q "if = en" (prefix match). Quote names with spaces.
tcpdump -r cap.pcapng -k NPI -nnReplay a saved capture with process, pid and interface columns.
-g / --apple-onelineKeep the IP header on one line under -v.
Ctrl-T (stty status ^T)SIGINFO: print received/dropped counts without stopping. SIGUSR2 flushes the -w buffer.
pcap-filter — the expression language
pcap-filter(7)
pcap-filter is the expression language that tcpdump, Wireshark and every other libpcap tool use to select which packets to capture. Primitives = [proto] [dir] [type] value, combined with and or not (&& || !) and parentheses (quote for the shell). Omitted qualifiers repeat: host a and b, port 80 or 443.
host 10.0.1.5 · src host x · dst host xEither end / source / destination. ip host, ip6 host, ether host aa:bb:cc:dd:ee:ff.
net 192.168.1.0/24 · net 10 · net 10.0.0.0 mask 255.0.0.0Prefixes (IPv6 needs /len). src net, dst net.
port 53 · tcp port 22 · udp src port 5353 · portrange 6000-6010Ports; bare port = tcp or udp.
tcp · udp · icmp · icmp6 · ip · ip6 · arp · vlan [id] · mplsProtocol keywords. vlan shifts offsets for the rest of the expression. ip proto 47 by number.
broadcast · multicast · ip multicast · ip6 multicastLink/IP broadcast and multicast.
less 100 · greater 1400 · len >= 1500Packet length.
'udp and (port 67 or port 68)' · 'udp port 5353' · 'tcp port 443 and tcp[tcpflags] & tcp-rst != 0'DHCP · mDNS · TLS resets.
'not (host 10.0.1.2 and port 22) and not arp'Everything except my SSH session and ARP.
wlan type mgt subtype beacon · wlan addr2 aa:bb:…802.11 frames in monitor mode: type mgt|ctl|data, subtype beacon|probe-req|auth|deauth|…, ra ta addr1..4.
airport is gone (14.4). What's left: wdutil (needs sudo), system_profiler, networksetup's Wi‑Fi subcommands, and ipconfig getsummary for the SSID.
wdutil
wdutil(8)
wdutil is a tool that reports the Wi‑Fi radio's state — signal, noise, channel, PHY mode, rate — and collects Wireless Diagnostics bundles; it replaces the removed airport command.
sudo wdutil infoWi‑Fi Info panel as text: interface, MAC, SSID/BSSID, channel/width/band, PHY mode, RSSI, noise, Tx rate, MCS, security, CCA, country, plus IPv4/IPv6/DNS/DHCP summary. SSID/BSSID/MAC print <redacted> without Location Services for the terminal (26.x). The man page says info needs no sudo; the binary insists.
sudo wdutil info | grep -E 'RSSI|Noise|Tx Rate|Channel|PHY'The five numbers that matter.
sudo wdutil diagnose [-q] [-f dir]Full Wireless Diagnostics bundle (minutes; tarball in /var/tmp or -f). -q skips the legal prompt and Finder window.
sudo wdutil log +wifi +dhcp +dns · -wifiToggle verbose subsystem logging (wifi dhcp dns od eapol); read with log stream --predicate 'subsystem == "com.apple.wifi"'.
wdutil dumpDump the temporary Wi‑Fi log buffer to /tmp/wifi-*.log.
airport -I / -sRemoved in macOS 14.4 (…/Apple80211.framework/…/Resources/airport). Scanning (-s) has no CLI replacement except the "Other Local Wi‑Fi Networks" list in system_profiler.
system_profiler · SSID · other views
system_profiler is a tool that dumps the hardware and software inventory System Information shows, including the Wi‑Fi card, the current network, every network service and the firewall.
system_profiler SPAirPortDataTypeCard, firmware, supported PHYs/channels, current network (PHY, channel, security, signal/noise, rate, MCS) and every other visible network. No sudo. -detailLevel full, -json, -xml.
ipconfig getsummary en0 | awk '/ SSID/{print $NF}'The SSID, on 26.6, without Location Services. Works because IPConfiguration knows the network it got its lease on.
system_profiler -json SPAirPortDataType | jq -r '.SPAirPortDataType[0].spairport_airport_interfaces[0].spairport_current_network_information._name'The SSID the other way, space-safe (jq 1.8 ships in /usr/bin). Sibling keys: spairport_network_channel, _phymode, _rate, _mcs, spairport_signal_noise, spairport_security_mode; spairport_airport_other_local_wireless_networks is the scan list.
Option-click the Wi‑Fi menuThe GUI still shows everything: RSSI, noise, channel, PHY, BSSID, Tx rate — and offers Wireless Diagnostics (with a Scan window and Performance graphs).
/Library/Preferences/SystemConfiguration/com.apple.airport.preferences.plistWhere known networks live (Setup:). Don't hand-edit; use networksetup.
sudo tcpdump -I -i en0 -e -y IEEE802_11_RADIO -nn 'type mgt subtype beacon'Raw beacons in monitor mode — the CLI scan of last resort (kills your association while it runs).
pfctl · pf.conf
The packet filter. Root for everything. Loaded at boot, not enabled; Apple's services enable it with reference counts. Your rules belong in an anchor, never in a replacement /etc/pf.conf.
pfctl
pfctl(8)
pfctl is a tool that controls the pf packet filter — enabling it, loading rulesets and anchors, and showing rules, states and tables.
enable · load · show
sudo pfctl -EEnable and take a reference (prints a token). -X token releases it; pf disables at zero. Bare -e/-d ignore the count and can yank Apple's NAT.
sudo pfctl -a '*' -srEvery rule in every anchor, recursively. -a 'com.apple/*' -sn = Internet Sharing's NAT.
-v -vv -vvv · -q · -r · -M · -zVerbosity · quiet · reverse-DNS states · port names · zero rule counters.
flush · kill · tables
sudo pfctl -F states | rules | nat | Tables | allFlush. -a local -F all flushes only your anchor.
sudo pfctl -k 10.0.1.55Kill states from a host; -k a -k b a→b; -k 0.0.0.0/0 -k host everything to host. -K for source-tracking entries.
sudo pfctl -t badhosts -T add 203.0.113.9Table ops: add delete replace show test flush kill zero expire N load; -f file reads addresses. -vsT shows flags, -vvsT counters.
-i en0 · -o none|basic|profile · -x loudRestrict to an interface · optimizer level · kernel debug level.
/etc/pf.conf · /etc/pf.anchors/com.apple · com.apple.pfctl (launchd)Stock config is five anchor lines for com.apple/* plus a load anchor. Apple injects rules at runtime under com.apple/200.AirDrop and com.apple/250.ApplicationFirewall (both pre-declared in /etc/pf.anchors/com.apple) and com.apple/100.InternetSharing (per natpmpd(8)).
sudo ifconfig pflog0 create; sudo tcpdump -n -e -ttt -i pflog0See logged packets (rule number, anchor, action, interface). No pflogd on macOS; create the interface and run tcpdump yourself.
pf.conf — grammar
pf.conf(5)
pf.conf is the rule language of the pf firewall — macros, tables, options, normalisation, NAT/redirection and filter rules — that pfctl loads. Sections in order: macros → tables → set options → scrub → queueing → translation (nat rdr binat) → filter (pass block antispoof anchor). Last matching rule wins unless quick. No match ⇒ pass. Every pass keeps state.
[in|out] [log [(all|user|to pflog1)]] [quick] [on en0]Direction (both if omitted) · log the state-creating packet (or all) · stop evaluation here · interface (! en0, { en0, en1 }).
[inet|inet6] proto tcp|udp|icmp|icmp6|{ tcp, udp }Family and protocol.
from src [port p] to dst [port p]Addresses: CIDR, hostname, any, self, en0, en0:network, (en0) (re-resolve on DHCP change), <table>, ! x, ranges a - b. Ports: number, service name, != < > <= >=, 2000:2004 range, 2000 >< 2004, 2000 <> 2004. all = from any to any.
flags S/SA · icmp-type echoreq · tos x · user u · group gTCP flags (S/SA is the stateful default) · ICMP type/code (icmp6-type) · TOS · socket owner (local endpoints only).
keep state | modulate state | synproxy state | no stateStateful (default) · with random initial sequence numbers · pf completes handshakes (SYN-flood shield) · stateless. Options: (max N, max-src-conn N, max-src-conn-rate N/S, overload <t> flush global, source-track, if-bound, floating, tcp.established 3600).
label "x" · tag T · tagged T · queue q · probability 20% · route-to (en1 gw) · reply-to · dup-toAccounting label · mark/match tags · ALTQ queue · random match · policy routing.
ext_if = "en0" · $ext_if · { a, b, c }Macros and lists (a list expands to one rule per element).
table <name> [persist] [const] { 10/8, 172.16/12 } · file "/etc/x"Address tables: fast, editable at runtime with pfctl -t. persist survives without a referencing rule; const is read-only.
set skip on lo0 · set block-policy drop · set loginterface en0 · set limit states 20000 · set timeout tcp.established 86400 · set optimization aggressive · set state-policy if-bound · set require-order noGlobal options. set skip on lo0 belongs in every ruleset.
scrub in on $ext_if all fragment reassemble · scrub in all no-df random-id min-ttl 5 max-mss 1440Normalisation; reassembly is needed before NAT. IPv6 fragments are dropped.
nat on $ext_if inet from ! ($ext_if) to any -> ($ext_if)Masquerade (what Internet Sharing installs). Pool options round-robin source-hash sticky-address static-port.
rdr pass on $ext_if proto tcp to ($ext_if) port 80 -> 127.0.0.1 port 8080Port forward / redirect (run a daemon unprivileged on 8080, expose 80). rdr … -> { a, b } round-robin for crude load balancing. binat is 1:1 both ways.
antispoof [log] [quick] for en0 [inet]Expands to block-in rules for packets claiming en0's network from elsewhere. Interferes with loopback — keep set skip on lo0.
anchor "local" · anchor "local/*" · load anchor "local" from "/etc/pf.anchors/local" · anchor "x" on en0 { … }Attach a named sub-ruleset (evaluated in place) · all children · load from a file · inline. nat-anchor, rdr-anchor likewise.
os "Windows" · os unknownPassive OS fingerprint match (IPv4 TCP SYN only; database /etc/pf.os, 2003-vintage — treat as policy, not security).
include "/etc/pf.local"Pull in another file.
dnctl · dummynet (traffic shaping)
dnctl(8)
dnctl is a tool that configures dummynet, the kernel traffic shaper: a pipe emulates a link with a bandwidth, one-way delay, queue size and loss rate, and a pf dummynet rule sends matching packets through it — the built-in way to test software on a slow or lossy link. Root for everything, even list. The dummynet rule keyword is absent from pf.conf(5) but pfctl parses it, and /etc/pf.conf pre-declares dummynet-anchor "com.apple/*".
sudo dnctl pipe 1 config bw 1Mbit/s delay 100 plr 0.01Pipe 1: 1 Mbit/s, 100 ms one-way delay, 1% loss. bw in [K|M]{bit/s|Byte/s} (0 = unlimited); delay in ms, rounded to the clock tick; plr 0–1; queue N slots (default 64).
dummynet out quick proto tcp from any to any port 80 pipe 1The pf rule (in an anchor file) that classifies traffic into the pipe. Dummynet rules are stateless — write one per direction, in and out, if both should be shaped. sudo pfctl -nf file checks the syntax.
sudo pfctl -a com.apple/shaper -f /etc/pf.anchors/shaper; sudo pfctl -ELoad it under the com.apple/* dummynet anchor the stock /etc/pf.conf already references (so nothing in the main file changes), then enable pf with a reference. sudo pfctl -a com.apple/shaper -F all; sudo pfctl -X token undoes it.
sudo dnctl list · -a · -s bytesShow pipes and queues (show is the same) · with counters · sorted by a field.
sudo dnctl pipe 1 delete · sudo dnctl -q flushRemove one pipe · remove all (-f skips the confirmation, -n parses only).
queue 2 config pipe 1 weight 10Weighted fair queueing (WF2Q+): several queues share one pipe in proportion to their weights — weights are shares, not priorities.
Application Firewall
/usr/libexec/ApplicationFirewall/socketfilterfw — the System Settings firewall. Decides per program whether it may accept inbound connections; never filters outbound. Query as a user, change with sudo.
socketfilterfw
socketfilterfw(8)
socketfilterfw is a tool that configures the Application Firewall from System Settings — which programs may accept incoming connections, block-all and stealth mode.
--getglobalstate"Firewall is enabled. (State = 1)" · 0 off · 2 block-all. --setglobalstate on|off.
--getblockall / --setblockall on|off"Block all incoming connections" (DHCP, Bonjour, VPN basics still pass).
--getstealthmode / --setstealthmode on|offStealth: drop ICMP echo and unsolicited probes. Breaks ping/traceroute to the Mac.
--getallowsignedShows both auto-allow flags: built-in Apple software (--setallowsigned) and downloaded signed software (--setallowsignedapp). Ad-hoc-signed Homebrew binaries don't qualify — hence the popups.
--listappsRegistered programs with Allow/Block state.
--add path · --remove pathRegister / unregister (an executable or a .app; the bundle's main binary is resolved).
--blockapp path · --unblockapp path · --getappblocked pathDeny / allow / query inbound for one program.
Options chainEvaluated in order on one line: sudo … --setglobalstate on --setstealthmode on --add /opt/homebrew/bin/node --unblockapp /opt/homebrew/bin/node.
--getloggingmode · --setloggingoptRemoved on 26.x (existed through macOS 14). Decisions are in the unified log: log stream --predicate 'process == "socketfilterfw"' --info.
com.apple.alf.agent · /usr/libexec/ApplicationFirewall/com.apple.alf.plistThe launchd job and default policy. When active, ALF also installs pf rules under com.apple/250.ApplicationFirewall.
system_profiler SPFirewallDataTypeThe same state as a report.
ssh
OpenSSH 10.3 client. Config: CLI → ~/.ssh/config → /etc/ssh/ssh_config (which includes ssh_config.d/*); first value wins. Exit status is the remote command's, or 255 for an ssh error.
ssh — connecting
ssh(1)
ssh is a tool that logs in to, or runs commands on, a remote machine over an encrypted, authenticated connection.
ssh user@host [command]Login, or run one command (no pty). Also ssh://user@host:port.
-p 2222 · -l user · -i ~/.ssh/keyPort · login name · identity file (repeatable; a .pub selects an agent key).
-J bastion[,b2]Jump host(s) (= ProxyJump). CLI options apply to the destination, not the jumps — configure those in ~/.ssh/config.
-o Key=value · -F fileAny config keyword · alternate config (-F none = no config at all).
-GPrint the effective configuration for this host and exit — the debugger for config files.
-v -vv -vvv · -E logDebug levels (which keys are offered, why auth failed) · write debug to a file. -q quiet.
-t / -TForce a pty (interactive remote programs, ssh -t host sudo -i; -tt even without a local tty) / no pty (binary-safe pipes: ssh host 'tar cz d' | tar xz).
-N · -f · -nNo remote command (just forwards) · background after auth · stdin from /dev/null.
-A / -aAgent forwarding on / off. Prefer -J; forwarding lets the remote root use your keys.
-Q kex | cipher | mac | key | sig | HostKeyAlgorithmsList supported algorithms. Default KEX is post-quantum mlkem768x25519-sha256 first.
-e ~ · ~. ~^Z ~# ~C ~? ~R ~VEscape character and sequences (start of line): disconnect · suspend · list forwards · command line (needs EnableEscapeCommandline yes) · help · rekey · less logging.
ssh — forwarding & multiplexing
ssh is also a tool that tunnels other traffic — port forwards, SOCKS proxies, jump hosts — and can share one connection between many sessions.
-L 8080:localhost:80 hostLocal forward: my :8080 → host's view of localhost:80. -L 5432:db.internal:5432 bastion reaches a third machine. Binds loopback unless -L 0.0.0.0:8080:… or GatewayPorts; ports < 1024 need root.
-R 2222:localhost:22 vpsRemote (reverse) forward: vps's :2222 → my :22. -R 0.0.0.0:… needs server GatewayPorts yes|clientspecified. -R port alone = reverse SOCKS. Port 0 = server picks.
-D 1080 -N hostDynamic forward: a local SOCKS4/5 proxy through host. Then curl --socks5-hostname localhost:1080 … or networksetup -setsocksfirewallproxy Wi-Fi localhost 1080 system-wide.
-W host:portPipe stdio to host:port via the server (the ProxyCommand='ssh -W %h:%p bastion' primitive; -J does this for you).
-w 0:0Real tun(4) device forwarding — a layer-3 VPN over SSH (server PermitTunnel yes).
-M · -S path · -O check|exit|stop|forward|cancel|proxyMultiplexing master · control socket · control commands (ssh -O forward -L 9000:localhost:9000 host adds a forward to a live master). Usually set via ControlMaster auto in config instead.
-gLet other hosts use local forwards (same as GatewayPorts for -L).
ssh -o ProxyCommand='nc -X connect -x proxy:8080 %h %p' hostThrough an HTTP CONNECT proxy (-X 5 for SOCKS5).
ssh -o VerifyHostKeyDNS=ask hostCheck SSHFP records (publish with ssh-keygen -r host).
Client keywords worth memorising, then the server: launchd job com.openssh.sshd, config in /etc/ssh/sshd_config + sshd_config.d/.
~/.ssh/config keywords
ssh_config(5)
ssh_config is the per-user (and system-wide) configuration file where ssh's options are set per host, so the command line stays short. First value wins ⇒ specific Host blocks beforeHost *. Tokens: %h host, %p port, %r remote user, %u local user, %C hash of host/port/user, %d home.
Host pattern · Match criteriaBlock selectors. Host *.internal, Host !prod *; Match host x exec "cmd", Match localnetwork 192.168.1.0/24, Match canonical, Match tagged t, Match final.
IdentityFile · IdentitiesOnly yesKey (additive) · offer only configured keys — fixes "Too many authentication failures" when the agent holds many.
UseKeychain yesmacOS only: read/store the key passphrase in the Keychain. Pair with AddKeysToAgent yes (or ask/confirm/1h).
IdentityAgent path|noneWhich agent socket (a 1Password/Secretive agent, or none).
ProxyJump [u@]h[:p] · ProxyCommand cmd · noneJump host(s) · custom transport; the first one seen wins.
ControlMaster auto · ControlPath ~/.ssh/cm-%C · ControlPersist 10mConnection sharing: reuse one TCP+auth for later sessions/scp; keep the master alive 10 min after the last session. Path must be unique per host/port/user (%C).
ServerAliveInterval 30 · ServerAliveCountMax 3Encrypted keepalives: detect a dead link in 90 s and keep NAT mappings alive. TCPKeepAlive is the kernel one (spoofable).
LocalForward · RemoteForward · DynamicForwardPersistent -L/-R/-D. ExitOnForwardFailure yes to fail loudly; GatewayPorts yes to bind wildcard.
ForwardAgent yes|no · ForwardX11 yesForwarding (caution with agent). PermitRemoteOpen limits a reverse SOCKS.
StrictHostKeyChecking ask|yes|accept-new|no · UserKnownHostsFile · HashKnownHosts yes · UpdateHostKeys yes · CheckHostIPHost-key policy and storage. UpdateHostKeys defaults to yes: extra or replacement keys sent by an already-trusted server are added to known_hosts. KnownHostsCommand for dynamic fleets.
HostKeyAlgorithms · KexAlgorithms · Ciphers · MACs · PubkeyAcceptedAlgorithmsAlgorithm lists; + append, - remove, ^ prepend (e.g. +ssh-rsa for an ancient switch). WarnWeakCrypto no-pq-kex.
sshd is the daemon that accepts SSH logins ("Remote Login" on a Mac); sshd_config is the file that decides who may log in, how, and what they may forward.
the daemon on macOS
sudo systemsetup -setremotelogin onRemote Login on (flips the GUI toggle). Under the hood: launchctl enable system/com.openssh.sshd && launchctl bootstrap system /System/Library/LaunchDaemons/ssh.plist.
sudo launchctl print system/com.openssh.sshdState; socket-activated, so no pid until a connection. kickstart -k restarts after config changes; sshd re-reads config on HUP. In ps, 10.x shows the listener sshd plus /usr/libexec/sshd-session per connection and sshd-auth for the pre-auth phase.
sudo sshd -t · -T · -T -C user=x,addr=yTest config and keys · dump effective config · simulate a connection for Match evaluation. -G parse and print.
sudo sshd -ddd -p 2222Debug instance in the foreground on another port (leave launchd's alone).
/etc/ssh/sshd_config.d/100-macos.confApple's drop-in: UsePAM yes, AcceptEnv LANG LC_*, Subsystem sftp /usr/libexec/sftp-server. Put overrides in 000-local.conf (sorts first, first value wins).
~/.ssh/authorized_keys optionscommand="…", from="10.0.0.0/8", restrict, no-port-forwarding, permitopen="h:p", permitlisten, no-agent-forwarding, no-pty, expiry-time="20261231", cert-authority, principals. File must not be group/world writable (StrictModes).
hardening keys
Port · ListenAddress · AddressFamilyWhere to listen (repeatable).
PermitRootLogin no · PasswordAuthentication no · KbdInteractiveAuthentication no · PermitEmptyPasswords noKeys only (with PAM, keyboard-interactive is password auth — disable both).
AllowUsers alice admin@192.168.1.0/24 · AllowGroups · DenyUsers · DenyGroupsWho may log in (Deny checked first).
Match User|Group|Host|Address|LocalPort … · RefuseConnectionConditional blocks (limited keyword set).
MaxAuthTries 3 · LoginGraceTime 30 · MaxSessions · MaxStartups 10:30:100 · PerSourcePenaltiesBrute-force resistance (penalties are on by default in 10.x).
ssh-keygen, ssh-add, ssh-agent, ssh-keyscan, ssh-copy-id. On a Mac the agent already runs (launchd) and the Keychain can hold passphrases.
ssh-keygen
ssh-keygen(1)
ssh-keygen is a tool that creates and manages SSH key pairs, fingerprints, known-host entries, certificates and file signatures.
ssh-keygen -t ed25519 -C "you@laptop 2026" [-f ~/.ssh/id_ed25519]Generate (ed25519 is the default type). -t rsa -b 4096 for old hosts; -t ed25519-sk/ecdsa-sk for FIDO keys. -N '' no passphrase; -a 100 KDF rounds.
-l -f key.pub [-E md5] · -lvFingerprint (SHA256 default; MD5 for old GitHub-era comparisons) · with randomart. Works on known_hosts too.
-y -f key > key.pubRegenerate a lost public key from the private one.
-R host · -R '[host]:2222' · -F host · -HRemove a host from known_hosts (after a legitimate key change) · find (works on hashed) · hash the file.
-r host.example.comPrint SSHFP DNS records for the host keys.
-s ca_key -I id -n principals -V +52w key.pubSign a user certificate (-h for a host cert); -O force-command=…,source-address=…,no-port-forwarding. -L -f cert.pub prints one.
ssh-agent is the daemon that holds decrypted private keys in memory and signs challenges on ssh's behalf; ssh-add is a tool that loads keys into it — on macOS optionally storing their passphrases in the Keychain.
ssh-add
ssh-add [key]Load a key (default ~/.ssh/id_*). Exit 2 = no agent reachable. Refuses keys readable by others.
-l / -LList fingerprints / full public keys of loaded keys.
--apple-use-keychain keymacOS: store the passphrase in the Keychain as you add. Old -K still works with a warning (APPLE_SSH_ADD_BEHAVIOR=macos silences it).
--apple-load-keychainmacOS: load every key whose passphrase is in the Keychain (old -A). Rarely needed if UseKeychain+AddKeysToAgent are set.
-d key.pub / -DRemove one / all (also removes a Keychain-stored passphrase for that key).
-t 4h · -cLifetime · confirm each use via askpass.
-x / -XLock / unlock the agent with a password.
-h bastion -h 'bastion>*.internal'Destination-constrained key: usable only toward those hosts, even when forwarded.
$SSH_AUTH_SOCK = /var/run/com.apple.launchd.*/Listenerslaunchd starts the agent on demand (com.openssh.ssh-agent); it's already in every login session. eval $(ssh-agent) makes a second, keychain-unaware one.
launchctl kickstart -k gui/$(id -u)/com.openssh.ssh-agentReset the system agent (empties it). ssh-agent -k can't — SSH_AGENT_PID isn't set.
eval "$(ssh-agent -s -t 8h)"A throwaway agent for this shell only. -a path fixed socket, -D foreground, -P pattern allowed PKCS#11 libs.
ssh-keyscan · ssh-copy-id
ssh-keyscan is a tool that collects hosts' public keys without logging in; ssh-copy-id is a tool that installs your public key in a remote host's authorized_keys.
ssh-keyscan -t ed25519 host >> ~/.ssh/known_hostsFetch host keys without logging in (verify the fingerprint out of band first!). -H hashed, -p port, -f file (hosts or CIDRs), -T secs, -D as SSHFP records.
ssh-keyscan -t ed25519 host | ssh-keygen -lf -Show a host's fingerprint for comparison.
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@hostAppend your public key to the remote authorized_keys (password once). -p port, -o opt, -n dry run, -f force (no private key needed), -s via SFTP only.
scp speaks SFTP underneath since OpenSSH 9. /usr/bin/rsync is openrsync (OpenBSD's, protocol 29) — Apple-specific -E, several samba-rsync flags missing; brew install rsync for the real one.
scp · sftp
scp is a tool that copies files to or from a remote host over SSH in one command; sftp is an interactive file-transfer client over the same protocol.
scp
scp file host:~/ · scp -r host:/var/log/nginx ./logsUp / down (recursive). ./ prefix for local names containing :. Also scp://user@host:port/path.
--daemon · rsync://host/module/Daemon mode / talk to a daemon (no ssh).
-X -A --iconv --info=progress2 --mkpathNot in openrsync. brew install rsync (protocol 31) if you need them; both ends negotiate down to the older protocol otherwise.
curl & HTTP
Apple's curl 8.7 is built on SecureTransport (Keychain trust, no SSLKEYLOGFILE, no HTTP/3). wget isn't shipped. networkQuality and ab are.
curl — output & requests
curl(1)
curl is a tool that transfers data to or from a URL over HTTP(S) and two dozen other protocols — the universal command-line HTTP client.
-o file · -O · -J · --output-dir d · --create-dirsSave to file · remote name · from Content-Disposition · into a directory.
-s · -S · -sS · -# · --no-progress-meterSilent · show errors · the script idiom · progress bar.
curl is also the tool that lets you control every layer of that transfer — address family, source interface, DNS overrides, proxies, TLS versions and certificates.
--resolve host:443:203.0.113.7Command-line hosts entry — test a server before DNS changes (keeps SNI/Host right). --connect-to host:443:other:8443 similar.
--connect-timeout 5 · -m 60 · --retry 5 · --retry-all-errors · --retry-connrefused · --retry-delay 2Timeouts and retries (transient errors and 408/429/5xx by default).
--limit-rate 500K · -Y 1000 -y 30 · --max-filesize 100MThrottle · abort if slower than 1000 B/s for 30 s · size cap (exit 63).
-Z · --parallel-max N · --rate 10/mParallel transfers (URL globs f[1-20].jpg) · cap · request rate.
--unix-socket /var/run/docker.sock · -N · --tcp-fastopen · --keepalive-time 60 · --happy-eyeballs-timeout-ms 200 · --doh-urlLocal socket · unbuffered (server-sent events) · TCP Fast Open · keepalive · v6 head start · DNS over HTTPS.
telnet://host:25 · smtp://host --mail-from a --mail-rcpt b -T msg.txt · imap://host/INBOXPoor man's telnet · send mail · fetch mail.
nscurl --ats-diagnostics https://host · nscurl -v -o - url · -k · -m 10 · --no-expensive · --backgroundApple's URLSession client (/usr/bin/nscurl, no man page; --help): the same TLS stack, Keychain trust, proxy settings and App Transport Security (ATS) rules an app gets. --ats-diagnostics reports which ATS exceptions a server would need — the tool for "works in curl, fails in the app". --fingerprint prints the server cert's hash; --ats-tls-version sets a minimum.
networkQuality is a tool that measures your connection's capacity and responsiveness under load; ab is a tool that load-tests an HTTP server; python3 -m http.server is the one-line way to serve a directory; wget is a Homebrew tool that downloads and mirrors web content non-interactively.
networkQuality
networkQuality [-v]Apple's capacity + responsiveness test against Apple's CDN: uplink/downlink Mbps, responsiveness in RPM (round trips per minute under load; <1000 low, 1000–2000 medium, >2000 high), idle latency. Uses hundreds of MB.
wget -m -np -k -E -p -e robots=off --wait=1 url/ · -r -l1 -A '*.pdf' -np url/Mirror a section for offline reading · every PDF linked from a page. -np is what stops you slurping the whole host.
TLS & certificates
/usr/bin/openssl is LibreSSL 3.3.6 (Homebrew's OpenSSL 3.x shadows its man page); security is the Keychain and trust-store CLI. openssl verify uses /etc/ssl/cert.pem, security verify-cert uses the Keychain — Safari agrees with the latter.
openssl
LibreSSL 3.3.6
openssl is a tool that creates and inspects keys, certificates and TLS connections — on macOS it is the LibreSSL build.
openssl version -a · which -a opensslWhich one you're running and its config dir.
s_client -connect host:443 -servername host [-showcerts] </dev/nullTLS handshake as a client; prints chain, protocol, cipher, ALPN, "Verify return code". -servername = SNI (without it you may get the default vhost). </dev/null so it exits (-brief is OpenSSL-only).
s_client -alpn h2 -tls1_3 · -starttls smtp|imap|pop3|ftp|ldap|xmpp|postgres|mysql · -status · -cert c -key k · -CAfile ca.pemForce ALPN/version · opportunistic TLS on plaintext ports · OCSP stapling · client cert · custom CA.
req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -days 825 -keyout key.pem -out cert.pem -subj '/CN=pi.local' -addext 'subjectAltName=DNS:pi.local,IP:192.168.1.50'Self-signed cert with a SAN, one shot. Apple rejects >825 days and no-SAN certs.
list · kdf · mac · -provider · x509 -extOpenSSL 3.x only — Homebrew's /opt/homebrew/bin/openssl.
security (Keychain & trust)
security(1)
security is a tool that manages the macOS Keychain and trust store — certificates, identities, trust settings and stored passwords.
trust & certificates
security verify-cert -v https://hostConnect and evaluate the presented chain with the macOS trust store — what Safari and Apple's curl see.
verify-cert -c leaf.pem -c inter.pem [-r root.pem] -p ssl -n host [-d YYYY-MM-DD] [-R ocsp] [-P]Offline chain check with policy, hostname, date, revocation; -P prints the chain.
add-trusted-cert -r trustRoot -p ssl -k ~/Library/Keychains/login.keychain-db cert.pemTrust a cert for this user (GUI auth dialog). sudo … -d -k /Library/Keychains/System.keychain for all users. -r trustAsRoot for a non-self-signed leaf; -e hostnameMismatch to tolerate CN mismatch; -p ssl|smime|codeSign|basic|eap|IPSec.
remove-trusted-cert [-d] cert.pem · dump-trust-settings [-d|-s] · trust-settings-export -d f · trust-settings-importUndo · list user/admin/system trust · backup/restore.
find-certificate -a -c 'name' [-p] [-Z] [keychain]Search (-p PEM out, -Z hashes). find-certificate -a -p /System/Library/Keychains/SystemRootCertificates.keychain > roots.pem exports Apple's roots for Homebrew tools.
security error -25293Decode an OSStatus (errSecAuthFailed).
SMB · NFS
File sharing from the shell. The fs types are smbfs and nfs (no cifs); mount points must exist first and shouldn't live under /Volumes in scripts. The AFP client (mount_afp) is deprecated — its man page announces removal; FTP is gone.
SMB client: smbutil · mount_smbfs
smbutil is a tool that lists shares and inspects the SMB client's sessions, dialects, signing and multichannel state; mount_smbfs (via mount -t smbfs) is the tool that mounts an SMB share from the shell.
smbutil
smbutil view //user@server · -G //guest@nas.local · -NList shares (prompts) · guest · no prompt. -a anonymous, -A auth only.
smbutil statshares -a · -m /Volumes/X [-f Json]Per-mount negotiated SMB version, signing, encryption, caching, server capabilities. The only way to see these.
smbutil multichannel -a · -m path [-i|-c|-s|-x]Multichannel: session, client NICs, server NICs, established channels.
smbutil status host · lookup name · identity //server · dfs smb://server/root · snapshot -m path · smbstat pathNetBIOS name/workgroup · NetBIOS→IP · who the server thinks you are · DFS (Distributed File System) referrals · Windows VSS snapshots · per-file info.
sudo smbdiagnose -f dirSMB diagnostics bundle: a packet trace of SMB traffic while it runs plus smbd debug logs — it captures file names and the authentication exchange, so treat the output as sensitive.
~/Library/Preferences/nsmb.conf · /etc/nsmb.conf[default] keys: signing_required=yes, protocol_vers_map=4 (SMB2+; 7 to allow SMB1), mc_on=no, dir_cache_off=yes, port445=both, per-server sections, passwords for -N. See nsmb.conf(5).
mount_smbfs (via mount -t smbfs)
mkdir -p ~/mnt/media; mount -t smbfs //user@host/Share ~/mnt/mediaMount (prompts). URL: //[domain;][user[:pw]@]host[/share]. Password in the URL shows in ps.
-o nobrowse,soft · -o shareencrypt · -o ro,noexec,nosuidHide from Finder + fail instead of hang · force SMB3 encryption · generic flags. Also nostreams nodatacache automounted sessionencrypt filemode= dirmode=.
-N · -s · -t @GMT-2026.08.01-03.00.00 · -f 0644 -d 0755No password prompt (nsmb.conf) · new session · mount a VSS snapshot read-only · file/dir modes.
open 'smb://user@host/Share' · osascript -e 'mount volume "smb://host/Share"'Finder mount at /Volumes/Share using Keychain credentials (returns before the mount completes).
sharing is a tool that creates and edits the share points File Sharing offers over SMB; netbiosd is the daemon that registers the Mac's NetBIOS name and browses the workgroup.
sudo sharing -l [-f json]List share points (System Settings → General → Sharing → File Sharing). Only matter when File Sharing is on (com.apple.smbd).
sudo sharing -a /Users/Shared/scans [-n name] [-S smbname]Add a share. -e name edit, -r name remove.
mount_nfs (via mount -t nfs) is the tool that mounts an NFS export; nfsd is the NFS server and its control command; showmount is a tool that lists what a server exports; rpcinfo is a tool that queries the RPC portmapper; nfsstat is a tool that reports NFS client and server statistics.
client
mount -t nfs -o vers=4,soft,intr host:/export ~/mnt/xMount. Default is v3 — say vers=4. soft/intr so a dead server doesn't hang you; deadtimeout=60 auto-unmounts.
-o vers=3,resvport,nolocks · -o bg,tcp,rsize=65536,wsize=65536 · -o sec=krb5 · -o nfc · -o namedattrLinux "secure" exports need resvport (root) · background retry, big buffers · Kerberos · NFC name normalisation for Linux servers · xattrs as named attributes.
/etc/nfs.confnfs.client.mount.options=vers=4,soft,intr sets defaults for every mount; nfs.client.allow_async; server keys nfs.server.nfsd_threads, nfs.server.require_resv_port=0.
showmount -e host · -a · -A · -p 4 hostExports · who has mounts · Bonjour-advertised NFS servers · ping NFS NULL for version 4.
rpcinfo -p host · -t host nfs 4 · -u host mountd · -b nfs 3Portmapper table · TCP/UDP null-procedure pings · broadcast for servers on the LAN. macOS runs rpcbind on demand.
/etc/exportsBSD syntax: /Users/Shared/nfs -alldirs -mapall=alice -network 192.168.1.0 -mask 255.255.255.0 (or -network 192.168.1.0/24, hostnames, -ro, -maproot=, -sec=sys:krb5). Subdirectories must be on the same volume.
sudo nfsd checkexports · enable · disable · status · update · start · stop · restart · verbose upValidate · turn on persistently (starts now; autostarts while /etc/exports exists) · off · state · reload exports (HUP) · lifecycle · log level.
Full Disk Access for /sbin/nfsd"sandbox_check failed. nfsd has no read access" ⇒ grant it in Privacy & Security, restart nfsd.
showmount -e localhost · rpcinfo -p localhostVerify registration. Firewall: allow nfsd and rpcbind, or disable stealth mode.
Services & daemons
Everything network-facing is a launchd job. Labels ≠ plist names. Most ship disabled: enable then bootstrap. Socket-activated daemons show no pid until the first connection.
launchctl (network subset)
launchctl(1)
launchctl is a tool that controls launchd — the init system that starts, stops, enables and supervises every network daemon on macOS.
sudo launchctl print system/com.openssh.sshdFull state of a daemon: path, pid, last exit, run reasons, sockets. Format is explicitly unstable.
sudo launchctl list | grep -E 'ssh|smb|nfs|httpd'Legacy 3-column view (pid / last exit / label) — stable.
sudo launchctl print-disabled systemWhich services are disabled (stored off-plist in launchd's own DB).
sudo launchctl enable system/LABEL · disablePersistent on/off. A disabled label refuses to bootstrap (error 5, "Input/output error").
sudo launchctl bootstrap system /System/Library/LaunchDaemons/ssh.plist · bootout system/LABELLoad / unload (the modern load/unload -w).
sudo launchctl kickstart -kp system/LABELStart now / kill and restart (-k), print pid (-p).
sudo launchctl kill HUP system/com.apple.nfsdSignal a service (reload configs).
launchctl blame · attach · error 5 · config system path …Why it launched · debugger · decode exit code · PATH for services (reboot).
/System/Library/LaunchDaemons (sealed) · /Library/LaunchDaemons (yours, root:wheel 644) · ~/Library/LaunchAgentsWhere plists live. To change an Apple one, copy it to /Library/LaunchDaemons under a new label.
Small servers & helpers
This card is the roll-call of the smaller network daemons and helpers Apple ships — a DHCP server, a TFTP server, time sync, Screen Sharing, PPP, legacy IPsec — plus the utilities that keep the Mac awake or hand a URL to the right app.
bootpdDHCP/BOOTP server and relay (/usr/libexec/bootpd, disabled; Internet Sharing starts it). Config /etc/bootpd.plist (rewritten by Internet Sharing!), static leases in /etc/bootptab, leases in /var/db/dhcpd_leases. Bench use: sudo bootpd -d -D -i en5.
tftpd/usr/libexec/tftpd, label com.apple.tftpd, serves /private/tftpboot (files must exist and be world-readable; uploads only overwrite world-writable files). Enable + bootstrap tftp.plist. Client: tftp host (interactive: binary, get, put, blocksize) or curl tftp://host/f.
natpmpd · dhcp6dInternet Sharing's helpers: the NAT-PMP port-mapping responder (/usr/libexec/natpmpd, rules under the com.apple/100.InternetSharing/natpmp pf anchor; test with dns-sd -X) and a stateless DHCPv6 server (com.apple.dhcp6d).
sntpdSNTP server: sudo launchctl enable system/com.apple.sntpd + bootstrap; -L claims stratum 1 — serve time to a bench LAN.
timed · /etc/ntp.conf · systemsetup -setnetworktimeserverThe daemon that keeps time (never start by hand) · its server list (rewritten by systemsetup).
screensharing · vnc://hostScreen Sharing (TCP 5900; com.apple.screensharing; System Settings → Sharing). open vnc://host launches the client.
kdumpdRemote kernel-panic receiver, UDP 1069 → /var/tmp/PanicDumps. Kernel work only.
mount_webdav -i https://host/dav ~/mnt/davWebDAV mounts (what Finder's Connect to Server does for http URLs); -s require secure auth, -S no UI, -v name volume name.
ippfind _ipp._tcp --ls · ippfind -n 'Office' _ipps._tcpCUPS's printer finder: browse IPP/IPPS printers over Bonjour and query their attributes.
ftp-proxy · vpndRelics that still ship: pf's FTP proxy (/usr/libexec/ftp-proxy) for active-mode FTP through NAT, and the macOS Server-era VPN server daemon (L2TP/IPsec).
pppd /dev/cu.usbmodem1101 115200 noauth local nodetach 10.0.0.1:10.0.0.2PPP over a serial/USB link to a board. Files under /etc/ppp/. macOS's own VPN/PPPoE drive it internally.
racoon · setkeyIKEv1 for legacy L2TP/Cisco IPsec profiles, launched on demand. New setups: IKEv2 profiles, WireGuard, Tailscale. sudo setkey -D dumps kernel SAs.
caffeinate -i cmd · -s · -t 3600 · -w pidKeep the Mac awake for a transfer/server (-s = on AC only; pmset -g assertions shows holders).
open http://host:8000/ · smb://host/share · vnc://host · ssh://user@host · -a Safari urlHand a URL to LaunchServices (mounts, launches Screen Sharing / Terminal).
sudo tmutil setdestination -p 'smb://user@nas/tm' · destinationinfo -XTime Machine over SMB (server needs fruit:time machine = yes). Needs Full Disk Access.
dsconfigad -show · -add domain -username u · odutil show nodesActive Directory binding and Open Directory state (network accounts, Kerberos SSO).
Files & paths
Where network configuration and state actually live on disk.
Configuration
This card lists the files where macOS network configuration actually lives, so you know what to read, back up, or leave alone.
/Library/Preferences/SystemConfiguration/preferences.plistThe Setup: store — locations, services, IPv4/IPv6/DNS/proxy config, hostnames. What networksetup edits. Don't hand-edit.
…/SystemConfiguration/NetworkInterfaces.plistHardware → BSD name mapping (why a replaced adapter becomes en6).
…/SystemConfiguration/com.apple.airport.preferences.plist · com.apple.smb.server.plist · com.apple.nat.plistKnown Wi‑Fi networks · SMB server identity · Internet Sharing config (rewritten on start).
/etc/hostsStatic names (read by mDNSResponder; honoured by apps and ping, ignored by dig). Ships with localhost, broadcasthost.
/etc/resolver/<domain>Per-domain resolver clients (create the directory). /etc/resolv.conf → /var/run/resolv.conf is generated.
~/Library/Application Support/com.apple.container/Apple container's state: images, the downloaded kernel, network definitions. Its launchd jobs are user agents — launchctl list | grep container shows com.apple.container.apiserver, …container-network-vmnet.default and one …container-runtime-linux.<name> per running container.
/Library/Preferences/SystemConfiguration/ · /Library/Preferences/OpenDirectory/Ditto for Open Directory (AD/LDAP binding).
Homebrew additions
What Apple doesn't ship. brew install the formula named; Homebrew's own curl/openssl shadow Apple's man pages once installed.
Worth installing
This card lists the third-party tools Homebrew provides for jobs Apple ships no tool for — continuous traceroute, port scanning, throughput testing, packet analysis and more.
mtrtraceroute + ping, continuously: per-hop loss and latency. sudo mtr -n host; -r -c 100 report mode. Needs sudo or the setuid helper.
nmapPort scanner and much more: nmap -sn 192.168.1.0/24 (who's up), -p- host (all ports), -sV versions, -O OS guess, --script NSE. Scan only what you own.
doggo · dog · drill / kdig (knot)Modern dig replacements with DoH/DoT support and colour.
mosh · autossh · sshuttle · ssh-auditRoaming shell over UDP · self-restarting tunnels · VPN over ssh with no server setup · audit an sshd's algorithms.
wireguard-tools (wg, wg-quick) · tailscaleWireGuard CLI · the mesh (or the App Store apps).
ngrep · tcpflow · termsharkgrep for packets · reassemble TCP streams to files · Wireshark in the terminal.
mkcert · step · testssl.sh · certbotLocal trusted dev certs (installs a CA into the Keychain) · smallstep CA/ACME client · TLS server audit · Let's Encrypt.
ipcalc · sipcalc · whois (newer) · inetutils (ftp, telnet) · lftp · ncftpSubnet math · the removed clients back · scriptable FTP.
dnsmasq · unbound · caddy · nginx · miniserveLocal DNS/DHCP · validating resolver · zero-config HTTPS server · web server · one-line file server with upload.
arping · arp-scan · nbtscan · avahi-utils (no) ARP ping / LAN inventory by ARP (sudo arp-scan --localnet) · NetBIOS scan. Avahi isn't for macOS — use dns-sd.
container — the networking subset
Apple 1.3.1
container is Apple's open-source container runtime for Apple silicon — brew install container, or the signed .pkg from github.com/apple/container. It runs one lightweight Linux VM per container, each with its own vmnet address, which makes its networking unlike Docker's. These are the subcommands that touch the network; the Config Plumbing guide card above explains what they do to ifconfig.
service
container system start · stop · statusBring up the apiserver and network plugin (the first run offers to download the Kata kernel; --enable-kernel-install / --disable-kernel-install answers that non-interactively) · park everything · running, or not running and not registered with launchd.
container system dns create <domain> · dns ls · dns rmRegister a local DNS domain so the Mac resolves containers by name. Must run as an administrator; --localhost <ip> redirects an address to localhost.
container system property lsDefaults as TOML: per-container cpus/memory, the builder image, and the kernel URL and digest actually installed.
container system logs · system dfService logs (the unified log unless system start --log-root was used) · disk used by images, containers and volumes.
networks
container network ls · inspect <name>Name and subnet · JSON with ipv4Gateway, ipv4Subnet, the IPv6 prefix, mode (nat) and plugin (container-network-vmnet). The built-in one is default, 192.168.64.0/24.
container network create [--subnet 10.10.0.0/24] [--subnet-v6 …] [--internal] <name>A second vmnet network. --internal is host-only — no NAT out. --option k=v for plugin options, --plugin to choose another. network rm, network prune remove them.
containers
container ls · ls -aThe IP column is the container's real address on the vmnet subnet — ping it, curl it, point a browser at it. Addresses are handed out in order from .2.
container run --network <name>[,mac=XX:XX:…][,mtu=VALUE]Attach to a named network, pin the MAC, or raise the MTU — eth0 comes up at 1280, not 1500.
container run -p [host-ip:]host-port:container-port[/proto]Publish to the host. Only needed for other machines — the Mac itself already reaches every port. With no host IP it binds *, so the LAN can reach it. It is a listener inside the container process, so it shows up in lsof -i and involves no pf rule.
container run --publish-socket host_path:container_pathPublish a Unix socket to a host path instead of a TCP port.
container run --dns <ip> · --dns-search · --dns-domain · --dns-option · --no-dnsOverride what lands in the guest's /etc/resolv.conf; by default it is a single nameserver line pointing at the gateway.
container inspect <name>networks[] gives hostname, ipv4Address, ipv4Gateway, ipv6Address, macAddress, mtu and which network it joined.
container stats · logs <name> · exec <name> ip routeLive TUI of CPU, memory, net Rx/Tx and block I/O per container · its stdout · any command inside the guest, which is where ip, ss and /etc/resolv.conf actually live.
Error messages
What the words mean, mapped to the layer that produced them.
Symptom → cause
This card decodes the error messages the network stack and its tools produce, mapped to the layer that generated them.
169.254.x.x addressDHCP got no answer (self-assigned link-local). Link is up but nothing served a lease: wrong VLAN, DHCP server down, MAC filtering, captive portal.
Connection refused (ECONNREFUSED 61)Host reachable, nothing listening on that port (RST came back) — or the listen queue is full. Check lsof -iTCP -sTCP:LISTEN on the server; bound to 127.0.0.1 instead of 0.0.0.0?
Operation timed out (ETIMEDOUT 60)SYN sent, nothing came back: firewall dropping silently, host down, wrong network, or asymmetric route. Takes ~75 s. nc -zv -w 3 host port; traceroute -P tcp -p port.
No route to host (EHOSTUNREACH 65)A route exists but the next hop can't be resolved: ARP/NDP failed (host down on the LAN), or ICMP host-unreachable came back. arp -an shows (incomplete).
Network is unreachable (ENETUNREACH 51)No route at all — no default gateway (netstat -rn), or IPv6 target with no IPv6 route.
Network is down (ENETDOWN 50)The interface itself is down (ifconfig lacks RUNNING) or IPv6 is ifdisabled.
Address already in use (EADDRINUSE 48)Port bound by another process — or by a TIME_WAIT ghost without SO_REUSEADDR. lsof -i :port; wait 30 s.
Can't assign requested address (EADDRNOTAVAIL 49)Binding/connecting from an IP this Mac doesn't have (stale DHCP address, wrong interface, IPv6 privacy address rotated).
Connection reset by peer (ECONNRESET 54)The other side sent RST mid-stream: process crashed, idle NAT mapping expired, an IDS/firewall injected it, or SMB/HTTP server killed a bad request.
Broken pipe (EPIPE 32) / SIGPIPEWrote to a connection the peer had already closed. SO_NOSIGPIPE turns the signal into an error.
nodename nor servname provided, or not knownmacOS's getaddrinfo failure text = DNS didn't resolve (NXDOMAIN, no resolver reachable, or a typo). scutil --dns, dns-sd -G v4 name.
Temporary failure in name resolution / SERVFAILThe resolver is up but failed upstream (DNSSEC, timeout). Try dig @1.1.1.1.
Permission denied (EACCES 13)Port < 1024 without root · raw socket (ping-like) without root · /dev/bpf without root (tcpdump) · broadcast without SO_BROADCAST.
No buffer space available (ENOBUFS 55)mbufs exhausted or a send queue full — netstat -m; usually a runaway sender or a driver bug.
Message too long (EMSGSIZE 40)UDP datagram bigger than net.inet.udp.maxdgram or the path MTU with DF set.
ping: sendto: No route to host · Request timeout for icmp_seqLocal routing/ARP failure · packets sent, no replies (remote down, ICMP filtered, stealth mode).
traceroute: * * *That hop doesn't send ICMP time-exceeded (many don't) — not necessarily a fault. At the end, the target filters UDP: retry -I or -P tcp.
ssh: Permission denied (publickey)No offered key was accepted: wrong key, not in authorized_keys, bad permissions on ~/.ssh (700) / authorized_keys (600), or wrong user. ssh -vvv shows what was offered.
WARNING: REMOTE HOST IDENTIFICATION HAS CHANGEDHost key differs from known_hosts: reinstalled server, DHCP address reuse, or a MITM. Confirm out of band, then ssh-keygen -R host.
Too many authentication failuresThe agent offered too many keys before the right one: IdentitiesOnly yes + IdentityFile.
ssh_exchange_identification / kex_exchange_identification: Connection closedConnected but the server dropped us before the banner: MaxStartups saturation, fail2ban/penalties, a TCP wrapper, or it isn't sshd on that port.
client_loop: send disconnect: Broken pipeIdle connection killed by a NAT/firewall: ServerAliveInterval 30.
curl: (60) SSL certificate problem: unable to get local issuer certificateChain doesn't reach a trusted root: server missing its intermediate, private CA not installed, or (Homebrew curl) the CA isn't in /etc/ssl/cert.pem. openssl s_client -showcerts to see what was sent.
curl: (35) · (6) · (7) · (28) · (52)TLS handshake failed (version/cipher mismatch, not TLS at all) · DNS · connect · timeout · empty reply (server closed without a response — wrong port, HTTP on an HTTPS port).
Bonjour / LAN hosts visible in Safari, invisible to a scriptLocal Network privacy (TCC): the terminal app that launched the tool was denied. System Settings → Privacy & Security → Local Network.
"You are not associated with an AirPort network." / <redacted>Not a Wi‑Fi problem — SSID privacy. See the Wi‑Fi section.
mount_smbfs: server connection failed: No route to host · Authentication error · mount_smbfs: … already mountedPort 445 unreachable · wrong credentials or SMB1-only server · Finder already has it under /Volumes.
NFS: Permission denied · RPC: Program not registered · RPC prog. not availNot exported to you · nfsd/mountd not running · no rpc.statd (mount with nolocks).
Operation not permitted (EPERM 1) as rootSIP/TCC, not permissions: Full Disk Access for the terminal (systemsetup, nfsd, tmutil), or a sealed path.
Field Guide
The traps, in one place, and how this sheet was built.
Things that will bite you
ifconfig, route, hostname and ipconfig set changes evaporate the moment configd re-evaluates the network (location switch, cable, VPN, DHCP renew). Persistent changes go through networksetup and scutil --set.
dig/host/nslookup don't use the system resolver. No /etc/hosts, no /etc/resolver/*, no VPN split DNS, no .local, no cache. scutil --dns is the config; dns-sd -G/dscacheutil -q host are the app's-eye view.
The DNS flush is two commands: sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder. Either alone isn't enough.
Service order beats metrics. With Wi‑Fi and Ethernet both up, networksetup -listnetworkserviceorder decides which carries the default route and DNS. Two default rows in netstat -rn are normal; the g one wins.
SSID from the shell is a moving target.airport removed in 14.4; networksetup -getairportnetwork and wdutil info redact without Location Services since 15. ipconfig getsummary en0 and system_profiler SPAirPortDataType still work on 26.6.
Local Network privacy is decided per launching app. A tool that sees no Bonjour or LAN replies from a script, while the same query works in Safari, has been denied under Privacy & Security → Local Network — for Terminal, not for the tool.
Private Wi‑Fi Address gives you a different MAC per SSID: DHCP reservations, MAC filters and "known device" lists all break. Turn it off per network if you rely on them.
Two firewalls. The Application Firewall (System Settings) is per-program and inbound-only; pf is off unless something enables it. Stealth mode makes your Mac ignore ping while you're trying to test it.
Never replace /etc/pf.conf wholesale. Apple's anchors carry Internet Sharing's NAT and the Application Firewall. Add an anchor, or load into one with pfctl -a. Use -E/-X, not -e/-d.
Apple's curl trusts the Keychain, not --cacert; Homebrew's tools trust /etc/ssl/cert.pem, not the Keychain. A private CA has to be installed both ways. openssl verify and security verify-cert can disagree for the same reason.
/usr/bin/openssl is LibreSSL 3.3; man openssl may show Homebrew's OpenSSL 3.x page. /usr/bin/rsync is openrsync (no -X -A --info=progress2; Apple's -E ≠ Samba's). telnet, ftp, ntpd, wget, mtr, nmap aren't shipped.
The ssh-agent is already running (launchd). eval $(ssh-agent) starts a second one that can't see Keychain passphrases. UseKeychain yes + AddKeysToAgent yes + one ssh-add --apple-use-keychain.
netstat has no PID column and its TCP stats read zero without sudo.lsof -i and nettop are the process views. tcpdump always needs sudo and skips lo0 unless named.
traceroute uses UDP; a trailing * * * usually means the target filters it — -I or -P tcp -p 443. ping -W is in milliseconds, -t and -i in seconds.
Mount points must exist, /Volumes belongs to Finder, the fs type is smbfs, NFS defaults to v3, and a dead NFS server hangs the process unless soft/intr.
launchd labels aren't plist names (ssh.plist → com.openssh.sshd, bootps.plist → com.apple.bootpd). Disabled services fail bootstrap with error 5; enable first. Socket-activated ones have no pid until used.
Full Disk Access is a network problem too: systemsetup, nfsd exports, tmutil setdestination and reading ~/Library/Preferences/nsmb.conf all fail with EPERM without it. SIP blocks DTrace; use fs_usage -f network and ktrace.
Internet Sharing rewrites /etc/bootpd.plist and its NAT config on every start. Fixed leases go in /etc/bootptab.
bridge100 and vmenetN exist only while a VM or container is running. They are vmnet's, not yours: the bridge holds 192.168.64.1 and does DHCP, DNS and NAT for the guests, and both interfaces vanish when the last guest stops. Apple's container runs one VM per container, so every container is a separate host on that subnet — reachable from the Mac on any port without -p, and not reachable from the LAN with it unless you meant to bind *.
IPv6 is on and preferred. Half-broken IPv6 shows up as 50–250 ms stalls (Happy Eyeballs), not failures. sudo ipconfig set en0 NONE-V6 or networksetup -setv6off to test; curl -4/-6 to compare.
Sleep. A MacBook serving files stops serving when it sleeps. caffeinate -s (on AC), systemsetup -setsleep Never, or Wake on LAN + a Bonjour Sleep Proxy.
How this reference was built
Every command in the index was checked against the man page shipped with macOS 26.6.2 (25G83) on Apple silicon, August 2026 and re-verified 4 September 2026 — roughly 120 pages in sections 1, 3, 4, 5, 7 and 8 — and against the live machine where behaviour and the page disagree (SSID redaction, the removed socketfilterfw logging flags, wdutil info demanding sudo despite its man page, SO_BINDTODEVICE appearing in the 26 SDK, man ftp now documenting a Tcl package because the binary is gone). The September pass retired two sysctl names that no longer exist (net.inet.tcp.ecn_negotiate_in, net.inet.icmp.icmplim), corrected the ICMP-redirect default, the ifconfig -C cloner list and the Internet Sharing launchd label, and added the bundled fping, jq, nscurl and dnctl. The container cards were written against Apple's container 1.3.1 installed from Homebrew and exercised on this machine — the addresses, MTU, bridge membership, resolv.conf, launchd labels and the behaviour of -p are all copied from what it actually did, not from its README. Versions: OpenSSH 10.3p1, LibreSSL 3.3.6, curl 8.7.1 (SecureTransport), tcpdump 4.99.1 (Apple 158), libpcap 1.10.1, lsof 4.91, BIND dig 9.10.6, openrsync protocol 29, fping 5.1, jq 1.8.2, ApacheBench 2.3, /usr/bin/python3 3.9.6 (Command Line Tools).
for c in ifconfig networksetup scutil ipconfig route arp ndp dig host nslookup dns-sd dscacheutil ping ping6 traceroute netstat lsof nettop nc tcpdump pcap-filter wdutil networkQuality pfctl pf.conf socketfilterfw ssh ssh_config sshd_config ssh-keygen ssh-add scp sftp rsync curl openssl security smbutil mount_smbfs mount_nfs nfsd sharing launchctl sysctl dnctl fping tcp ip inet6 resolver; do man -w $c; doneman 5 resolver; man 2 bind # section numbers matter/usr/bin/openssl version; which -a openssl; rsync --version; ssh -V; curl -V; tcpdump --version
The conceptual half follows the two open textbooks it cites — Peterson & Davie, Computer Networks: A Systems Approach (6th ed., CC BY 4.0), and Dordal, An Introduction to Computer Networks (2nd ed., CC BY-NC-ND) — with chapter links checked live. Threshold numbers (RSSI bands, RPM bands, MTU arithmetic) are the usual field values rather than anything Apple publishes. Nothing here is an Apple publication; commands that change system state are marked and were run only where reversible.
Filter box at the top matches names and descriptions across the whole index — type sudo, ipv6, keychain, pktap or redacted to slice it by topic. Press / to jump to it.