Skip to content

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

bash
lsof -iTCP -sTCP:LISTEN -n -P

Understanding the Output

text
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

bash
# 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

bash
# 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)

bash
# 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

bash
# 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

bash
# 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)

bash
# 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

text
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)

bash
# 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

bash
# 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

bash
# 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

bash
# 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

bash
# 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

bash
# 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:

bash
#!/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:

bash
chmod +x ~/bin/port-cleanup.sh

Security Best Practices

1. Audit Regularly

Add to your morning check script:

bash
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

bash
# 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

bash
# 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

bash
# 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"

bash
lsof -iTCP:3000 -sTCP:LISTEN -n -P
# Find the PID
kill <PID>

"Docker is using a port I need"

bash
docker ps
docker stop <container_name>
# or stop all
docker stop $(docker ps -q)

"Something keeps restarting on port 8080"

bash
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"

bash
lsof -iTCP -sTCP:LISTEN -n -P | grep -E "5432|3306|27017|6379"

"Is anything listening that shouldn't be?"

bash
# Show everything NOT on localhost
lsof -iTCP -sTCP:LISTEN -n -P | grep -v "127.0.0.1"

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.

  • File Permissions — Related system-level reference for access control
  • Docker — Container port mapping that interacts with host ports