Pure python SSH tunnels for creating secure connections through intermediate servers
npx @tessl/cli install tessl/pypi-sshtunnel@0.4.0Pure python SSH tunnels for creating secure connections through intermediate servers. This library enables both local and remote port forwarding via SSH, supporting applications that need reliable SSH tunnel functionality without external dependencies beyond paramiko.
pip install sshtunnelimport sshtunnelCommon patterns:
from sshtunnel import SSHTunnelForwarder, open_tunnelfrom sshtunnel import SSHTunnelForwarder
# Create and start SSH tunnel
server = SSHTunnelForwarder(
ssh_address_or_host='ssh.example.com',
ssh_username='user',
ssh_password='secret',
remote_bind_address=('127.0.0.1', 3306),
local_bind_address=('127.0.0.1', 3307)
)
server.start()
print(f"Server connected via local port: {server.local_bind_port}")
# Use your tunnel connection here
# Connect to localhost:3307 to reach 127.0.0.1:3306 on remote server
server.stop()Context manager usage:
from sshtunnel import open_tunnel
with open_tunnel(
('ssh.example.com', 22),
ssh_username='user',
ssh_password='secret',
remote_bind_address=('127.0.0.1', 3306),
local_bind_address=('127.0.0.1', 3307)
) as tunnel:
print(f"Tunnel opened on local port: {tunnel.local_bind_port}")
# Use tunnel connection here
# Tunnel automatically closed when exiting contextThe sshtunnel package is built around a few key components:
Main functionality for creating and managing SSH tunnels with full lifecycle control.
class SSHTunnelForwarder:
def __init__(
self,
ssh_address_or_host=None,
ssh_config_file=SSH_CONFIG_FILE,
ssh_host_key=None,
ssh_password=None,
ssh_pkey=None,
ssh_private_key_password=None,
ssh_proxy=None,
ssh_proxy_enabled=True,
ssh_username=None,
local_bind_address=None,
local_bind_addresses=None,
logger=None,
mute_exceptions=False,
remote_bind_address=None,
remote_bind_addresses=None,
set_keepalive=5.0,
threaded=True,
compression=None,
allow_agent=True,
host_pkey_directories=None
): ...
def start(self): ...
def stop(self, force=False): ...
def close(self): ...
def restart(self): ...Access tunnel connection information and status.
class SSHTunnelForwarder:
@property
def local_bind_port(self) -> int: ...
@property
def local_bind_ports(self) -> list[int]: ...
@property
def local_bind_host(self) -> str: ...
@property
def local_bind_hosts(self) -> list[str]: ...
@property
def local_bind_address(self) -> tuple: ...
@property
def local_bind_addresses(self) -> list[tuple]: ...
@property
def tunnel_bindings(self) -> dict: ...
tunnel_is_up: dict # Instance attribute - tunnel status mapping
@property
def is_active(self) -> bool: ...
is_alive: bool # Instance attribute - whether tunnels are alive
def check_tunnels(self): ...
def local_is_up(self, target: tuple) -> bool: ... # deprecatedConvenience function for common tunnel use cases with context manager support.
def open_tunnel(*args, **kwargs) -> SSHTunnelForwarder:
"""
Open an SSH Tunnel, wrapper for SSHTunnelForwarder.
Parameters:
- ssh_address_or_host: SSH server address/hostname
- ssh_username: SSH username
- ssh_password: SSH password
- ssh_pkey: Private key file path or paramiko.PKey object
- ssh_port: SSH port (default: 22)
- remote_bind_address: Remote (host, port) tuple
- remote_bind_addresses: List of remote bind addresses
- local_bind_address: Local (host, port) tuple
- local_bind_addresses: List of local bind addresses
- debug_level: Log level for debugging
- skip_tunnel_checkup: Disable tunnel status checking
- Additional SSHTunnelForwarder parameters supported
Returns:
SSHTunnelForwarder instance configured as context manager
"""Utilities for loading SSH keys from various sources.
class SSHTunnelForwarder:
@staticmethod
def get_agent_keys(logger=None) -> list:
"""Load public keys from SSH agent."""
@staticmethod
def get_keys(
logger=None,
host_pkey_directories=None,
allow_agent=False
) -> list:
"""Load keys from SSH agent or local directories."""
@staticmethod
def read_private_key_file(
pkey_file: str,
pkey_password=None,
key_type=None,
logger=None
):
"""Read private key file and return paramiko.PKey object."""Utility functions for address validation and logger configuration.
def check_host(host: str): ...
def check_port(port: int): ...
def check_address(address): ...
def check_addresses(address_list: list, is_remote=False): ...
def create_logger(
logger=None,
loglevel=None,
capture_warnings=True,
add_paramiko_handler=True
) -> logging.Logger:
"""Create or configure logger with console handler."""
def address_to_str(address) -> str:
"""Convert address to string representation."""
def get_connection_id() -> int:
"""Generate unique connection ID for tunnel connections."""class BaseSSHTunnelForwarderError(Exception):
"""Base exception for SSHTunnelForwarder errors."""
class HandlerSSHTunnelForwarderError(BaseSSHTunnelForwarderError):
"""Exception for tunnel forwarder errors."""__version__: str = '0.4.0'
__author__: str = 'pahaz'
SSH_TIMEOUT: float = 0.1 # Transport socket timeout
TUNNEL_TIMEOUT: float = 10.0 # Tunnel connection timeout
DEFAULT_LOGLEVEL: int = logging.ERROR # Default log level
TRACE_LEVEL: int = 1 # Trace log level
DEFAULT_SSH_DIRECTORY: str = '~/.ssh' # Default SSH directory
SSH_CONFIG_FILE: str # Path to SSH config filefrom sshtunnel import SSHTunnelForwarder
# Create multiple tunnels at once
server = SSHTunnelForwarder(
ssh_address_or_host='ssh.example.com',
ssh_username='user',
ssh_password='secret',
remote_bind_addresses=[('127.0.0.1', 3306), ('127.0.0.1', 5432)],
local_bind_addresses=[('127.0.0.1', 3307), ('127.0.0.1', 5433)]
)
server.start()
print(f"MySQL tunnel: localhost:{server.local_bind_ports[0]}")
print(f"PostgreSQL tunnel: localhost:{server.local_bind_ports[1]}")
server.stop()from sshtunnel import SSHTunnelForwarder
# Using SSH key file
server = SSHTunnelForwarder(
ssh_address_or_host='example.com',
ssh_username='user',
ssh_pkey='/home/user/.ssh/id_rsa',
ssh_private_key_password='key_password', # if key is encrypted
remote_bind_address=('192.168.1.100', 22),
local_bind_address=('127.0.0.1', 2222)
)
# Using SSH agent keys
server = SSHTunnelForwarder(
ssh_address_or_host='example.com',
ssh_username='user',
allow_agent=True,
remote_bind_address=('192.168.1.100', 22)
)import logging
from sshtunnel import SSHTunnelForwarder, create_logger
# Create custom logger
logger = create_logger(loglevel=logging.DEBUG)
server = SSHTunnelForwarder(
ssh_address_or_host='example.com',
ssh_username='user',
ssh_password='secret',
remote_bind_address=('127.0.0.1', 3306),
logger=logger
)from sshtunnel import (
SSHTunnelForwarder,
BaseSSHTunnelForwarderError,
HandlerSSHTunnelForwarderError
)
try:
server = SSHTunnelForwarder(
ssh_address_or_host='example.com',
ssh_username='user',
ssh_password='wrong_password',
remote_bind_address=('127.0.0.1', 3306),
mute_exceptions=False # Enable exception raising
)
server.start()
except BaseSSHTunnelForwarderError as e:
print(f"SSH tunnel error: {e}")
except HandlerSSHTunnelForwarderError as e:
print(f"Tunnel handler error: {e}")from sshtunnel import SSHTunnelForwarder
# Using HTTP proxy
server = SSHTunnelForwarder(
ssh_address_or_host='target.example.com',
ssh_username='user',
ssh_password='secret',
ssh_proxy=('proxy.example.com', 8080),
remote_bind_address=('127.0.0.1', 3306)
)
# Using ProxyCommand from SSH config
server = SSHTunnelForwarder(
ssh_address_or_host='target.example.com',
ssh_username='user',
ssh_config_file='~/.ssh/config', # Contains ProxyCommand
ssh_proxy_enabled=True,
remote_bind_address=('127.0.0.1', 3306)
)