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.


20 Answers 20


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

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.

  • 43
    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, 2014 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, 2015 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? Mar 12, 2015 at 20:25
  • 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). Aug 23, 2016 at 6:38
  • 2
    One liner for Ruby 2.3.1: ruby -e 'puts Addrinfo.tcp("", 0).bind { |s| s.local_address.ip_port }' Aug 23, 2016 at 6:39

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, 2014 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, 2014 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 3, 2014 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, 2014 at 9:11
  • 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, 2019 at 19:07


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, 2020 at 23:11
  • I also think that the sort in the first process substitution is not needed.
    – whoan
    Feb 1, 2020 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! Feb 3, 2020 at 4:47
  • I don't think seq is POSIX. Jun 9, 2023 at 11:37

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, 2018 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, 2019 at 13:47
  • Otherwise pretty cool though!
    – lxs
    Jan 4, 2019 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, 2019 at 16:08
read LOWERPORT UPPERPORT < /proc/sys/net/ipv4/ip_local_port_range
while :
        PORT="`shuf -i $LOWERPORT-$UPPERPORT -n 1`"
        ss -lpna | grep -q ":$PORT " || break
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


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, 2019 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, 2019 at 16:21
  • 3
    @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, 2019 at 20:54
  • 1
    This should be higher up. Thank you! Dec 9, 2022 at 18:20

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


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, 2012 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. Nov 17, 2012 at 9:00

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"

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.


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 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
:;  random_free_tcp_port 2

and start at an arbitrary port:

:;  random_free_tcp_port 2 10240

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


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}

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 {
    start = int(3000 + rand()*10000);
    for (i = start; i < 65000 && p[i]; i++){};
    if (i == 65000) {exit 1};
    print i

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

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

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}"
  echo "Unable to find a free port after 5 attempts" 1>&2
  return 1
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

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

  if(argc<2) {
  } else {
  /* 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]

Port is 31725
Port is 32064
bind: Cannot assign requested address

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .