Python port range checks with reliable timeouts
Building a Python script that tests TCP connectivity to a range of ports with timeout sounds small, yet it is useful for administration, release checks, and incident triage. The reliable version gives every attempt a finite deadline, records the outcome, and produces output that is easy to compare between runs.
TCP connectivity is a narrow question. A completed handshake proves that a transport connection was accepted; it does not prove that HTTP is healthy, that a database accepts credentials, or that an application responds quickly. The tool should report transport observations plainly and only be run against hosts you own or have permission to test.
This kind of utility fits a personal open-source portfolio alongside DenyHosts, Kodos, Scratchy, and other Linux tools. Readable source, sensible licensing, downloadable releases, and background about the design are more valuable than a decorative dashboard. Timing assumptions should be visible too: a timeout is evidence of a network condition, not automatic proof that a service is closed.
Define the target and safety limits
Define the target before writing code. Treat the range as inclusive: 8000 to 8010 includes 8010. Validate that ports fall between 1 and 65535 and reject reversed bounds before opening a socket, keeping the hostname separate from the numeric range.
Start with one host and a modest range. A connection per port clearly answers whether a TCP handshake succeeds, though it generates more traffic than an application-level health check. State that the tool tests TCP only; UDP requires different logic.
Build a standard-library checker
The standard library is enough. The worker below opens one connection with a deadline, distinguishes timeout from refusal, and returns a record for sorted output.
#!/usr/bin/env python3
import argparse
import socket
from concurrent.futures import ThreadPoolExecutor, as_completed
def probe(host, port, timeout):
try:
with socket.create_connection((host, port), timeout=timeout):
return port, "open", "TCP handshake completed"
except socket.timeout:
return port, "timeout", "deadline reached"
except ConnectionRefusedError:
return port, "closed", "connection refused"
except OSError as exc:
return port, "error", str(exc)
def main():
parser = argparse.ArgumentParser(
description="Test TCP connectivity over a port range."
)
parser.add_argument("host")
parser.add_argument("start", type=int)
parser.add_argument("end", type=int)
parser.add_argument("--timeout", type=float, default=1.5)
parser.add_argument("--workers", type=int, default=32)
args = parser.parse_args()
if not 1 <= args.start <= args.end <= 65535:
parser.error("ports must satisfy 1 <= start <= end <= 65535")
if args.timeout <= 0 or args.workers < 1:
parser.error("timeout and workers must be positive")
ports = range(args.start, args.end + 1)
results = []
with ThreadPoolExecutor(max_workers=args.workers) as pool:
futures = [
pool.submit(probe, args.host, port, args.timeout)
for port in ports
]
for future in as_completed(futures):
results.append(future.result())
for port, state, detail in sorted(results):
print(f"{port:5d} {state:<7} {detail}")
if __name__ == "__main__":
main()
Run it with python3 port_probe.py 192.0.2.10 8000 8010 --timeout 1.5 --workers 16; the address is reserved for documentation, so replace it with a host you control. Sorting by port keeps runs comparable even when workers finish out of order.
Tune timeout and concurrency
timeout applies to each attempt, not the whole range. One hundred ports at 1.5 seconds can take much longer than 1.5 seconds. Short deadlines can create false negatives on a congested route, while long ones slow the identification of filtered ports. Begin with one or two seconds, then tune it.
Bound concurrency as well. Sixteen workers may suit a home NBN connection; a hosted service may need fewer. Rapid reconnects can trigger throttling, and an unbounded pool can exhaust file descriptors. Keep the worker count configurable and test a small range first. Singermukesh Python examples can help compare broader implementation choices.
Read results without overclaiming
Interpret states carefully. open means the handshake completed; closed usually means refusal; timeout may indicate a firewall, missing route, or filtered port. DNS failures and local socket limits can produce error. None proves that the application is healthy.
Print one line per port, and add CSV or JSON output when another program needs results. Include the host, port, state, timeout, and UTC timestamp. Saving a baseline makes later comparisons easier than relying on terminal scrollback.
Test across Australian networks
Australian paths vary considerably. A service in Sydney may respond differently to clients in Melbourne, Brisbane, Perth, or Adelaide than from the same metropolitan fibre network. Run the same range from each location with an identical timeout, then compare states.
Local hosting choices matter. An instance in the AWS Sydney region, Azure Australia East, or a provider serving the Australian market may behave differently from one overseas. These are useful reference points for Australian businesses seeking regional latency and local support.
Test fixed-line and mobile paths separately. Fibre and NBN services often differ from 5G, while evening congestion can turn a timeout into an apparent outage. Record AEST or AEDT and the connection type, then repeat the check. For purchasing decisions, compare AUD costs and GST, but prioritise reliability.
Fit the checker into an open-source project
Once stable, publish the utility with a licence, usage guide, example output, and release link. Version the command-line interface so changes to timeout defaults or result labels remain visible. Tests should cover an open port, a refusal, a timeout, and invalid boundaries. Basic download statistics can indicate continued use without replacing reports from actual operators.
The same performance-minded approach in Kodos performance testing applies: measure the slow path, remove avoidable work, and keep the interface honest. A bounded worker pool, per-attempt deadline, and stable output are sufficient for most needs.
The project can sit beside the other tools without pretending they share a purpose. DenyHosts protects an SSH-facing host, Kodos explores regular expressions, and Scratchy analyses Apache logs; a TCP checker answers a narrower operational question. Predictable behaviour and reproducible results give it lasting value.
