sshtunnel
sshtunnel is a pure Python library that enables the creation of secure SSH tunnels, primarily for port forwarding to access services (like databases or web servers) behind a firewall or on a private network. It leverages `paramiko` for SSH capabilities and is actively maintained, with recent releases addressing stability and usability.
Warnings
- breaking Version 0.4.0 changed the daemon flag for all tunnel threads, which is not fully backward compatible and may require adjustments if you relied on previous thread behavior.
- breaking Version 0.3.0 introduced several breaking changes: the default context manager behavior now uses `.stop(force=True)` on exit, the `daemon_forward_servers = True` option was removed, and the `block_on_close` option was deprecated and removed.
- gotcha Incorrect configuration is a common issue, including misconfigured local/remote ports, IP addresses, or authentication credentials. Ensure the `remote_bind_address` points to the actual target service (e.g., database) from the perspective of the SSH jump host, and the `local_bind_port` is the port your client application will connect to on your local machine.
- gotcha Firewall restrictions can prevent SSH tunnels from establishing or forwarding traffic. Ensure that the SSH port (default 22) is open on the jump server, and any forwarded ports are not blocked on either the local or remote machines.
- gotcha Long-lived idle SSH connections can be terminated by intermediate firewalls due to inactivity, even if the SSH process remains active. While `sshtunnel` 0.3.0+ sets transport keepalive, issues may still arise on highly restrictive networks.
Install
-
pip install sshtunnel
Imports
- SSHTunnelForwarder
from sshtunnel import SSHTunnelForwarder
- open_tunnel
from sshtunnel import open_tunnel
Quickstart
import os
from sshtunnel import SSHTunnelForwarder
import time
# --- Configuration from Environment Variables ---
# SSH Host (the jump server)
SSH_HOST = os.environ.get('SSH_JUMP_HOST', 'your_ssh_jump_host.com')
SSH_PORT = int(os.environ.get('SSH_JUMP_PORT', '22'))
SSH_USERNAME = os.environ.get('SSH_JUMP_USER', 'your_ssh_user')
SSH_PASSWORD = os.environ.get('SSH_JUMP_PASSWORD', '') # Optional, use SSH keys preferrably
SSH_PKEY = os.environ.get('SSH_JUMP_PKEY_PATH', '~/.ssh/id_rsa') # Path to private key
SSH_PKEY_PASSWD = os.environ.get('SSH_JUMP_PKEY_PASSWD', '') # Password for private key, if encrypted
# Remote Bind Address (the actual service you want to reach, from the perspective of the SSH Host)
REMOTE_BIND_HOST = os.environ.get('DB_HOST_PRIVATE_IP', '10.0.0.5')
REMOTE_BIND_PORT = int(os.environ.get('DB_PORT', '5432')) # e.g., 5432 for PostgreSQL, 3306 for MySQL
# Local Bind Address (where the tunnel will be accessible on your local machine)
LOCAL_BIND_HOST = os.environ.get('LOCAL_TUNNEL_HOST', '127.0.0.1')
LOCAL_BIND_PORT = int(os.environ.get('LOCAL_TUNNEL_PORT', 'auto')) # 'auto' for a random available port
# Example of connecting to a PostgreSQL database via an SSH tunnel
try:
with SSHTunnelForwarder(
(SSH_HOST, SSH_PORT),
ssh_username=SSH_USERNAME,
ssh_password=SSH_PASSWORD if SSH_PASSWORD else None,
ssh_pkey=SSH_PKEY if os.path.exists(os.path.expanduser(SSH_PKEY)) else None,
ssh_private_key_password=SSH_PKEY_PASSWD if SSH_PKEY_PASSWD else None,
remote_bind_address=(REMOTE_BIND_HOST, REMOTE_BIND_PORT),
local_bind_address=(LOCAL_BIND_HOST, LOCAL_BIND_PORT) if LOCAL_BIND_PORT != 'auto' else (LOCAL_BIND_HOST, 0)
) as tunnel:
print(f"SSH tunnel established. Local port: {tunnel.local_bind_port}")
print(f"Accessing {REMOTE_BIND_HOST}:{REMOTE_BIND_PORT} via localhost:{tunnel.local_bind_port}")
# In a real application, you would now connect your database client to:
# host='127.0.0.1', port=tunnel.local_bind_port
# For demonstration, we just keep the tunnel open for a few seconds.
print("Tunnel active for 10 seconds. Press Ctrl+C to stop sooner.")
time.sleep(10)
print("Tunnel closing.")
except Exception as e:
print(f"An error occurred: {e}")
print("Please check your SSH configuration, network connectivity, and credentials.")