--- title: "Build Your Own Port Scanner in Python (and Understand How Nmap Thinks)" author: "Lorenzo Fazioli" published: "2026-09-18T15:39:05.950Z" updated: "2026-09-18T15:39:05.950Z" url: "https://zyvop.com/build-your-own-port-scanner-in-python-and-understand-how-nmap-thinks-tesdx" canonical: "https://zyvop.com/build-your-own-port-scanner-in-python-and-understand-how-nmap-thinks-tesdx" tags: ["cybersecurity","PortScanner","Python","GitHub","hacker"] --- # Build Your Own Port Scanner in Python (and Understand How Nmap Thinks) It’s simple enough to build in an afternoon, but building one teaches you more about how networks actually behave than reading ten articles about Nmap flags. ![Cover image]() **Why write one when Nmap exists?** Because using a tool and understanding a tool are different skills. When you write the scan loop yourself, you feel why a full connect scan is loud, why timeouts dominate your runtime, and why threading matters. That intuition transfers directly to reading Nmap output later. **The naive version** Start with the simplest thing that works: a socket, a connect call, and a loop. ```python import socket def scan(host, ports): for port in ports: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(0.5) if s.connect_ex((host, port)) == 0: print(f"[+] {port}/tcp open") s.close() scan("127.0.0.1", range(1, 1025)) ``` `connect_ex` is the key: it returns an error code instead of raising, so a closed port is just a non-zero return, not an exception to catch. **Why it’s painfully slow** Run it against 1024 ports and you’ll wait. Each closed port burns the full timeout serially. Scanning 1024 ports at 0.5s each is over eight minutes in the worst case — for one host. **Making it threaded** Network scanning is I/O bound, so threads help enormously here despite the GIL. ```python from concurrent.futures import ThreadPoolExecutor def check(host, port): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.settimeout(0.5) if s.connect_ex((host, port)) == 0: return port def scan(host, ports, workers=200): with ThreadPoolExecutor(max_workers=workers) as pool: results = pool.map(lambda p: check(host, p), ports) return [p for p in results if p] ``` Suddenly 1024 ports finish in seconds. Where to go next - Grab banners: after connecting, send a probe and read the first bytes to fingerprint the service. - Add a SYN scan with raw sockets (requires root) to avoid completing the handshake. - Rate-limit yourself so you don’t trip IDS on networks you’re allowed to test. One rule before you run it Only scan hosts you own or have written permission to test. A port scan against someone else’s infrastructure can be illegal in most jurisdictions, full stop. If you want the finished version, my Portscanner repo is on GitHub.