PortReach
A lightweight Python utility for checking TCP port connectivity. PortReach is designed for developers, DevOps engineers, and system administrators who need a fast way to diagnose network accessibility from the command line.
Release history: CHANGELOG.md.
PortReach combines:
- Simplicity: Just
portreach localhost 8080to check a port - Reliability: Socket-based TCP checks with configurable timeouts
- Speed: Minimal startup time and low overhead
- Cross-platform: Works on Windows, macOS, and Linux
- Scriptable: Machine-readable output and predictable exit codes
- Extensible: Clean core API for use from Python applications
Table of Contents
- What PortReach Does
- Why PortReach Is Useful
- Key Features
- Requirements
- Installation
- Quick Start
- Usage Guide
- Host Support
- Python API
- Error Handling
- Common Use Cases
- Development
- Project Structure
- Contributing
- Security and Scope
- Roadmap
- License
- Author
What PortReach Does
PortReach performs TCP connectivity checks by attempting to establish a socket connection to a specified host and port. It reports whether each port is reachable (open) or not (closed).
Key capabilities:
- Check individual ports
- Check multiple ports in a single invocation
- Expand port ranges (e.g.,
8000-8010→ check ports 8000 through 8010) - Resolve DNS hostnames to IP addresses
- Customize connection timeouts
- Output results in human-readable or JSON format
- Generate predictable exit codes for automation
Why PortReach Is Useful
Common Scenarios
Local Development
# Verify your development server is running
portreach localhost 3000
# Check multiple service ports at once
portreach localhost 3000 5432 6379
Deployment Verification
# Confirm application port is accessible after deployment
portreach api.example.com 443 --timeout 5
# Check a range of worker ports on a load balancer
portreach lb.internal 8000-8010
CI/CD Pipelines
# Exit code 0 if port is open, 1 if closed
portreach app-server 5000 --quiet
if [ $? -eq 0 ]; then
echo "Health check passed"
exit 0
else
echo "Service is not responding"
exit 1
fi
Container Health Checks
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD portreach localhost 8080 --quiet
Troubleshooting Connectivity
# Is the firewall blocking port 443?
portreach example.com 443
# Which database ports are accessible from this host?
portreach db-server 3306 5432 27017
Why Not Other Tools?
| Feature | PortReach | netcat | telnet | curl | nc |
|---|---|---|---|---|---|
| Pure Python | ✓ | ✗ | ✗ | ✗ | ✗ |
| No Dependencies | ✓ | N/A | N/A | N/A | N/A |
| Cross-Platform | ✓ | ✓ | ✗ | ✓ | ✓ |
| JSON Output | ✓ | ✗ | ✗ | ✓ | ✗ |
| Port Ranges | ✓ | ✗ | ✗ | ✗ | ✗ |
| Easy Install | ✓ | N/A | N/A | ✓ | ✗ |
| Python API | ✓ | ✗ | ✗ | ✗ | ✗ |
Key Features
✅ Lightweight and Fast
- Pure Python implementation
- No external dependencies at runtime
- Minimal memory footprint
- Low startup overhead
✅ Simple, Intuitive CLI
- Clear, readable output with visual indicators (✓/✗)
- Sensible defaults (2-second timeout)
- Comprehensive help text (
portreach --help)
✅ Flexible Input
- Individual ports:
80 443 8080 - Ranges:
8000-8010 - Mixed:
80 443 8000-8005 9000
✅ Multiple Output Formats
- Human-readable (default)
- JSON for integration with other tools
- Quiet mode for scripts
✅ Reliable Exit Codes
0: All ports open1: One or more ports closed2: Configuration error (invalid arguments, invalid ports/ranges, invalid timeout, etc.)
✅ Robust Error Handling
- DNS resolution failures are handled gracefully
- Connection timeouts are configurable
- Clear error messages without Python tracebacks
✅ Typed Python API
- Type-annotated Python functions
- Reusable in other applications
- Suitable for scripting and automation
Requirements
- Python 3.10 or later (3.10, 3.11, 3.12, 3.13 tested)
- Operating System: Windows, macOS, or Linux
- Network Access: Ability to create TCP sockets to target hosts/ports
- No external dependencies at runtime
PortReach preserves the ✓ and ✗ status symbols when the output stream supports them and uses [+] and [-] when it does not.
Python Version Support
| Version | Support |
|---|---|
| Python 3.10 | ✓ Supported |
| Python 3.11 | ✓ Supported |
| Python 3.12 | ✓ Supported |
| Python 3.13 | ✓ Supported |
| Python 3.9 and earlier | ✗ Not supported |
Installation
From PyPI
PortReach v0.1.0 has not been published to PyPI. Install from source using the instructions below; do not use pip install portreach until a release is published.
From Source
Clone the repository and install in development mode:
git clone https://github.com/dimpletz/portreach.git
cd portreach
For development (including test dependencies):
pip install -e ".[dev]"
Quick Start
The commands below show syntax only; their output depends on whether the example target and port are reachable.
Check if a port is open
$ portreach localhost 8080
✓ 8080: OPEN
Check if a port is not accessible
$ portreach localhost 9999
✗ 9999: CLOSED
Check multiple ports
$ portreach example.com 80 443 8080
✓ 80: OPEN
✓ 443: OPEN
✗ 8080: CLOSED
Use quiet mode for scripts
$ portreach localhost 3000 --quiet
$ echo $?
0
Get JSON output
$ portreach localhost 8080 --json
{
"host": "localhost",
"results": [
{
"port": 8080,
"status": "open"
}
]
}
Usage Guide
CLI Syntax
portreach HOST PORT [PORT ...] [OPTIONS]
Positional Arguments:
HOST Hostname, IPv4 address, or IPv6 address to check
PORT Port numbers or ranges (e.g., 80 443 8000-8010)
Options:
--timeout SECONDS Connection timeout in seconds (default: 2.0)
--json Output results as JSON
--quiet Suppress all output (useful for scripts)
--version Show program version
--help Show this help message
Single Port
Check a single port on a host:
# Check localhost
$ portreach localhost 8080
# Check a domain
$ portreach example.com 443
# Check an IP address
$ portreach 192.168.1.100 22
Output:
✓ 8080: OPEN
or
✗ 8080: CLOSED
Multiple Ports
Check several ports in one command:
$ portreach example.com 80 443 8080 9000
Output:
✓ 80: OPEN
✓ 443: OPEN
✗ 8080: CLOSED
✗ 9000: CLOSED
Ports are checked sequentially. Duplicates are automatically removed and results are sorted.
Port Ranges
Expand a range of ports:
# Check ports 8000 through 8010
$ portreach localhost 8000-8010
Output:
✓ 8000: OPEN
✓ 8001: OPEN
✓ 8002: OPEN
✓ 8003: OPEN
✓ 8004: OPEN
✗ 8005: CLOSED
✗ 8006: CLOSED
✗ 8007: CLOSED
✗ 8008: CLOSED
✗ 8009: CLOSED
✗ 8010: CLOSED
Mix individual ports and ranges:
$ portreach localhost 80 443 8000-8005 9000
Port ranges must be valid:
- Both start and end must be integers between 1 and 65535
- Start port must be ≤ end port
Timeout Configuration
By default, PortReach waits 2 seconds for a connection:
$ portreach example.com 443 --timeout 2
For slow networks or distant hosts, increase the timeout:
$ portreach slow-server.example.com 443 --timeout 10
For quick checks on your local network:
$ portreach 192.168.1.1 22 --timeout 1
Note: Timeout must be a positive number (in seconds).
JSON Output
Get structured output for programmatic use:
$ portreach localhost 80 443 --json
Output:
{
"host": "localhost",
"results": [
{
"port": 80,
"status": "open"
},
{
"port": 443,
"status": "closed"
}
]
}
Fields:
host(string): The hostname or IP address checkedresults(array): Result for each portport(int): Port numberstatus(string):"open"or"closed"error(string, optional): Platform-dependent resolver diagnostic when hostname resolution fails
Quiet Mode
Suppress normal per-port result output for valid requests; rely on exit codes. Argument-parser diagnostics can still be printed for malformed invocations:
$ portreach localhost 8080 --quiet
$ echo $?
0
No output is printed. Check the exit code to determine the result:
0= all ports open1= any port closed2= error
Useful in shell scripts:
#!/bin/bash
portreach app-server 3000 --quiet
if [ $? -eq 0 ]; then
echo "Application is running"
else
echo "Application is not responding"
exit 1
fi
Exit Codes
PortReach returns specific exit codes for automation and CI/CD:
| Code | Meaning | Example |
|---|---|---|
0 |
Success: All requested ports are open | portreach localhost 8080 (port open) |
1 |
Failure: One or more ports are closed | portreach localhost 9999 (port closed) |
2 |
Error: Invalid arguments or configuration error | portreach localhost 70000 (invalid port) |
Examples
All ports open → exit 0:
$ portreach localhost 80 443 --quiet
$ echo $?
0
Port closed → exit 1:
$ portreach localhost 9999 --quiet
$ echo $?
1
Invalid port → exit 2:
$ portreach localhost 70000 --quiet
$ echo $?
2
Host Support
Hostnames
Resolve and check hosts by DNS name:
$ portreach example.com 80
$ portreach api.example.com 443
$ portreach localhost 8080
DNS resolution happens automatically. If a hostname cannot be resolved, PortReach reports the port as closed with an error message.
IPv4 Addresses
Check IPv4 addresses directly (no DNS lookup needed):
$ portreach 127.0.0.1 8080
$ portreach 192.168.1.1 22
$ portreach 10.0.0.5 443
IPv6 Addresses
IPv6 is supported on systems with IPv6 network stack:
$ portreach ::1 8080
$ portreach 2001:db8::1 443
Note: IPv6 support depends on your operating system and network configuration. Use the IPv6 address in standard notation (colons and hex digits).
Python API
Use PortReach programmatically in your Python applications.
Check a Single Port
from portreach import check_port
result = check_port("localhost", 8080)
print(result.status) # "open" or "closed"
print(result.port) # 8080
Check Multiple Ports
from portreach import check_ports
result = check_ports("example.com", [80, 443, 8080])
# Check if all ports are open
if result.all_open:
print("All ports are accessible")
# Check if any port is open
if result.any_open:
print("At least one port is accessible")
# Iterate through results
for port_result in result.results:
print(f"Port {port_result.port}: {port_result.status}")
Serialize Results to JSON
from portreach import check_ports
import json
result = check_ports("localhost", [80, 443])
json_data = result.to_dict()
print(json.dumps(json_data, indent=2))
Output:
{
"host": "localhost",
"results": [
{
"port": 80,
"status": "open"
},
{
"port": 443,
"status": "closed"
}
]
}
Parse Port Specifications
from portreach.checker import parse_ports
ports = parse_ports(["80", "443", "8000-8010"])
print(ports) # [80, 443, 8000, 8001, ..., 8010]
Custom Timeout
from portreach import check_port
result = check_port("slow-server.example.com", 443, timeout=5.0)
Exception Handling
Invalid port values raise InvalidPortError. Hostname resolution failures are returned as a closed result with an error message; they are not raised by check_port:
from portreach import InvalidPortError, check_port
try:
result = check_port("localhost", 0)
except InvalidPortError as e:
print(f"Invalid port: {e}")
result = check_port("localhost", 80)
if result.error:
print(f"Host resolution failed: {result.error}")
Result Objects
PortReach uses typed models for results:
from portreach import check_ports
from portreach.models import CheckResult, PortResult
result: CheckResult = check_ports("localhost", [80, 443])
# CheckResult properties
print(result.host) # "localhost"
print(result.timeout) # 2.0
print(result.all_open) # bool: True if all ports open
print(result.any_open) # bool: True if any port open
# PortResult objects
for port_result in result.results:
print(port_result.port) # int
print(port_result.status) # "open" or "closed"
print(port_result.error) # str or None
Error Handling
PortReach handles errors gracefully and provides clear error messages.
Hostname Resolution Failure
If a hostname cannot be resolved, the port is reported as closed with an error message:
$ portreach invalid.hostname.local 80
✗ 80: Could not resolve hostname 'invalid.hostname.local': <system resolver message>
Exit code: 1 (port unreachable)
Invalid Port Number
Ports must be between 1 and 65535:
$ portreach localhost 99999
Error: Port must be between 1 and 65535, got 99999
Exit code: 2 (configuration error)
Invalid Port Range
Port ranges must have start ≤ end:
$ portreach localhost 8010-8000
Error: Invalid port range: 8010-8000 (start 8010 > end 8000)
Exit code: 2
Malformed Arguments
$ portreach localhost abc
Error: Invalid port number: abc
Exit code: 2
Connection Timeout
If a connection takes longer than the timeout, the port is reported as closed:
$ portreach unreachable-host 80 --timeout 1
✗ 80: CLOSED
No error is shown (timeout is normal for closed/unreachable ports).
No Ports Specified
If no port arguments are provided:
$ portreach localhost
Error: no valid ports specified
Exit code: 2
Common Use Cases
Health Check in CI/CD Pipeline
#!/bin/bash
set -e
# Wait for application to start
sleep 5
# Check application port
if portreach localhost 8080 --quiet; then
echo "✓ Application is running"
else
echo "✗ Application failed to start"
exit 1
fi
Docker Health Check
FROM python:3.12-slim
COPY . .
RUN pip install .
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD portreach localhost 8000 --quiet
CMD ["python", "-m", "myapp"]
Post-Deployment Verification
#!/bin/bash
ENVIRONMENT=$1
SERVERS=("api1.example.com" "api2.example.com" "api3.example.com")
PORTS=(80 443 8080)
echo "Verifying deployment to $ENVIRONMENT..."
for server in "${SERVERS[@]}"; do
echo "Checking $server..."
for port in "${PORTS[@]}"; do
if ! portreach "$server" "$port" --timeout 5 --quiet; then
echo "✗ Failed: $server:$port"
exit 1
fi
done
echo "✓ $server is healthy"
done
echo "✓ All servers healthy"
Firewall Rule Validation
#!/bin/bash
# Test if firewall allows access to required ports
echo "Testing firewall rules..."
if portreach api.internal 443 --quiet && \
portreach db.internal 5432 --quiet && \
portreach cache.internal 6379 --quiet; then
echo "✓ All firewall rules verified"
else
echo "✗ Firewall rule violation detected"
exit 1
fi
Network Diagnostics
# Check which ports are open on a server
portreach myserver 22 80 443 3306 5432 6379 8080
# Check a range of ports (e.g., application worker ports)
portreach load-balancer 8000-8010
# Verify DNS and connectivity
portreach example.com 443 --timeout 5
# Check multiple internal services
portreach 192.168.1.10 3000 5000 8000
Development
Setup
Clone the repository and install:
git clone https://github.com/dimpletz/portreach.git
cd portreach
pip install -e ".[dev]"
This installs:
pytestfor testingpytest-covfor coveragemypyfor type checkingrufffor linting and formatting
Running Tests
Run the full test suite:
pytest
Run with verbose output:
pytest -v
Run with coverage report:
pytest --cov=src/portreach --cov-report=html
Coverage report is generated in htmlcov/index.html.
Run specific test file:
pytest tests/test_checker.py
Run specific test:
pytest tests/test_checker.py::TestPortValidation::test_valid_port
Type Checking
Check for type errors with mypy:
mypy src tests
Mypy is configured to use strict mode for maximum safety.
Code Quality
Linting
Check for code style issues:
ruff check src tests
Formatting
Check if code is properly formatted:
ruff format --check src tests
Auto-format code:
ruff format src tests
All Checks
Run all quality checks:
ruff check src tests
ruff format --check src tests
mypy src tests
pytest
PyPI Release (Maintainers)
PortReach v0.1.0 has not been published. Run .github/workflows/publish.yml manually from main, enter the existing release tag (for example, v0.1.0), and explicitly confirm publication. The workflow fetches that tag and only as much main history as needed to prove the tagged commit is reachable from main. It validates the version from the tag contents, records the tagged commit SHA, and requires the verification matrix and build job to check out that same SHA. It builds and validates distributions separately, then publishes only those artifacts after approval by the protected pypi GitHub environment, using PyPI Trusted Publishing (OIDC) without a long-lived PyPI token.
Before enabling publishing, protect v* tags with a repository ruleset that restricts tag creation, updates, and deletion to approved maintainers. Configure the pypi environment with required reviewers and allow deployments from main: GitHub evaluates environment branch/tag restrictions against the workflow dispatch ref (main), not the separately checked-out release tag. Restricting the environment to v* tags would block this main-dispatched workflow. Register a PyPI Trusted Publisher with owner dimpletz, repository portreach, workflow filename publish.yml, and environment pypi; because the project is not yet on PyPI, use PyPI's pending-publisher setup for the first publication if required.
GitHub Actions are pinned to immutable commit SHAs to prevent mutable-tag substitution. Maintainers must deliberately update these pins when upgrading actions.
Project Structure
portreach/
├── .github/
│ └── workflows/
│ └── ci.yml # GitHub Actions CI/CD
├── src/
│ └── portreach/
│ ├── __init__.py # Package exports
│ ├── __main__.py # Module entry point
│ ├── checker.py # Core port checking logic
│ ├── cli.py # CLI interface
│ ├── exceptions.py # Custom exceptions
│ └── models.py # Result dataclasses
├── tests/
│ ├── conftest.py # pytest configuration
│ ├── test_checker.py # Core logic tests
│ └── test_cli.py # CLI tests
├── test_sanity_check.py # Quick smoke checks
├── test_manual_scenarios.py # Functional scenarios
├── verify_release.ps1 # Windows release checks
├── verify_release.sh # Bash release checks
├── RENAME_REPORT.md # Rename verification notes
├── .gitignore
├── CHANGELOG.md # Version history
├── LICENSE # MIT License
├── README.md # This file
├── TESTING.md # Testing instructions
└── pyproject.toml # Package configuration
Module Overview
checker.py - Core networking logic
validate_port(port)- Validate port numberparse_ports(specs)- Parse port specifications and rangescheck_port(host, port, timeout)- Check single portcheck_ports(host, ports, timeout)- Check multiple ports
cli.py - Command-line interface
create_parser()- Build argument parserformat_port_result()- Format single port resultformat_check_result()- Format multiple ports resultmain(argv)- CLI entry point
models.py - Data types
PortResult- Single port resultCheckResult- Multiple ports result
exceptions.py - Exception classes
PortReachError- Base exceptionInvalidPortError- Invalid port numberInvalidRangeError- Invalid port rangeInvalidHostError- Internal hostname-resolution exception;check_portreturns resolution failures inPortResult.error
Contributing
Contributions are welcome! Please follow these guidelines:
Before Contributing
- Check existing issues
- For major changes, open an issue to discuss approach
- Keep changes focused and minimal
Code Standards
- All tests must pass:
pytest - Code must be type-checked:
mypy src tests - Code must pass linting:
ruff check src tests - Code must be formatted:
ruff format src tests - New features should include tests
Submitting Changes
- Fork the repository
- Create a feature branch:
git checkout -b feature/your-feature - Make your changes
- Write tests for new functionality
- Ensure all checks pass
- Commit with clear message:
git commit -am 'Add feature X' - Push to your fork:
git push origin feature/your-feature - Open a pull request
What We're Looking For
- Bug reports with reproduction steps
- Performance improvements (with benchmarks)
- Documentation improvements
- Test coverage improvements
- New port checking strategies (if they don't add dependencies)
What We Won't Accept
- Changes that add external runtime dependencies
- Changes that reduce type safety
- Contributions without tests
- Code that breaks existing functionality
Security and Scope
What PortReach Is
PortReach is a connectivity diagnostic utility. It checks whether TCP ports are reachable using socket connections.
What PortReach Is NOT
PortReach is not a security scanner or penetration testing tool. It does not:
- Perform vulnerability scanning
- Exploit security vulnerabilities
- Conduct stealth scans or evasive techniques
- Test credentials or authentication
- Perform network discovery or subnet scanning
- Conduct aggressive or high-volume scanning
- Fingerprint services or detect versions
- Attempt to exploit services
Safe Use
PortReach is safe to use on:
- Your own systems
- Servers you own or have permission to test
- Internal networks and services
- Development and testing environments
Unsafe Use
Do not use PortReach to:
- Scan systems you don't own or haven't been authorized to test
- Violate computer access laws or policies
- Conduct unauthorized security testing
- Test systems in production without proper change management
Responsible Disclosure
If you discover a security issue in PortReach, report it through GitHub Issues and avoid including sensitive details in a public report.
Roadmap
Version 0.1.0 (Current)
✓ Single and multiple port checking ✓ Port range support ✓ JSON output ✓ Quiet mode ✓ Configurable timeouts ✓ Hostname resolution ✓ IPv4 and IPv6 support ✓ Clean Python API ✓ Comprehensive test suite ✓ Type annotations throughout
Potential Future Enhancements
-
v0.2.0 Ideas:
- Connection retry logic with exponential backoff
- Parallel port checking for faster batch operations
- DNS caching for repeated lookups
- Performance metrics (connection time)
- Configuration file support for default settings
- Shell completion scripts (bash/zsh/PowerShell)
- Batch mode for checking multiple hosts
-
Future Interfaces:
- Web UI for graphical port checking
- REST API for remote checking
- MCP (Model Context Protocol) server for AI applications
- GitHub Action for CI/CD workflows
- Docker container for isolated checking
Not Planned
- Vulnerability scanning
- Service fingerprinting
- Aggressive scanning features
- Built-in VPN or proxy support
- Integration with cloud provider APIs
License
PortReach is released under the MIT License. See LICENSE for details.
This means you can:
- Use PortReach in commercial projects
- Modify the source code
- Distribute PortReach
- Use it privately
As long as you include a copy of the license and don't hold us liable.
Author
Ofelia B Webb
For questions or issues, please open an issue on GitHub: https://github.com/dimpletz/portreach/issues
Acknowledgments
PortReach is built with:
- Python 3.10+
- pytest for testing
- mypy for type checking
- ruff for code quality
Happy port checking! 🚀
Metadata
Release files for portreach 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| portreach-0.1.0.tar.gz | 30.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| portreach-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 47.8 kB
Release files / portreach-0.1.0.tar.gz
| Download URL | portreach-0.1.0.tar.gz |
|---|---|
| Size | 30.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
be840fa284172b8d4249ad91bb266eb6adca220673e921a6f1c03bf7decb8879
|
|
BLAKE2b-256 checksum How to use checksums |
13ec53d5e329ff765254561180da3e6891e039cac6b180496ded457640e5d7b2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.
Transparency logRelease files / portreach-0.1.0-py3-none-any.whl
| Download URL | portreach-0.1.0-py3-none-any.whl |
|---|---|
| Size | 17.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
25842606922dcf5c803661af6c900e6c2cad16f0cc7f46f02c7f58c5b9236615
|
|
BLAKE2b-256 checksum How to use checksums |
bdd77e1d38784f841967a6c8f3099adcb516594fe4c240419b19509fd228b8fd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.
Transparency log