{"id":712,"library":"sshtunnel","title":"sshtunnel","description":"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.","status":"active","version":"0.4.0","language":"python","source_language":"en","source_url":"https://github.com/pahaz/sshtunnel","tags":["ssh","tunneling","security","networking","paramiko","port-forwarding"],"install":[{"cmd":"pip install sshtunnel","lang":"bash","label":"Install with pip"}],"dependencies":[{"reason":"Core dependency for SSH client functionality.","package":"paramiko","optional":false}],"imports":[{"symbol":"SSHTunnelForwarder","correct":"from sshtunnel import SSHTunnelForwarder"},{"note":"Recommended wrapper function for context manager usage.","symbol":"open_tunnel","correct":"from sshtunnel import open_tunnel"}],"quickstart":{"code":"import os\nfrom sshtunnel import SSHTunnelForwarder\nimport time\n\n# --- Configuration from Environment Variables ---\n# SSH Host (the jump server)\nSSH_HOST = os.environ.get('SSH_JUMP_HOST', 'your_ssh_jump_host.com')\nSSH_PORT = int(os.environ.get('SSH_JUMP_PORT', '22'))\nSSH_USERNAME = os.environ.get('SSH_JUMP_USER', 'your_ssh_user')\nSSH_PASSWORD = os.environ.get('SSH_JUMP_PASSWORD', '') # Optional, use SSH keys preferrably\nSSH_PKEY = os.environ.get('SSH_JUMP_PKEY_PATH', '~/.ssh/id_rsa') # Path to private key\nSSH_PKEY_PASSWD = os.environ.get('SSH_JUMP_PKEY_PASSWD', '') # Password for private key, if encrypted\n\n# Remote Bind Address (the actual service you want to reach, from the perspective of the SSH Host)\nREMOTE_BIND_HOST = os.environ.get('DB_HOST_PRIVATE_IP', '10.0.0.5')\nREMOTE_BIND_PORT = int(os.environ.get('DB_PORT', '5432')) # e.g., 5432 for PostgreSQL, 3306 for MySQL\n\n# Local Bind Address (where the tunnel will be accessible on your local machine)\nLOCAL_BIND_HOST = os.environ.get('LOCAL_TUNNEL_HOST', '127.0.0.1')\nLOCAL_BIND_PORT = int(os.environ.get('LOCAL_TUNNEL_PORT', 'auto')) # 'auto' for a random available port\n\n# Example of connecting to a PostgreSQL database via an SSH tunnel\ntry:\n    with SSHTunnelForwarder(\n        (SSH_HOST, SSH_PORT),\n        ssh_username=SSH_USERNAME,\n        ssh_password=SSH_PASSWORD if SSH_PASSWORD else None,\n        ssh_pkey=SSH_PKEY if os.path.exists(os.path.expanduser(SSH_PKEY)) else None,\n        ssh_private_key_password=SSH_PKEY_PASSWD if SSH_PKEY_PASSWD else None,\n        remote_bind_address=(REMOTE_BIND_HOST, REMOTE_BIND_PORT),\n        local_bind_address=(LOCAL_BIND_HOST, LOCAL_BIND_PORT) if LOCAL_BIND_PORT != 'auto' else (LOCAL_BIND_HOST, 0)\n    ) as tunnel:\n        print(f\"SSH tunnel established. Local port: {tunnel.local_bind_port}\")\n        print(f\"Accessing {REMOTE_BIND_HOST}:{REMOTE_BIND_PORT} via localhost:{tunnel.local_bind_port}\")\n\n        # In a real application, you would now connect your database client to:\n        # host='127.0.0.1', port=tunnel.local_bind_port\n        # For demonstration, we just keep the tunnel open for a few seconds.\n        print(\"Tunnel active for 10 seconds. Press Ctrl+C to stop sooner.\")\n        time.sleep(10)\n        print(\"Tunnel closing.\")\n\nexcept Exception as e:\n    print(f\"An error occurred: {e}\")\n    print(\"Please check your SSH configuration, network connectivity, and credentials.\")","lang":"python","description":"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."},"warnings":[{"fix":"Review how tunnel threads interact with your application's lifecycle; `sshtunnel` now sets the daemon flag on tunnel threads.","message":"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.","severity":"breaking","affected_versions":">=0.4.0"},{"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.","message":"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.","severity":"breaking","affected_versions":">=0.3.0"},{"fix":"Double-check all address and port parameters. Enable `debug_level='TRACE'` in `SSHTunnelForwarder` for detailed logging during troubleshooting.","message":"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.","severity":"gotcha","affected_versions":"all"},{"fix":"Verify firewall rules (e.g., `iptables`, `ufw`, security groups) on all involved hosts to allow necessary traffic.","message":"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.","severity":"gotcha","affected_versions":"all"},{"fix":"For persistent issues, consider explicitly setting `ssh_server_alive_interval` in `SSHTunnelForwarder` or configuring `ServerAliveInterval` in your `~/.ssh/config` file.","message":"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.","severity":"gotcha","affected_versions":"all"}],"env_vars":null,"last_verified":"2026-05-12T18:11:25.730Z","next_check":"2026-06-26T00:00:00.000Z","problems":[],"ecosystem":"pypi","meta_description":null,"install_score":100,"install_tag":"verified","quickstart_score":0,"quickstart_tag":"stale","pypi_latest":"0.4.0","install_checks":{"last_tested":"2026-05-12","tag":"verified","tag_description":"installs cleanly on critical runtimes, fast import, recently tested","results":[{"runtime":"python:3.10-alpine","python_version":"3.10","os_libc":"alpine (musl)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":null,"import_time_s":0.62,"mem_mb":13.3,"disk_size":"42.0M"},{"runtime":"python:3.10-alpine","python_version":"3.10","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.66,"mem_mb":13.1,"disk_size":"41.0M"},{"runtime":"python:3.10-slim","python_version":"3.10","os_libc":"slim (glibc)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":3.3,"import_time_s":0.45,"mem_mb":13.3,"disk_size":"42M"},{"runtime":"python:3.10-slim","python_version":"3.10","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.44,"mem_mb":13.1,"disk_size":"41M"},{"runtime":"python:3.11-alpine","python_version":"3.11","os_libc":"alpine (musl)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":null,"import_time_s":0.84,"mem_mb":14.7,"disk_size":"44.7M"},{"runtime":"python:3.11-alpine","python_version":"3.11","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.96,"mem_mb":14.8,"disk_size":"43.8M"},{"runtime":"python:3.11-slim","python_version":"3.11","os_libc":"slim (glibc)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":3.2,"import_time_s":0.79,"mem_mb":14.7,"disk_size":"45M"},{"runtime":"python:3.11-slim","python_version":"3.11","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.71,"mem_mb":14.8,"disk_size":"44M"},{"runtime":"python:3.12-alpine","python_version":"3.12","os_libc":"alpine (musl)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":null,"import_time_s":1.05,"mem_mb":14.8,"disk_size":"36.4M"},{"runtime":"python:3.12-alpine","python_version":"3.12","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":1.1,"mem_mb":14.6,"disk_size":"35.5M"},{"runtime":"python:3.12-slim","python_version":"3.12","os_libc":"slim (glibc)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":2.9,"import_time_s":1.14,"mem_mb":14.8,"disk_size":"37M"},{"runtime":"python:3.12-slim","python_version":"3.12","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":1.06,"mem_mb":14.6,"disk_size":"36M"},{"runtime":"python:3.13-alpine","python_version":"3.13","os_libc":"alpine (musl)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":null,"import_time_s":1.08,"mem_mb":15.6,"disk_size":"36.1M"},{"runtime":"python:3.13-alpine","python_version":"3.13","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":1.1,"mem_mb":15.5,"disk_size":"35.1M"},{"runtime":"python:3.13-slim","python_version":"3.13","os_libc":"slim (glibc)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":2.9,"import_time_s":1.03,"mem_mb":15.6,"disk_size":"36M"},{"runtime":"python:3.13-slim","python_version":"3.13","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":1.08,"mem_mb":15.5,"disk_size":"35M"},{"runtime":"python:3.9-alpine","python_version":"3.9","os_libc":"alpine (musl)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":null,"import_time_s":0.57,"mem_mb":12.9,"disk_size":"42.2M"},{"runtime":"python:3.9-alpine","python_version":"3.9","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.59,"mem_mb":12.8,"disk_size":"41.3M"},{"runtime":"python:3.9-slim","python_version":"3.9","os_libc":"slim (glibc)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":4.1,"import_time_s":0.6,"mem_mb":12.9,"disk_size":"43M"},{"runtime":"python:3.9-slim","python_version":"3.9","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.51,"mem_mb":12.8,"disk_size":"42M"}]},"quickstart_checks":{"last_tested":"2026-04-24","tag":"stale","tag_description":"widespread failures or data too old to trust","results":[{"runtime":"python:3.10-alpine","exit_code":1},{"runtime":"python:3.10-slim","exit_code":1},{"runtime":"python:3.11-alpine","exit_code":1},{"runtime":"python:3.11-slim","exit_code":1},{"runtime":"python:3.12-alpine","exit_code":1},{"runtime":"python:3.12-slim","exit_code":1},{"runtime":"python:3.13-alpine","exit_code":1},{"runtime":"python:3.13-slim","exit_code":1},{"runtime":"python:3.9-alpine","exit_code":1},{"runtime":"python:3.9-slim","exit_code":1}]}}