freeport: Kill Whatever's Holding Your Port

When a dev server won’t start because the port is already in use, you usually have to look up the PID and kill it by hand. This zsh function does that in one command, for a single port or a range:

freeport() {
  if [[ $# -ne 1 ]]; then
    print -u2 "usage: freeport <port> | <start-end>"
    print -u2 "  freeport 3000"
    print -u2 "  freeport 3000-3004"
    return 1
  fi

  local -a ports
  local start end
  if [[ "$1" == <->-<-> ]]; then
    start=${1%%-*}
    end=${1##*-}
    if (( start > end )); then
      print -u2 "freeport: invalid range '$1' (start > end)"
      return 1
    fi
    ports=({$start..$end})
  elif [[ "$1" == <-> ]]; then
    ports=($1)
  else
    print -u2 "freeport: invalid port '$1'"
    return 1
  fi

  local port
  local -a pids
  local -a killed
  for port in "${ports[@]}"; do
    pids=("${(@f)$(lsof -ti ":$port")}")
    pids=("${pids[@]:#}")
    if (( ${#pids[@]} == 0 )); then
      print "No process found on port $port"
      continue
    fi
    kill "${pids[@]}" && killed+=($port)
  done

  (( ${#killed[@]} > 0 )) && print "Freed ports: ${killed[*]}"
}

Usage: freeport 3000 or freeport 3000-3004.

zsh’s <-> pattern (a numeric glob) tells a single port from a start-end range, and the range is expanded with {$start..$end}, so freeport 3000-3004 walks every port in between.

lsof -ti :3000 finds the PIDs holding a port; the rest just splits the output into an array and kills them. It sends SIGTERM rather than kill -9, so each process gets a chance to shut down cleanly, and a port with nothing running is reported without stopping the rest. Anything it actually freed is listed at the end.

It needs lsof, which ships with macOS and most Linux distributions.