Debugging “can’t assign requested address” on macOS: When VPN Daemon Processes Prevent Socket…
Introduction
Debugging “can’t assign requested address” on macOS: When VPN Daemon Processes Prevent Socket Binding
Introduction
While attempting to SSH into an AWS EC2 instance, I encountered an error: Can't assign requested address. The confusing part? My internet connection worked perfectly—I could ping Google's DNS servers, browse the web, and my routing table showed a valid default gateway. Yet every attempt to connect to my AWS instance failed with the same error. At first, I thought it was a Security Group Error but the settings were correct.
After extensive investigation, I discovered the root cause: OpenVPN’s background daemon processes (ovpnagent and ovpnhelper) were running and creating multiple tunnel interfaces that prevented the macOS kernel from properly binding outbound sockets. Even though I had "quit" the OpenVPN application, these root-level daemons continued operating and maintaining conflicting network state.
The Error: Everything Works Except One Connection
The error was consistent:
$ ssh -i ~/key.pem ubuntu@[IP-ADDRESS]
debug1: Connecting to [IP-ADDRESS][IP-ADDRESS] port 22.
debug1: connect to address [IP-ADDRESS] port 22: Can't assign requested address
ssh: connect to host [IP-ADDRESS] port 22: Can't assign requested address
What made this particularly confusing was that everything else worked:
$ ping 8.8.8.8
64 bytes from 8.8.8.8: icmp_seq=0 ttl=116 time=40.157 ms
64 bytes from 8.8.8.8: icmp_seq=1 ttl=116 time=59.031 ms
# Ping works perfectly ✅
$ ifconfig | grep "inet "
inet [IP Address] netmask 0xfffffe00 broadcast [IP Address]
# Valid IP address assigned ✅
$ netstat -rn | grep default
default [IP Address] UGScg en0
# Valid default gateway ✅
So why couldn’t I connect to my AWS instance?
Understanding “Can’t Assign Requested Address”
On macOS, Can't assign requested address means the kernel cannot allocate a socket for the connection. This isn't about routing packets to the destination—it's about the kernel being unable to determine which source IP address to bind the outbound socket to.
Think of it like this: when you make a phone call, your phone needs to know which SIM card to use. If your phone thinks it has multiple SIM cards but some are corrupted or conflicting, it can’t complete the call — not because the recipient is unreachable, but because it can’t figure out how to place the call in the first place.
The Investigation: What Was Really Running?
Step 1: Check the Routing Table
First, I examined my routing table:
$ netstat -rn | grep default
default [IP Address] UGScg en0
default fe80::%utun0 UGcIg utun0
default fe80::%utun1 UGcIg utun1
default fe80::%utun2 UGcIg utun2
default fe80::%utun3 UGcIg utun3
This revealed five default routes — one IPv4 route through my physical network interface (en0), and four IPv6 link-local routes through virtual tunnel interfaces (utun0-3).
Step 2: Identify Active Network Interfaces
$ ifconfig | grep inet
inet 127.0.0.1 netmask 0xff000000 # Loopback
inet [IP Address]netmask 0xfffffe00 # Physical interface (en0)
inet6 [V 1]%utun0 prefixlen 64 # Tunnel 0
inet6 [V 2]%utun1 prefixlen 64 # Tunnel 1
inet6 [V 3]%utun2 prefixlen 64 # Tunnel 2
inet6 [V 4]%utun3 prefixlen 64 # Tunnel 3
Four virtual tunnel interfaces were active, each with its own IPv6 link-local address.
Step 3: The Smoking Gun — VPN Processes Still Running
Here’s where it got interesting. I had closed the OpenVPN application, but when I checked for VPN-related processes:
$ ps aux | grep -i vpn
root 80165 /usr/sbin/ovpnagent
root 80168 /usr/sbin/ovpnhelper
jinsuk 80863 /Applications/OpenVPN Connect/OpenVPN Connect.app/...
jinsuk 80867 OpenVPN Connect Helper (GPU).app/...
jinsuk 80868 OpenVPN Connect Helper.app/... --type=utility --utility-sub-type=network.mojom.NetworkService
jinsuk 80870 OpenVPN Connect Helper (Renderer).app/...
The VPN was still running! Not just the UI application, but critically:
ovpnagent(root process) - The core VPN routing engineovpnhelper(root process) - Network configuration helper- Multiple Electron renderer/helper processes
These background processes were maintaining the tunnel interfaces and interfering with socket binding, even though the main application window was closed.
Root Cause: VPN Daemon Processes Creating Network Ambiguity
The problem manifested as follows:
1. SSH attempts to connect to [IP-ADDRESS]:22
2. Kernel needs to create an outbound socket and bind it to a source IP
3. Kernel examines routing table:
- IPv4 default: Use en0 ([IP Address])
- IPv6 defaults: utun0, utun1, utun2, utun3 all claim to be valid
4. Kernel checks which interface to use for this connection:
- Routing says: Use en0 via gateway [IP Address]
- But VPN daemon (ovpnagent) is enforcing policies that say:
"Some traffic must go through tunnel interfaces"
5. Conflict: Should this connection use en0's IP or a tunnel interface?
6. Kernel cannot resolve the ambiguity
7. Result: EADDRNOTAVAIL - Can't assign requested address
The key insight: The VPN daemon processes were still enforcing routing policies and maintaining tunnel interfaces, creating ambiguity about which source address to use for outbound connections.
Why General Internet Traffic Still Worked
You might wonder: “If the VPN is causing conflicts, why does ping 8.8.8.8 work?"
The answer lies in how different protocols interact with VPN policies:
- ICMP (ping) — Often uses raw sockets with different binding rules
- DNS queries — Typically use system-level resolvers that bypass VPN filters
- General web traffic — May have been routed correctly by VPN’s routing rules
But SSH to a specific IP triggered the exact condition where the VPN’s routing policy conflicted with the kernel’s socket binding logic.
The Resolution: A Multi-Step Process
Step 1: Kill the VPN Application
$ sudo killall -9 "OpenVPN Connect"
$ sudo killall -9 ovpnagent
$ sudo killall -9 ovpnhelper
The -9 flag (SIGKILL) forces immediate termination without cleanup routines.
Step 2: Verify Processes Are Gone
$ ps aux | grep -i vpn
jinsuk 69001 /Applications/Docker.app/... vpnkit.connect=connect://2/1999 ...
root 83459 /usr/sbin/ovpnagent # ← Still running!
root 83462 /usr/sbin/ovpnhelper # ← Still running!
The daemons restarted themselves! This is because OpenVPN Connect installs system-level launch daemons that automatically restart when killed.
Step 3: Attempt Route Deletion
$ sudo route -n delete [IP-ADDRESS]
delete host [IP-ADDRESS]
This didn’t help because there was no specific route to delete — the problem was the default route ambiguity, not a stale host route.
Step 4: Flush Routing Table and Reset Interface
$ sudo ifconfig en0 down
$ sudo route -n flush
:: ::%utun0 done
:: ::%utun1 done
:: ::%utun2 done
:: ::%utun3 done
$ sudo ifconfig en0 up
This was the critical step. Flushing the routing table removed all the tunnel interface routes, and bringing en0 back up forced a clean re-initialization.
Step 5: Wait for Network Stabilization
After waiting a few seconds (the system needed time to rebuild routes via DHCP and reestablish the default gateway), I tried again:
$ nc -vz [IP-ADDRESS]
Connection to [IP-ADDRESS] port 22 [tcp/ssh] succeeded!
Success! The connection worked.
Step 6: Verify SSH Works
$ ssh -i ~/Documents/SSH-test/vpn-bastion.pem ubuntu@[IP-ADDRESS]
debug1: Connecting to IP-ADDRESS[IP-ADDRESS] port 22.
debug1: Connection established.
...
Welcome to Ubuntu 24.04.3 LTS (GNU/Linux 6.14.0-1011-aws x86_64)
ubuntu@ip-[IP Address]:~$
Perfect! SSH connected immediately.
Why This Solution Worked
Let’s break down what each step accomplished:
Killing VPN Processes
Before: ovpnagent + ovpnhelper running → Enforcing VPN routing policies
After: Processes killed → No active policy enforcement
Flushing Routes
Before: Multiple utun interfaces with default routes → Kernel confused
After: All tunnel routes cleared → Only physical interface remains
Interface Reset
Before: en0 has stale socket bindings → Conflicting state in kernel
After: en0 reinitialized cleanly → Fresh socket allocation possible
The Complete Fix: Proper VPN Daemon Removal
Since the daemons kept restarting, here’s what actually needs to be done to permanently stop them:
=> Unload Launch Daemons
# Find OpenVPN launch daemons
sudo launchctl list | grep -i openvpn
# Unload them
sudo launchctl unload /Library/LaunchDaemons/org.openvpn.connect.agent.plist
sudo launchctl unload /Library/LaunchDaemons/org.openvpn.connect.helper.plist
# Then kill processes
sudo killall -9 ovpnagent ovpnhelper
How To Prevent?
=> Properly Disconnect VPN Before Quitting
Always use the “Disconnect” button in the VPN application before closing it:
✅ Correct:
- Click “Disconnect” in OpenVPN Connect
- Wait for “Disconnected” status
- Quit the application
❌ Incorrect:
- Cmd+Q to quit without disconnecting
- Force quit the application
- Close MacBook lid while VPN is connected
Understanding the Error Evolution
One interesting aspect of this issue was how the error changed through the troubleshooting process:
State Error Meaning Initial Can't assign requested address Socket binding failed due to VPN daemon conflicts After killing daemons Can't assign requested address Daemons restarted automatically, same problem After route flush Network is unreachable Routes cleared, no path to destination After waiting Connection succeeded Network rebuilt, everything working
This progression actually helped confirm the diagnosis — each error indicated we were getting closer to resolution.
When You’ll Encounter This Issue
This specific error pattern occurs when:
- Using OpenVPN Connect — This application installs persistent launch daemons
- Improper VPN disconnection — Quitting the app without disconnecting
- System sleep/wake with VPN active — Can leave daemons in inconsistent state
- Multiple VPN applications — Cisco AnyConnect + OpenVPN + iCloud Private Relay
- Network switching with VPN active — Wi-Fi → Ethernet → Wi-Fi while connected
Quick Diagnostic Checklist
If you encounter Can't assign requested address:
# 1. Verify basic connectivity
ping 8.8.8.8
# 2. Check for multiple default routes (should be 1-2 max)
netstat -rn | grep default
# 3. Check for active tunnel interfaces (0-2 is normal)
ifconfig | grep "^utun"
# 4. Check for VPN daemon processes
ps aux | grep -i "vpn\|ovpn" | grep -v grep
# 5. If problems found, run the full reset
sudo launchctl unload /Library/LaunchDaemons/org.openvpn.connect.*.plist
sudo killall -9 "OpenVPN Connect" ovpnagent ovpnhelper
sudo route -n flush
sudo ifconfig en0 down && sleep 2 && sudo ifconfig en0 up
How Socket Binding Works
When an application creates a TCP connection:
int sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in local_addr;
local_addr.sin_family = AF_INET;
local_addr.sin_addr.s_addr = INADDR_ANY; // Kernel chooses source IP
local_addr.sin_port = 0; // Kernel chooses source port
bind(sock, (struct sockaddr*)&local_addr, sizeof(local_addr));
connect(sock, &remote_addr, sizeof(remote_addr));
When INADDR_ANY is used, the kernel must:
- Look up destination in routing table
- Determine which interface will be used
- Select an IP from that interface
- Bind socket to that IP
- Proceed with connection
VPN Daemon Interference
OpenVPN’s daemons (ovpnagent and ovpnhelper) operate at a low level:
User Space: [SSH Application]
↓
[System Calls: socket(), bind(), connect()]
↓
Kernel Space: [Socket Layer]
↓
[Routing Decision] ← VPN daemon injects policy here
↓
[Interface Selection] ← Conflict occurs here
↓
[Network Interface]
The daemons inject routing policies that say:
- “Traffic to X.X.X.X must use utun2”
- “All traffic except Y.Y.Y.Y uses utun2”
- “DNS traffic uses utun2”
When these policies conflict with the actual available interfaces, socket binding fails.
Why The Error is “Can’t Assign”
The error name is precise: the kernel literally cannot assign a source address because:
- Policy says: “Use tunnel interface”
- Reality: Tunnel interface doesn’t have a routable address for this destination
- Kernel: “I cannot assign a valid source address from the specified interface”
- Result:
EADDRNOTAVAIL
This is different from:
ENETUNREACH(Network unreachable) - No route existsEHOSTUNREACH(Host unreachable) - Route exists but host doesn't respondECONNREFUSED(Connection refused) - Host reachable but port closed
EADDRNOTAVAIL specifically means the local side cannot establish the socket, not that the remote side is unreachable.
Conclusion
The Can't assign requested address error when using OpenVPN Connect on macOS is caused by persistent background daemon processes (ovpnagent and ovpnhelper) that continue enforcing VPN routing policies even after the main application is closed. These daemons create and maintain multiple tunnel interfaces that cause socket binding ambiguity in the macOS kernel.
The resolution requires a complete network reset:
- Unload launch daemons to prevent auto-restart
- Kill all VPN processes including the root-level daemons
- Flush the routing table to clear tunnel interface routes
- Reset the primary interface to force clean socket allocation
Key Takeaways
- Quitting a VPN application ≠ disconnecting the VPN
- Background daemons can maintain network state even after app closure
- Socket binding errors occur before any packets are sent to the destination
- Multiple default routes create ambiguity that prevents socket allocation
If you see Can't assign requested address while using a VPN:
# Don't just quit the VPN app—actually kill the daemons:
sudo launchctl unload /Library/LaunchDaemons/org.openvpn.connect.*.plist
sudo killall -9 ovpnagent ovpnhelper "OpenVPN Connect"
sudo route -n flush
sudo ifconfig en0 down && sleep 2 && sudo ifconfig en0 up 메타데이터
- post_id
- 8cc68bfe72a6
- slug
- debugging-cant-assign-requested-address-on-macos-when-vpn-daemon-processes-prevent-socket-8cc68bfe72a6
- url
- https://medium.com/@jjpark067/debugging-cant-assign-requested-address-on-macos-when-vpn-daemon-processes-prevent-socket-8cc68bfe72a6
- canonical_url
- https://medium.com/@jjpark067/debugging-cant-assign-requested-address-on-macos-when-vpn-daemon-processes-prevent-socket-8cc68bfe72a6
- author_url
- https://medium.com/@jjpark067
- status
- ok
- fetched_at
- 2026-07-13 16:16:37