How to Check TCP Port Reachability in Python (Sync & Async)
- December 22, 2025
- 10 min read
- Python programming , Debugging and troubleshooting
Table of Contents
Nothing stalls a deployment quite like a ‘Connection Refused’ error that you didn’t see coming. Whether you’re debugging a silent firewall change or monitoring a microservice, verifying TCP reachability is the first line of defense in network troubleshooting.
One common way to do this is by checking if a TCP service on a remote server is accepting connections.
In this article, we’ll look at how to implement a simple remote TCP service reachability check in Python using both synchronous and asynchronous approaches.
By the end, you’ll understand how to create these checks, when to use them, and why they are important.
​
Why Check TCP Reachability?
At its core, checking TCP reachability involves trying to establish a connection to a specific port on a remote server. If the connection is successful, the service is reachable. If the connection fails (e.g., due to a timeout or refused connection), the service is either down or unreachable.
This is useful for:
- Monitoring service availability.
- Verifying firewall or network configurations.
- Debugging connection issues in distributed systems.
If you are currently at a Linux terminal, in a restricted container, you can also check ports using only built-in Bash features. See my guide on 5 Essential Network Debugging Commands in Minimal Linux for a quick, no-install approach.
Now let’s see how to implement it without relying on third-party libraries or tools.
Newsletter
Subscribe to our newsletter and stay updated.
​
Without Third-party Tools or Libraries
In Python, the socket standard library provides everything we need to implement a TCP reachability check. We’ll look at two approaches: synchronous (blocking) and asynchronous (non-blocking).
​
Synchronous Implementation
The synchronous approach is straightforward and easy to understand. It attempts to create a connection to the remote service and either succeeds or times out.
Here’s the code:
1import socket
2
3def check_port_reachable(address: str, port: int, timeout_sec: float = 3) -> bool:
4 """
5 Check that the (address, port) is reachable using a TCP connection.
6 Return True if reachable, else False.
7
8 Parameters
9 ----------
10 address : str
11 The IP address or hostname of the remote service.
12 port : int
13 The port number to check.
14 timeout_sec : int
15 The timeout for the connection attempt, in seconds.
16
17 Returns
18 -------
19 bool
20 True if the port is reachable, False otherwise.
21 """
22 # For IPV6 Use `AF_INET6` instead of `AF_INET`
23 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
24 sock.settimeout(timeout_sec)
25
26 try:
27 sock.connect((address, port))
28 sock.shutdown(socket.SHUT_RDWR)
29 sock.close()
30 return True
31 except (socket.timeout, socket.error):
32 sock.close()
33 return False
How It Works
- A TCP socket is created using
socket.socket. - The
connectmethod tries to connect to the specified(address, port)within a given timeout (timeout_sec). - If successful, the connection is shut down and closed, and the function returns
True. If not, it returnsFalse.
Alternative Version Using connect_ex
A slight variation of this code uses connect_ex, which returns 0 if the connection succeeds, and a different (error) code otherwise:
1def check_port_with_connect_ex(address: str, port: int, timeout_sec: float = 3) -> bool:
2 try:
3 # For IPV6 Use `AF_INET6` instead of `AF_INET`
4 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
5 sock.settimeout(timeout_sec)
6 if sock.connect_ex((address, port)) == 0:
7 return True
8 return False
9 except OSError:
10 return False
This version handles exceptions like invalid IP addresses or ports more gracefully and avoids raw exception catching.
Tip
If you compare this code version with the with check_port_reachable, you will see that I used with statement when opening the socket.
This makes the code close the socket automatically when going out of the scope of with.
​
Asynchronous Implementation
For applications requiring non-blocking I/O, especially when dealing with many connections, an asynchronous approach using Python’s asyncio library can be more efficient.
Here’s how you can do it asynchronously:
1import asyncio
2
3async def check_port_reachable_async(address, port, timeout_sec=3):
4 """
5 Asynchronously check if the (address, port) is reachable using a TCP connection.
6 Return True if reachable, else False.
7
8 Parameters
9 ----------
10 address : str
11 The IP address or hostname of the remote service.
12 port : int
13 The port number to check.
14 timeout_sec : int
15 The timeout for the connection attempt, in seconds.
16
17 Returns
18 -------
19 bool
20 True if the port is reachable, False otherwise.
21 """
22 writer = None
23 try:
24 # Attempt to open a connection asynchronously
25 reader, writer = await asyncio.wait_for(
26 asyncio.open_connection(address, port),
27 timeout=timeout_sec
28 )
29 # Cleanly close the connection
30 writer.close()
31 await writer.wait_closed()
32 return True
33 except (TimeoutError, asyncio.TimeoutError, ConnectionRefusedError, OSError):
34 return False
35 finally:
36 if writer:
37 writer.close()
38 try:
39 await writer.wait_closed() # added in Python 3.7
40 except (OSError, AttributeError):
41 pass # Already closed
How It Works
asyncio.open_connectiontries to establish a TCP connection to the remote service asynchronously.- If successful, it closes the connection and returns
True. - If the connection attempt times out or is refused, it returns
False.
Why Use Asynchronous?
If you need to check multiple services at once, use an asynchronous approach. Instead of waiting for each connection to finish one by one, asyncio lets you start all checks simultaneously. This significantly boosts performance when monitoring large clusters or distributed systems.
Newsletter
Subscribe to our newsletter and stay updated.
​
When to Use a Retry-Backoff Mechanism
Sometimes, network glitches can cause temporary connection issues. In these cases, it may be useful to retry the reachability check a few times before giving up. You can implement this using a simple retry mechanism.
While simple loops work for basic tasks, production systems need more. Sophisticated strategies like exponential backoff and jitter prevent systems from crashing under pressure. For a deep dive into these concepts, see my article on Software Robustness and Timeout Retry Backoff Paradigms.
Here’s an example of a simple retry mechanism with a constant delay:
- sync
- async
1import time
2
3def check_with_retries(address, port, retries=3, delay_sec=2):
4 for attempt in range(retries):
5 if check_port_reachable(address, port):
6 return True
7 time.sleep(delay_sec)
8 return False
1import asyncio
2
3async def check_with_retries_async(address, port, retries=3, delay_sec=2):
4 for attempt in range(retries):
5 if await check_port_reachable_async(address, port):
6 return True
7 await asyncio.sleep(delay_sec)
8 return False
This retries the reachability check up to 3 times, with a 2-second delay between attempts.
​
Common Reasons for Non-Reachability
Several factors can cause a remote service to be unreachable:
- The service is down: The service may not be running on the remote machine.
- Network issues: Routing issues, dropped packets, or DNS problems can prevent the connection.
- Incorrect address or port: Double-check that the IP/hostname and port are correct.
- Firewall restrictions: Network firewalls may be blocking incoming or outgoing connections on the port.
- Silent Packet Dropping: Be aware that some firewalls are configured to ‘DROP’ packets rather than ‘REJECT’ them. In these cases, your script won’t get a ‘Refused’ error immediately; it will hang for the entire duration of your
timeout_secbefore returningFalse.
Understanding why a check fails is key to fixing it. Is it a temporary Error or a complete system Failure? I break down the crucial differences between these terms in Illustrative Explanation of Fault, Error, Failure, bug, and Defect in Software.
Choosing the right timeout is a balancing act. It must be long enough for a slow service but short enough to keep your app snappy. My guide on Timeout Retry Backoff Paradigms explains how to calculate these values.
​
Using Third-party Tools and Libraries
While Python’s standard library is powerful, third-party tools can simplify complex reachability checks or integrate with broader network scanning needs.
These tools often provide advanced features like service identification, multi-threading, and more robust retry mechanisms.
Below, we’ll cover some popular third-party libraries for network scanning and TCP reachability checks, and summarize their key characteristics in a comparison table.
​
Popular Third-party Tools and Libraries
-
Scapy: A powerful network packet manipulation library that allows users to craft and send custom packets. It can be used for tasks like port scanning, but also extends to more advanced use cases like security testing and protocol analysis.
-
PortScan: A Python package that allows you to scan ports and services on remote hosts. It’s lightweight and easy to use, providing quick reachability checks.
-
TPScanner: A Python-based port scanner tool that can be easily adapted to check multiple ports. It offers basic functionality for TCP scanning.
-
python-nmap: A Python wrapper around the Nmap network scanning tool. It enables users to leverage Nmap’s comprehensive port scanning, service detection, and OS fingerprinting features within Python scripts.
-
PyPortScanner: A simple and easy-to-use Python library for scanning TCP ports. It is suitable for lightweight tasks where the goal is just to check if ports are open.
​
Summary of Third-party Libraries
| Library | Features | Installation Complexity | Best For |
|---|---|---|---|
| Scapy | Low-level packet crafting, port scanning, security testing | Medium | Advanced network testing and packet manipulation |
| PortScan | Basic TCP port scanning, lightweight, quick checks | Low | Simple, fast reachability checks |
| TPScanner | Scans multiple ports, easily adaptable, open-source tool | Low | Lightweight multi-port scanning |
| python-nmap | Full-fledged network scanning (service, OS detection, etc.) | Medium (requires Nmap) | Comprehensive network scanning |
| PyPortScanner | Lightweight, easy-to-use TCP port scanning | Low | Quick, simple port scanning tasks |
With these libraries, you can choose the one that best fits your specific needs, whether it’s simple port checking, asynchronous scanning, or complex network probing.
Newsletter
Subscribe to our newsletter and stay updated.
​
Frequently Asked Questions
- What is the difference between a Ping and a TCP check?
- A ping uses ICMP packets to see if a host is alive, but it doesn’t tell you if a specific service (like a web server or database) is running. A TCP check confirms that a specific port is actually listening and ready to accept traffic.
- Does checking reachability actually open a session?
- Yes, these methods perform a “three-way handshake”. Our scripts immediately close the connection afterward to minimize the impact on the remote service.
Strictly speaking,
socket.connectperforms a full handshake, but it doesn’t send any application data (like HTTP headers), making it very lightweight and safe for frequent monitoring.
​
Conclusion
Checking the reachability of a remote TCP service is an essential task in many networking scenarios. Using Python’s built-in libraries, you can easily implement both synchronous and asynchronous approaches to verify whether a service is available.
Depending on your use case, you can also introduce retry-backoff mechanisms or leverage third-party tools to streamline the process.
These techniques help you manage servers and debug connectivity issues. They are the first step in ensuring your networked applications stay reachable and functional.
TCP checks are just one part of a stable system. To build truly resilient applications, you need a broader strategy. Learn more about designing for reliability in my guide on Quality Attributes of Computer Programs: Implement Software Robustness.