Port Scanning with Bash

Bash’s /dev/tcp/$HOST/$PORT pseudo-device lets you attempt a TCP connection with nothing but the shell — no nc, nmap, or curl required. If the connection succeeds, the port is open; if it fails, it’s closed or filtered.

(: </dev/tcp/$IP_ADDRESS/$PORT) &>/dev/null && echo "OPEN" || echo "CLOSED"

How it works

  • /dev/tcp/$IP/$PORT is a Bash feature (not a real device file) that opens a TCP socket when redirected to.
  • (: </dev/tcp/$IP/$PORT) tries to open the socket for reading inside a subshell. The &>/dev/null suppresses both stdout and stderr.
  • If the open succeeds, the subshell exits 0 and the && branch runs; if it fails, the || branch runs.

A minimal scanner loop

for port in 21 22 23 53 80 88 135 139 389 443 445 3389; do
  (timeout 1 bash -c "echo >/dev/tcp/$TARGET/$port") 2>/dev/null && \
    echo "[+] $TARGET:$port OPEN"
done

timeout 1 prevents filtered ports from hanging the loop on the kernel’s TCP connect timeout.

Trade-offs vs. a real scanner

Bash /dev/tcpnmap
PrerequisitesNone (Bash is everywhere)Binary upload or install
SpeedSerial, one connect at a timeParallel, engineered for scale
StealthLooks like shell activity — until it touches 500 hostsInstantly recognizable
UDP supportNone (/dev/udp exists but is unreliable)Full UDP scan engine

Detection

A Bash process fanning out TCP connects is a high-signal anomaly. Defenders can alert on:

  • Rapid sequential connections from a single PID to many destination ports.
  • /dev/tcp appearing in shell history or audit logs.
  • Absence of a corresponding listening service on the scanning host.
  • powershell-port-scanning — the Windows PowerShell equivalent
  • udp — the protocol Bash’s /dev/tcp does not scan reliably
  • netcat — the classic next step when you have a binary to upload
  • unix-file-descriptors/dev/tcp is Bash’s file-descriptor abstraction over TCP

Sources