99

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

Currently I'm using something similar to this:

port=$RANDOM
quit=0

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

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

  • 4
    Why do you want to do that? It's inherently racy (and inefficient - and least add -n to netstat and a more selective grep). The way to do it is to try and open a port in whatever mode you need, and try another one if it's not available. – Mat Nov 16 '12 at 16:04
  • 1
    @Mat I'm trying to automatically find an open port to use with ssh -D as a SOCKS server. – mybuddymichael Nov 16 '12 at 16:08
  • Similar question: http://stackoverflow.com/questions/13308144/how-to-get-the-first-available-tcp-port-to-listen-to || POSIX: http://stackoverflow.com/questions/913501/how-to-let-kernel-choose-a-port-number-in-the-range-1024-5000-in-tcp-socket-pr – Ciro Santilli OurBigBook.com Dec 19 '15 at 15:38
  • Not a duplicate, since it was asked after, and in a different SE site, but I answered there with a portable sh(1) script that would nicely answer this one too. To not repeat the answer, I'm linking from here: https://superuser.com/a/1746110/658727 – alx - recommends codidact Oct 06 '22 at 11:36

20 Answers20

91

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]) s.close()

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.

Stephen Kitt
  • 434,908
  • 42
    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 02 '14 at 22:28
  • 6
    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 02: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
  • I ran the script 10,000 times and got similar results to @bukzor: 1282 ports used twice, 153 used 3 times, 10 used 4 times and 1 used 5 times. – dshepherd Feb 17 '16 at 13:08
  • 2
    @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 06:38
  • 2
    One liner for Ruby 2.3.1: ruby -e 'puts Addrinfo.tcp("", 0).bind { |s| s.local_address.ip_port }' – Franklin Yu Aug 23 '16 at 06:39
  • Here's an example of how to get the value from the python script: https://stackoverflow.com/a/34171914/289099 – pattivacek Aug 15 '17 at 09:11
  • 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
  • Thank you for giving me the name, ip_local_port_range. – noname Mar 26 '19 at 10:13
  • you'll want to do s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) in order to be actually able to use the port immediately afterwards – Alexia Luna Dec 15 '22 at 20:33
  • It's 2024. Is there no standard command for this? Like get-open-port? – Matthias Feb 21 '24 at 17:36
46

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.

#!/bin/bash
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
    done
done
Chris Down
  • 125,559
  • 25
  • 270
  • 266
  • 1
    @Lekensteyn: Where do you see a race condition here? – Chris Down Nov 02 '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 02 '14 at 23:44
  • 2
    @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 03 '14 at 00: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 03 '14 at 09: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
  • @monksy Your version of nc is not GNU netcat, then. You're probably using OpenBSD netcat, which has different options. – Chris Down Nov 20 '16 at 02:01
  • 1
    This is not really all that useful if you then want to know what port number was picked and do something with it; you'll inevitably run into a race condition there. – dramzy Feb 21 '19 at 19:07
  • @dramzy You don't have to run into a race condition. Launch the program, get notified (by something like sd_notify) about successful startup, then record the last port entered (or pass it through dbus, or whatever IPC you like). – Chris Down Feb 22 '19 at 20:43
  • @Chris In my case, I have no control over the process that uses the port since I am running a Postgres instance. The process will exit with an error code if the port is in use or it will run indefinitely otherwise. The solution I can think of involves a race condition with a wait and a retry. – dramzy Feb 22 '19 at 20:49
36

One-liner

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.

Example

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

54930
57937
51399

Notes

  • 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 01 '20 at 23:11
  • I also think that the sort in the first process substitution is not needed. – whoan Feb 01 '20 at 23:21
  • 3
    @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 03 '20 at 04:47
  • I don't think seq is POSIX. – Tripp Kinetics Jun 09 '23 at 11:37
17

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() {
    LOW_BOUND=49152
    RANGE=16384
    while true; do
        CANDIDATE=$[$LOW_BOUND + ($RANDOM % $RANGE)]
        (echo "" >/dev/tcp/127.0.0.1/${CANDIDATE}) >/dev/null 2>&1
        if [ $? -ne 0 ]; then
            echo $CANDIDATE
            break
        fi
    done
}

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

root@ubuntu:~> EPHEMERAL_PORT
59453
root@ubuntu:~> PORT=$(EPHEMERAL_PORT)
JigglyNaga
  • 7,886
Sandeep
  • 343
  • 3
  • 7
  • 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 04 '19 at 13:47
  • Otherwise pretty cool though! – lxs Jan 04 '19 at 13:47
  • 3
    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
15
#!/bin/bash
read LOWERPORT UPPERPORT < /proc/sys/net/ipv4/ip_local_port_range
while :
do
        PORT="`shuf -i $LOWERPORT-$UPPERPORT -n 1`"
        ss -lpna | grep -q ":$PORT " || break
done
echo $PORT


"ss -lpn" - will show only ports with established connectios. We should use
"ss -lpna" to consider listening ports too

Credits to Chris Down

Ivan
  • 3
13

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.

w00t
  • 408
7

This is the version I use:

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

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

pfo
  • 73
4

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

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'
3

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 do while [[ $interim -lt $taken && ${#free_ports[@]} -lt $ports ]] do free_ports+=( $interim ) interim=$(( interim + spacing + (RANDOM % spacing) )) done interim=$(( interim > taken + spacing ? interim : taken + spacing + (RANDOM % spacing) )) done

[[ ${#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 one call is issued to external commands, to get a list of ports in use at the beginning. One can request one or more free ports:

:;  random_free_tcp_port
2070
:;  random_free_tcp_port 2
2073
2114

and start at an arbitrary port:

:;  random_free_tcp_port 2 10240
10245
10293
3

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.

garethTheRed
  • 33,957
0
while port=$(shuf -n1 -i $(cat /proc/sys/net/ipv4/ip_local_port_range | tr '\011' '-'))
netstat -atun | grep -q ":$port\s" ; do
    continue
done
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.

0

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

#!/bin/bash

RANDOM=$$

# Based on 
# https://unix.stackexchange.com/a/55918/41065
# https://unix.stackexchange.com/a/248319/41065
find_n_ports () {
    local n=$1
    RANDOM=$$
    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"
        done
        # if none of the ports is in use return them
        if [ "$(ss -tHn "${filter# or}" | wc -l)" = "0" ] ; then
            echo "$ports"
            return 0
        fi
    done
    echo "Could not find $n consecutive ports">&2
    return 1
}

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

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 )
   lp=null

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

YMMV

mills013
  • 684
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:

0

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

echo ${PORT}

mozey
  • 101
0

I liked w00t's answer, but I didn't want to have the same free port chosen upon each invocation. Adding an incremental update, this will select a free port at random between 3,000 to 13,000 (but if all of those are taken, it will incrementally search from that randomly selected port):

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

Yet one more answer. It is very similar to several of the provided ones, but some of these don't actually work for me. Some don't tell me which free port I could use. Some give me a port which is not really free. And others use shuffle which I don't need.

read first last < /proc/sys/net/ipv4/ip_local_port_range
# or define your own range, like:
# first=9000; last=10000

for port in $(seq $first $last); do ss -atun | grep -q ":$port " || break done

echo $port

Or as a long one-liner:

read first last < /proc/sys/net/ipv4/ip_local_port_range; for port in $(seq $first $last); do ss -atun | grep -q ":$port " || break; done; echo $port
mivk
  • 3,596
0

My take, similar to multiple ideas here. Randomly picks form a range and checks with nc

# Tries to find a free port randomly from a given range
random_free_port() {
  local max_attempts=5
  local low_range=8432
  local range=200
  for i in $(seq "${max_attempts}"); do
    # Pick a random port between 8432-8632
    local port="$(($low_range + $RANDOM % $range))"
    # Check if the port is not open
    if ! nc -z -w 0 localhost 8433; then
      echo "Found free port $port" 1>&2
      echo "${port}"
      return
    fi
  done
  echo "Unable to find a free port after 5 attempts" 1>&2
  return 1
}
Keymon
  • 763
0
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int
main (int argc, char** argv)
{
  int sock;
  struct sockaddr_in name;
  char *addr;

  if(argc<2) {
     addr="0.0.0.0";
  } else {
     addr=argv[1];
  }
  /* Create the socket. */
  sock = socket (PF_INET, SOCK_STREAM, 0);
  if (sock < 0)
    {
      perror ("socket");
      exit (EXIT_FAILURE);
    }

  /* Give the socket a name. */
  name.sin_family = AF_INET;
  name.sin_port = htons (0);
  name.sin_addr.s_addr = inet_addr(addr);
  if (bind (sock, (struct sockaddr *) &name, sizeof (name)) < 0)
    {
      perror ("bind");
      exit (EXIT_FAILURE);
    }

  int len = sizeof(name);
  int sn = getsockname(sock, (struct sockaddr *)&name, &len);
  printf("Port is %d\n",ntohs(name.sin_port));
  return sock;
}

Compile: gcc getport.c -o getport

Usage: ./getport [address]

Ouput:
./getport
Port is 31725
./getport 127.0.0.1
Port is 32064
./getport 1.2.3.4
bind: Cannot assign requested address
Zibri
  • 573
  • 5
  • 9