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

lsof -iTCP -sTCP:LISTEN -n -P

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:

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

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"

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

"Docker is using a port I need"

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

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

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

"Is anything listening that shouldn't be?"

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