Managing Listening Ports on macOS¶
Published: February 14, 2026 ยท Last edited: February 24, 2026
A step-by-step approach to identifying, investigating, and removing unwanted services listening on your ports.
Step 1: See What's Listening¶
Understanding the Output¶
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
node 1234 user 23u IPv4 0x1a2b3c4d 0t0 TCP *:3000 (LISTEN)
postgres 5678 user 7u IPv4 0x5e6f7g8h 0t0 TCP 127.0.0.1:5432 (LISTEN)
Docker 9012 user 19u IPv6 0x9i0j1k2l 0t0 TCP *:8080 (LISTEN)
Key columns:
- COMMAND: Program name (what's running)
- PID: Process ID (unique number for this instance)
- USER: Who started it (you, root, system service)
- NAME: The actual port (e.g., *:3000 or 127.0.0.1:5432)
Port binding types:
- *:3000 or :::3000 - Listening on ALL interfaces (accessible from network)
- 127.0.0.1:3000 - Only listening locally (only you can access)
- 0.0.0.0:3000 - Listening on all IPv4 interfaces
Step 2: Make it Readable¶
Better Formatted View¶
# Create this helper function in ~/.bashrc or ~/.zshrc
listening() {
echo "๐ Listening Ports on Your System"
echo ""
lsof -iTCP -sTCP:LISTEN -n -P | awk 'NR==1 || NR>1 {printf "%-15s %-8s %-10s %-20s\n", $1, $2, $3, $9}' | column -t
}
Even Better: Filter by Port or Command¶
# Find what's on a specific port
port-check() {
if [ -z "$1" ]; then
echo "Usage: port-check <port_number>"
return 1
fi
lsof -iTCP:$1 -sTCP:LISTEN -n -P
}
# Find all ports used by a specific program
app-ports() {
if [ -z "$1" ]; then
echo "Usage: app-ports <program_name>"
return 1
fi
lsof -iTCP -sTCP:LISTEN -n -P | grep -i "$1"
}
# Show only non-local listeners (potential security issue)
exposed-ports() {
echo "โ ๏ธ Ports exposed to network (not 127.0.0.1):"
lsof -iTCP -sTCP:LISTEN -n -P | grep -v "127.0.0.1"
}
Step 3: Identify if a Port is Wanted or Unwanted¶
Common Expected Ports (Usually Safe)¶
# Development servers
3000, 3001, 8000, 8080, 8888 โ Node/React/Python dev servers
4200 โ Angular dev server
5173, 5174 โ Vite dev server
# Databases (local only is safe)
5432 โ PostgreSQL
3306 โ MySQL
27017 โ MongoDB
6379 โ Redis
# Docker/VM
2375, 2376 โ Docker
Red Flags ๐ฉ¶
Dangerous if exposed to network (not 127.0.0.1): - Databases (PostgreSQL, MySQL, MongoDB, Redis) - Development servers you forgot about - Unknown services running as root - Ports in common exploit ranges (e.g., 22 SSH, 445 SMB)
Questions to ask: 1. Do I recognize this program? 2. Am I actively using this service? 3. Should this be accessible from the network? 4. Is it running as root? (More dangerous)
Step 4: Investigate Unknown Processes¶
Get More Info About a Process¶
# See full command and arguments
ps -p <PID> -o command
# Example:
ps -p 1234 -o command
# Output: node /Users/you/projects/old-app/server.js
# See when it started
ps -p <PID> -o lstart
# See everything about it
ps -p <PID> -f
Find the Executable Path¶
# Where is the binary located?
lsof -p <PID> | grep "txt"
# Example:
lsof -p 1234 | grep "txt"
# Shows: /usr/local/bin/node
Check if it's a LaunchAgent/Daemon (Auto-starting)¶
# Check user launch agents
ls ~/Library/LaunchAgents/
# Check system launch daemons (requires sudo)
ls /Library/LaunchDaemons/
ls /Library/LaunchAgents/
# Find which plist file might be starting this
launchctl list | grep -i <partial_name>
Step 5: Decision Tree - What to Do¶
Is the port wanted?
โโ YES โ Do nothing, or secure it if needed
โโ NO โ Continue...
โ
โโ Is it a one-time process? (dev server, script)
โ โโ Just kill it: kill <PID>
โ
โโ Is it auto-starting? (daemon, LaunchAgent)
โโ Disable it permanently (see below)
Step 6: Stopping Unwanted Processes¶
Option A: Kill the Process (Temporary)¶
# Gentle stop (lets process cleanup)
kill <PID>
# Force stop if gentle doesn't work
kill -9 <PID>
# Kill by name (kills ALL matching processes)
pkill -f "process_name"
# Example: Kill all node processes
pkill -f node
Option B: Disable Auto-Starting Services¶
For LaunchAgents/Daemons¶
# List all running services
launchctl list
# Find the service (example output: com.docker.helper)
launchctl list | grep -i docker
# Unload (stop and disable)
launchctl unload ~/Library/LaunchAgents/com.example.service.plist
# Or for system services (needs sudo)
sudo launchctl unload /Library/LaunchDaemons/com.example.service.plist
# Remove the plist file to prevent restart
rm ~/Library/LaunchAgents/com.example.service.plist
# or
sudo rm /Library/LaunchDaemons/com.example.service.plist
For Homebrew Services¶
# List all brew services
brew services list
# Stop a service
brew services stop <service_name>
# Example:
brew services stop postgresql
brew services stop redis
For Docker Containers¶
# List running containers
docker ps
# Stop a container
docker stop <container_id_or_name>
# Stop all containers
docker stop $(docker ps -q)
# Prevent auto-restart
docker update --restart=no <container_id_or_name>
# Remove container completely
docker rm <container_id_or_name>
Option C: Application-Specific¶
# Node/npm servers
# Find the process in your terminal and Ctrl+C
# Or:
pkill -f "node.*server"
# Python servers
pkill -f "python.*server"
# Rails/Ruby
pkill -f "rails server"
Step 7: Verification¶
# Check if the port is now free
port-check <port_number>
# Or see all listening ports again
lsof -iTCP -sTCP:LISTEN -n -P
# If port still shows up, the process restarted
# Check launchctl: launchctl list | grep -i <name>
Complete Workflow Script¶
Create ~/bin/port-cleanup.sh:
#!/bin/bash
# Interactive port cleanup helper
echo "๐ Port Cleanup Helper"
echo ""
# Show all listening ports
echo "๐ Current listening ports:"
lsof -iTCP -sTCP:LISTEN -n -P | awk 'NR==1 || NR>1 {printf "%-15s %-8s %-10s %-30s\n", $1, $2, $3, $9}'
echo ""
# Ask which port to investigate
read -p "Enter port number to investigate (or 'q' to quit): " port
if [ "$port" = "q" ]; then
exit 0
fi
# Show what's on that port
echo ""
echo "๐ Checking port $port..."
info=$(lsof -iTCP:$port -sTCP:LISTEN -n -P)
if [ -z "$info" ]; then
echo "โ Nothing listening on port $port"
exit 0
fi
echo "$info"
echo ""
# Extract PID
pid=$(echo "$info" | awk 'NR==2 {print $2}')
if [ -z "$pid" ]; then
echo "โ Could not determine PID"
exit 1
fi
# Show process details
echo "๐ Process details:"
ps -p $pid -o pid,ppid,user,lstart,command
echo ""
# Show full command
echo "๐ป Full command:"
ps -p $pid -o command=
echo ""
# Check if it's a launchd service
launch_service=$(launchctl list | grep -i "$(ps -p $pid -o comm= | xargs basename)" | awk '{print $3}')
if [ -n "$launch_service" ]; then
echo "โ๏ธ This appears to be a LaunchAgent/Daemon: $launch_service"
echo ""
fi
# Ask what to do
echo "What would you like to do?"
echo "1) Kill this process (temporary)"
echo "2) Kill and disable auto-start (if LaunchAgent)"
echo "3) Just show info (don't kill)"
echo "4) Cancel"
echo ""
read -p "Choose (1-4): " choice
case $choice in
1)
kill $pid
echo "โ
Sent kill signal to PID $pid"
sleep 1
if ps -p $pid > /dev/null 2>&1; then
echo "โ ๏ธ Process still running, trying force kill..."
kill -9 $pid
echo "โ
Force killed PID $pid"
fi
;;
2)
kill $pid
echo "โ
Killed PID $pid"
if [ -n "$launch_service" ]; then
echo "๐ Looking for plist file..."
plist_user="$HOME/Library/LaunchAgents/${launch_service}.plist"
plist_system="/Library/LaunchAgents/${launch_service}.plist"
plist_daemon="/Library/LaunchDaemons/${launch_service}.plist"
if [ -f "$plist_user" ]; then
launchctl unload "$plist_user"
rm "$plist_user"
echo "โ
Removed $plist_user"
elif [ -f "$plist_system" ]; then
sudo launchctl unload "$plist_system"
sudo rm "$plist_system"
echo "โ
Removed $plist_system"
elif [ -f "$plist_daemon" ]; then
sudo launchctl unload "$plist_daemon"
sudo rm "$plist_daemon"
echo "โ
Removed $plist_daemon"
else
echo "โ ๏ธ Could not find plist file"
fi
fi
;;
3)
echo "โน๏ธ No action taken"
;;
*)
echo "โ Cancelled"
;;
esac
echo ""
echo "๐ Checking if port is now free..."
still_there=$(lsof -iTCP:$port -sTCP:LISTEN -n -P)
if [ -z "$still_there" ]; then
echo "โ
Port $port is now free!"
else
echo "โ ๏ธ Port $port is still in use:"
echo "$still_there"
fi
Make executable:
Security Best Practices¶
1. Audit Regularly¶
Add to your morning check script:
echo "๐ Security Check - Exposed Ports:"
exposed=$(lsof -iTCP -sTCP:LISTEN -n -P | grep -v "127.0.0.1" | grep -v "COMMAND")
if [ -n "$exposed" ]; then
echo "$exposed"
echo "โ ๏ธ These ports are accessible from network!"
else
echo "โ
No exposed ports"
fi
2. Firewall Configuration¶
# Check if firewall is on
sudo /usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate
# Turn on firewall
sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate on
# Block all incoming connections
sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setblockall on
# Or allow specific apps
sudo /usr/libexec/ApplicationFirewall/socketfilterfw --add /path/to/app
3. Development Safety¶
# Always bind dev servers to localhost
# Node/Express
app.listen(3000, '127.0.0.1') # โ
Good
app.listen(3000, '0.0.0.0') # โ Exposed to network
# Python
python -m http.server 8000 --bind 127.0.0.1 # โ
Good
python -m http.server 8000 # โ Exposed to network
# Rails
rails server -b 127.0.0.1 # โ
Good
rails server # โ Exposed to network
Quick Reference¶
# See all listening ports
lsof -iTCP -sTCP:LISTEN -n -P
# Check specific port
lsof -iTCP:<port> -sTCP:LISTEN -n -P
# Kill by PID
kill <PID>
# Force kill
kill -9 <PID>
# See process details
ps -p <PID> -f
# List services
launchctl list
# Stop service
brew services stop <name>
# or
launchctl unload ~/Library/LaunchAgents/<name>.plist
# Check if port is free
lsof -iTCP:<port> -sTCP:LISTEN
# Interactive cleanup
~/bin/port-cleanup.sh
Common Scenarios¶
"I forgot to stop my dev server and now port 3000 is taken"¶
"Docker is using a port I need"¶
"Something keeps restarting on port 8080"¶
launchctl list | grep 8080
# or
ps aux | grep 8080
# Check the parent process (PPID)
# Likely a LaunchAgent - unload it
"I want to see only database ports"¶
"Is anything listening that shouldn't be?"¶
Reminders¶
โ
Before killing: Identify what it is first
โ
Save your work: If it's your dev server, you might lose unsaved changes
โ
Check for auto-restart: Some services restart automatically
โ
Document unknowns: If you don't recognize it, Google the process name first
โ
One at a time: Don't kill multiple things at once - you won't know what broke
Remember: Understand first, act second.