What's the easiest way to find an unused local port?

Currently I'm using something similar to this:


while [ "$quit" -ne 1 ]; do
  netstat -a | grep $port >> /dev/null
  if [ $? -gt 0 ]; then
    port=`expr $port + 1`

It feels awfully roundabout, so I'm wondering if there's a more simple path such as a builtin that I've missed.


16 Answers 16


If your application supports it, you can try passing port 0 to the application. If your application passes this to the kernel, the port will be dynamically allocated at request time, and is guaranteed not to be in use (allocation will fail if all ports are already in use).

Otherwise, you can do this manually. The script in your answer has a race condition, the only way to avoid it is to atomically check if it is open by trying to open it. If the port is in use, the program should quit with a failure to open the port.

For example, say you're trying to listen with GNU netcat.

read lower_port upper_port < /proc/sys/net/ipv4/ip_local_port_range
while :; do
    for (( port = lower_port ; port <= upper_port ; port++ )); do
        nc -l -p "$port" 2>/dev/null && break 2
  • 1
    @Lekensteyn: Where do you see a race condition here? – Chris Down Nov 2 '14 at 23:23
  • 2
    This port tries to use the first port available. When you have two concurrent processes, then the port which just got checked might be reused. Re-reading your answer, it seems that you suggest to retry binding on an available port until all ports are exhausted. Assuming that the program in question can distinguish between "port in use" and other errors, it should be fine (though randomization would still make it better for unpredictability). – Lekensteyn Nov 2 '14 at 23:44
  • 1
    @Lekensteyn Successful port binding results in the kernel returning EADDRINUSE if you try and use it again, it's not possible that "the port which just got checked might be reused". – Chris Down Nov 3 '14 at 0:56
  • Yes, I wrongly assumed that you would exit the loop and use $port in the actual program as in while ...; done; program --port $port. – Lekensteyn Nov 3 '14 at 9:11
  • From the man page: -p source_port Specifies the source port nc should use, subject to privilege restrictions and availability. It is an error to use this option in conjunction with the -l option. – monksy Nov 17 '16 at 16:34

My solution is to bind to port 0, which asks the kernel to allocate a port from it's ip_local_port_range. Then, close the socket and use that port number in your configuration.

This works because the kernel doesn't seem to reuse port numbers until it absolutely has to. Subsequent binds to port 0 will allocate a different port number. Python code:

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('', 0))
addr = s.getsockname()
print addr[1]

This gives just a number of a port, eg. 60123.

Run this program 10 000 times (you should run these concurrently), and you'll get 10 000 different port numbers. Therefore, I think it's pretty safe to use the ports.

  • 31
    Here is a one-liner (valid with Python 2 and Python 3): python -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()' – Lekensteyn Nov 2 '14 at 22:28
  • 4
    I ran the mentioned experiment, and not all results were unique. My histogram was: { 1: 7006, 2: 1249, 3: 151, 4: 8, 5: 1, 6: 1} – bukzor Jan 31 '15 at 2:00
  • 2
    Is there an easy way to add in a check that the port is not blocked by a firewall, or rather only searches open ports? – Mark Lakata Mar 12 '15 at 20:25
  • 1
    @dshepherd I believe you'll get different ports if you do not close the previous one (and close them all at once at last). – Franklin Yu Aug 23 '16 at 6:38
  • 2
    @FranklinYu - that didn't work for me (ruby 2.1.6p336). This did: ruby -e 'require "socket"; puts Addrinfo.tcp("", 0).bind {|s| s.local_address.ip_port }' – G. Sylvie Davies Aug 16 '17 at 19:57


I've put together a nice one-liner that quickly serves the purpose, allowing to grab an arbitrary number of ports in an arbitrary range (here it's divided in 4 lines for readability):

comm -23 \
<(seq "$FROM" "$TO" | sort) \
<(ss -Htan | awk '{print $4}' | cut -d':' -f2 | sort -u) \
| shuf | head -n "$HOWMANY"

Line by line

comm is a utility that compares lines in two files that must appear sorted alphabetically. It outputs three columns: lines that appear only in the first file, lines that only appear in the second one and common lines. By specifying -23 we suppress the latter columns and only keep the first one. We can use this to obtain the difference of two sets, expressed as a sequence of text lines. I learned about comm here.

The first file is the range of ports that we can select from. seq produces a sorted sequence of numbers from $FROM to $TO. The result is sorted alphabetically (instead of numerically, in order to comply with comms requirement) and piped to comm as the first file using process substitution.

The second file is the sorted list of ports, that we obtain by calling the ss command (with -t meaning TCP ports, -a meaning all - established and listening - and -n numeric - don't try to resolve, say, 22 to ssh). We then pick only the fourth column with awk, which contains the local address and port. We use cut to split address and port with the : delimiter and keep only the latter (-f2). We then comply with comm's requirement by sorting without duplicates -u.

Now we have a sorted list of open ports, that we can shuffle to then grab the first "$HOWMANY" ones with head -n.


Grab the three random open ports in the private range (49152-65535)

comm -23 <(seq 49152 65535 | sort) <(ss -Htan | awk '{print $4}' | cut -d':' -f2 | sort -u) | shuf | head -n 3

could return for example



  • switch -t with -u in ss to get free UDP ports instead.
  • replace shuf with sort -n if you prefer to get available ports numerically sorted instead of at random
  • 1
    I like this one +1. You can also use -H to suppress ss headers so you can avoid grepping to get only numbers. – whoan Feb 1 '20 at 23:11
  • I also think that the sort in the first process substitution is not needed. – whoan Feb 1 '20 at 23:21
  • 1
    @whoan Thanks for the tip regarding the header, I made it part of the answer. sorting in the first process substitution makes sure that the output of seq is sorted alphabetically instead of numerically (which is seqs default) to comply with comms requirement for the input to be sorted like that. I've modified the answer to be more explicit about it. Thanks again! – stefanobaghino Feb 3 '20 at 4:47
read LOWERPORT UPPERPORT < /proc/sys/net/ipv4/ip_local_port_range
while :
        PORT="`shuf -i $LOWERPORT-$UPPERPORT -n 1`"
        ss -lpn | grep -q ":$PORT " || break
echo $PORT

Credits to Chris Down


Apparently TCP connections can be used as file descriptors on linux from with in bash/zsh. The following function uses that technique and should be faster than invoking netcat/telnet.

function EPHEMERAL_PORT() {
    while true; do
        (echo "" >/dev/tcp/${CANDIDATE}) >/dev/null 2>&1
        if [ $? -ne 0 ]; then
            echo $CANDIDATE

Usage instructions: Bind the output to a variable and use in scripts. Tested on Ubuntu 16.04

root@ubuntu:~> EPHEMERAL_PORT
root@ubuntu:~> PORT=$(EPHEMERAL_PORT)
  • Works withksh93 also. – fpmurphy Apr 19 '18 at 11:50
  • If you change UPORT to 32768, you can still get EG 35835. RANDOM returns a number in [0,32767]. Modding this by a number greater than the max has no effect. You want something like $[$LPORT + ($RANDOM % ($UPORT-$LPORT))]. – lxs Jan 4 '19 at 13:47
  • Otherwise pretty cool though! – lxs Jan 4 '19 at 13:47
  • 1
    This sends \n to any listening port though :) I'd suggest adding -n. This will still try to open a connection but not send anything but immediately disconnect. – stefanct Feb 14 '19 at 16:08

Here's a cross-platform, efficient "oneliner" that slurps up all in-use ports and gives you the first available one from 3000 onwards:

netstat -aln | awk '
  $6 == "LISTEN" {
    if ($4 ~ "[.:][0-9]+$") {
      split($4, a, /[:.]/);
      port = a[length(a)];
      p[port] = 1
  END {
    for (i = 3000; i < 65000 && p[i]; i++){};
    if (i == 65000) {exit 1};
    print i

You can simply join all lines to have it on one line. If you want to get the first available from a different port number, change the assignment to i in the for loop.

It works on both Mac and Linux, which is why the [:.] regex is needed.

  • Why -a and not -t to just look at TCP(6) sockets? – stefanct Feb 14 '19 at 16:10
  • And while we are at it, parsing the output of ss -Htnl might be better (and faster! - not that I care about that :P). – stefanct Feb 14 '19 at 16:21
  • 1
    @stefanct BSD netstat doesn't have -t, at least the one that Apple ships with, and ss is also not present on macOS. netstat -aln even works on Solaris. – w00t Feb 25 '19 at 20:54

On Linux, you could do something like:

ss -tln | 
  awk 'NR > 1{gsub(/.*:/,"",$4); print $4}' |
  sort -un |
  awk -v n=1080 '$0 < n {next}; $0 == n {n++; next}; {exit}; END {print n}'

To find the first free port above 1080. Note that ssh -D would bind on the loopback interface, so in theory you could reuse port 1080 if a socket has it bound on another address. Another way would be to actually try and bind it:

perl -MSocket -le 'socket S, PF_INET, SOCK_STREAM,getprotobyname("tcp");
  $port = 1080;
  ++$port until bind S, sockaddr_in($port,inet_aton("127.1"));
  print $port'
  • This does, however, involve a race condition between attempting to open the port and actually using it. – Chris Down Nov 17 '12 at 0:02
  • @ChrisDown, Indeed, but with ssh -D, I can't see any better option. The -O forward option of ssh doesn't return an error when the forward fail. – Stéphane Chazelas Nov 17 '12 at 9:00

This is the version I use:

  port=$(shuf -n 1 -i 49152-65535)
  netstat -atun | grep -q "$port"

echo "$port"

The command shuf -n 1 -i 49152-65535 gives you a "random" port in the dynamic range. If it is used already, another port in that range is tried.

The command netstat -atun lists all (-a) TCP (-t) and UDP (-u) ports without wasting time to determine hostnames (-n).


If you have python lying around, I'd do this:

port="$(python -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1])')";
echo "Unused Port: $port"

This is part of a function I have in my .bashrc, which dynamically creates SSH tunnels and tries to use any port in a range:

   lps=( 7002 7003 7004 7005 7006 7007 7008 7009 7010 7011 )

   # find a free listening port
   for port in ${lps[@]}; do
      lsof -i -n -P |grep LISTEN |grep -q ":${port}"
      [ $? -eq 1 ] && { lp=$port; break; }
   [ "$lp" = "null" ] && { echo "no free local ports available"; return 2; }
   return $port



Yet another run at this old hobby horse:

function random_free_tcp_port {
  local ports="${1:-1}" interim="${2:-2048}" spacing=32
  local free_ports=( )
  local taken_ports=( $( netstat -aln | egrep ^tcp | fgrep LISTEN |
                         awk '{print $4}' | egrep -o '[0-9]+$' |
                         sort -n | uniq ) )
  interim=$(( interim + (RANDOM % spacing) ))

  for taken in "${taken_ports[@]}" 65535
    while [[ $interim -lt $taken && ${#free_ports[@]} -lt $ports ]]
      free_ports+=( $interim )
      interim=$(( interim + spacing + (RANDOM % spacing) ))
    interim=$(( interim > taken + spacing
                ? interim
                : taken + spacing + (RANDOM % spacing) ))

  [[ ${#free_ports[@]} -ge $ports ]] || return 2

  printf '%d\n' "${free_ports[@]}"

This code makes purely portable use of netstat, egrep, awk, &al. Note that only call is issued to external commands, to get a list of taken ports at the beginning. One can request one or more free ports:

:;  random_free_tcp_port
:;  random_free_tcp_port 2

and start at an arbitrary port:

:;  random_free_tcp_port 2 10240
while port=$(shuf -n1 -i $(cat /proc/sys/net/ipv4/ip_local_port_range | tr '\011' '-'))
netstat -atun | grep -q ":$port\s" ; do
echo $port

My combination from other answers above. Get it:

With shuf -n1 we take one random number from the range (-i) in /proc/sys/net/ipv4/ip_local_port_range. shuf need the syntax with dash, so we use tr to change the tab in a dash.

Next wie use netstat to show us all (-a) tcp and udp (-u -t) connections in numbers(-n), if we find our random port $port in this (start with a : and end with w whitespace (\s) then we need a other Port and so continue. Else (grep -q has a returncode > 0 we left the while loop and $port is set.


My take on it... the function tries to find n consecutive free ports:



# Based on 
# https://unix.stackexchange.com/a/55918/41065
# https://unix.stackexchange.com/a/248319/41065
find_n_ports () {
    local n=$1
    read lower_port upper_port < /proc/sys/net/ipv4/ip_local_port_range
    local tries=10
    while [ $((tries--)) -gt 0 ]; do
        # Sleep for 100 to 499 ms
        sleep "0.$((100+$RANDOM%399))"
        local start="`shuf -i $lower_port-$(($upper_port-$n-1)) -n 1`"
        local end=$(($start+$n-1))
        # create ss filter for $n consecutive ports starting with $start
        local filter=""
        local ports=$(seq $start $end)
        for p in $ports ; do
            filter="$filter or sport = $p"
        # if none of the ports is in use return them
        if [ "$(ss -tHn "${filter# or}" | wc -l)" = "0" ] ; then
            echo "$ports"
            return 0
    echo "Could not find $n consecutive ports">&2
    return 1

ports=$(find_n_ports 3)
[ $? -ne 0 ] && exit 1
exit 0

Yet another answer for macOS, based on this and this answer:

lower_port=`sysctl -n net.inet.ip.portrange.first`
upper_port=`sysctl -n net.inet.ip.portrange.last`

comm -23 \
<(seq "$lower_port" "$upper_port" | sort) \
<(sudo lsof -iTCP -n -P -sTCP:LISTEN -t | sort -u) \
| gshuf | head -n 1

You can change the 1 in head -n 1 to another number (or a script argument variable) if you want multiple ports.

shuf is available as gshuf via the coreutils Homebrew package.

The port range code is from this answer on this Stack Overflow question:


This script works for me on macOS

#!/usr/bin/env bash
set -u                    # exit on undefined variable
bash -c 'set -o pipefail' # return code of first cmd to fail in a pipeline

# Print random open port. Inspired by
# https://superuser.com/a/1041677/537059

CHECK="do while"

while [[ ! -z $CHECK ]]; do
  PORT=$(((RANDOM % 60000) + 1025))
  # Match end of word boundary to avoid false positives,
  # e.g. 2000 matching 20000
  # https://stackoverflow.com/a/34074458/639133
  CHECK=$(netstat -an -ptcp | grep LISTEN | grep -r "${PORT}\b")

echo ${PORT}

Another one, albeit late to the party. This one-liner even works with busybox:

for p in $(seq 3000 4000); do ss -tlnH | tr -s ' ' | cut -d" " -sf4 | grep -q "${p}$" || echo "${p}"; done | head -n 1

Change seq 3000 4000 for shuf -i 3000-4000 to get a random port.

Increase the number after head -n to return more than one free port.

This does require ss to be installed. If it's not available, it's trivial to change ss -tlnH to netstat -tln | tail -n +3 to use busybox's netstat instead.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.