sshtunnel

raw JSON →
0.4.0 verified Tue May 12 auth: no python install: verified quickstart: stale

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.

pip install sshtunnel
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.
fix Review how tunnel threads interact with your application's lifecycle; `sshtunnel` now sets the daemon flag on tunnel threads.
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.
fix If using the context manager, be aware of the `force=True` behavior. Remove `daemon_forward_servers` and `block_on_close` from your code and adapt to the new defaults or alternatives.
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.
fix Double-check all address and port parameters. Enable `debug_level='TRACE'` in `SSHTunnelForwarder` for detailed logging during troubleshooting.
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.
fix Verify firewall rules (e.g., `iptables`, `ufw`, security groups) on all involved hosts to allow necessary traffic.
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.
fix For persistent issues, consider explicitly setting `ssh_server_alive_interval` in `SSHTunnelForwarder` or configuring `ServerAliveInterval` in your `~/.ssh/config` file.
python os / libc status wheel install import disk
3.10 alpine (musl) wheel - 0.62s 42.0M
3.10 alpine (musl) - - 0.66s 41.0M
3.10 slim (glibc) wheel 3.3s 0.45s 42M
3.10 slim (glibc) - - 0.44s 41M
3.11 alpine (musl) wheel - 0.84s 44.7M
3.11 alpine (musl) - - 0.96s 43.8M
3.11 slim (glibc) wheel 3.2s 0.79s 45M
3.11 slim (glibc) - - 0.71s 44M
3.12 alpine (musl) wheel - 1.05s 36.4M
3.12 alpine (musl) - - 1.10s 35.5M
3.12 slim (glibc) wheel 2.9s 1.14s 37M
3.12 slim (glibc) - - 1.06s 36M
3.13 alpine (musl) wheel - 1.08s 36.1M
3.13 alpine (musl) - - 1.10s 35.1M
3.13 slim (glibc) wheel 2.9s 1.03s 36M
3.13 slim (glibc) - - 1.08s 35M
3.9 alpine (musl) wheel - 0.57s 42.2M
3.9 alpine (musl) - - 0.59s 41.3M
3.9 slim (glibc) wheel 4.1s 0.60s 43M
3.9 slim (glibc) - - 0.51s 42M

This quickstart demonstrates how to establish an SSH tunnel using `SSHTunnelForwarder` to access a remote service, such as a database. It uses environment variables for secure credential handling. The `with` statement ensures the tunnel is properly started and stopped.

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.")