SSHTUNNEL(1)
💅 NAME 💅
okay so welcome to SSHTUNNEL(1) — yes bestie, this is the sshtunnel manual page, section 1, we are HERE and we are slaying 💖. And the official name line? Literally just sshtunnel - sshtunnel Documentation. It's giving "the docs named itself," but honestly? Iconic. Periodt.
The badges, because we stan a decorated README:
[image: CircleCI] [image] <https://circleci.com/gh/pahaz/sshtunnel>[image: AppVeyor] [image] <https://ci.appveyor.com/project/pahaz/sshtunnel>[image: Documentation Status] [image] <http://sshtunnel.readthedocs.io/en/latest/?badge=latest>[image: coveralls] [image] <https://coveralls.io/github/pahaz/sshtunnel?branch=master>[image: version] [image] <https://pypi.python.org/pypi/sshtunnel>[image: pyversions] [image][image: license] [image] <https://github.com/pahaz/sshtunnel/blob/master/LICENSE>
Author: Pahaz <https://github.com/pahaz> 🫶
Repo: <https://github.com/pahaz/sshtunnel/>
Inspired by <https://github.com/jmagnusson/bgtunnel>, which doesn't work on Windows. 💀 (not me calling it out in the official docs, but here we are)
See also: <https://github.com/paramiko/paramiko/blob/master/demos/forward.py>
🦋 REQUIREMENTS 🦋
paramiko<http://www.paramiko.org/>
sshtunnel <https://pypi.python.org/pypi/sshtunnel> is on PyPI, so simply run:
pip install sshtunnel
or
easy_install sshtunnel
or
conda install -c conda-forge sshtunnel
to have it installed in your environment. three whole ways, pick your fighter ✨
For installing from source, clone the repo <https://github.com/pahaz/sshtunnel> and run:
python setup.py install
🧚 TESTING THE PACKAGE 🧚
In order to run the tests you first need tox <https://testrun.org/tox/latest/> and run:
python setup.py test
One of the typical scenarios where sshtunnel is helpful is depicted in the figure below. The user may need to connect a port of a remote server (i.e. 8080) where only the SSH port (usually port 22) is reachable. 👑
----------------------------------------------------------------------
|
-------------+ | +----------+
LOCAL | | | REMOTE | :22 SSH
CLIENT | <== SSH ========> | SERVER | :8080 web service
-------------+ | +----------+
|
FIREWALL (only port 22 is open)
----------------------------------------------------------------------
Fig1: How to connect to a service blocked by a firewall through SSH tunnel.
If allowed by the SSH server, it is also possible to reach a private server (from the perspective of REMOTE SERVER) not directly visible from the outside (LOCAL CLIENT's perspective). 🌈
----------------------------------------------------------------------
|
-------------+ | +----------+ +---------
LOCAL | | | REMOTE | | PRIVATE
CLIENT | <== SSH ========> | SERVER | <== local ==> | SERVER
-------------+ | +----------+ +---------
|
FIREWALL (only port 443 is open)
----------------------------------------------------------------------
Fig2: How to connect to PRIVATE SERVER through SSH tunnel.
The API allows either initializing the tunnel and starting it, or using a with context, which will take care of starting and stopping the tunnel: 🎀
🔥 EXAMPLE 1 🔥
Code corresponding to Fig1 above follows, given the remote server's address is pahaz.urfuclub.ru, password authentication, and a randomly assigned local bind port. (yes the prose says pahaz.urfuclub.ru and the code says alfa.8iq.dev — that's just how it is, don't @ me 🙈)
from sshtunnel import SSHTunnelForwarder
server = SSHTunnelForwarder(
'alfa.8iq.dev',
ssh_username="pahaz",
ssh_password="secret",
remote_bind_address=('127.0.0.1', 8080)
)
server.start()
print(server.local_bind_port) # show assigned local port
# work with `SECRET SERVICE` through `server.local_bind_port`.
server.stop()
💖 EXAMPLE 2 💖
Example of port forwarding to a private server that isn't directly reachable, assuming password-protected pkey authentication, the remote server's SSH service listening on port 443, and that port being open in the firewall (Fig2):
import paramiko
import sshtunnel
with sshtunnel.open_tunnel(
(REMOTE_SERVER_IP, 443),
ssh_username="",
ssh_pkey="/var/ssh/rsa_key",
ssh_private_key_password="secret",
remote_bind_address=(PRIVATE_SERVER_IP, 22),
local_bind_address=('0.0.0.0', 10022)
) as tunnel:
client = paramiko.SSHClient()
client.load_system_host_keys()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect('127.0.0.1', 10022)
# do some operations with client session
client.close()
print('FINISH!')
🌸 EXAMPLE 3 🌸
Example of port forwarding for the Vagrant MySQL local port:
from sshtunnel import open_tunnel
from time import sleep
with open_tunnel(
('localhost', 2222),
ssh_username="vagrant",
ssh_password="vagrant",
remote_bind_address=('127.0.0.1', 3306)
) as server:
print(server.local_bind_port)
while True:
# press Ctrl-C for stopping
sleep(1)
print('FINISH!')
Or simply using the CLI:
(bash)$ python -m sshtunnel -U vagrant -P vagrant -L :3306 -R 127.0.0.1:3306 -p 2222 localhost
⭐ EXAMPLE 4 ⭐
Opening an SSH session jumping over two tunnels. Heads up bestie: the SSH transport and tunnels will be daemonised, which will not wait for the connections to stop at close time. ⚠️
import sshtunnel
from paramiko import SSHClient
with sshtunnel.open_tunnel(
ssh_address_or_host=('GW1_ip', 20022),
remote_bind_address=('GW2_ip', 22),
) as tunnel1:
print('Connection to tunnel1 (GW1_ip:GW1_port) OK...')
with sshtunnel.open_tunnel(
ssh_address_or_host=('localhost', tunnel1.local_bind_port),
remote_bind_address=('target_ip', 22),
ssh_username='GW2_user',
ssh_password='GW2_pwd',
) as tunnel2:
print('Connection to tunnel2 (GW2_ip:GW2_port) OK...')
with SSHClient() as ssh:
ssh.connect('localhost',
port=tunnel2.local_bind_port,
username='target_user',
password='target_pwd',
)
ssh.exec_command(...)
And here's the whole --help flex, verbatim, every single flag, no cap:
$ sshtunnel --help
usage: sshtunnel [-h] [-U SSH_USERNAME] [-p SSH_PORT] [-P SSH_PASSWORD] -R
IP:PORT [IP:PORT ...] [-L [IP:PORT [IP:PORT ...]]]
[-k SSH_HOST_KEY] [-K KEY_FILE] [-S KEY_PASSWORD] [-t] [-v]
[-V] [-x IP:PORT] [-c SSH_CONFIG_FILE] [-z] [-n]
[-d [FOLDER [FOLDER ...]]]
ssh_address
Pure python ssh tunnel utils
Version 0.4.0
positional arguments:
ssh_address SSH server IP address (GW for SSH tunnels)
set with "-- ssh_address" if immediately after -R or -L
optional arguments:
-h, --help show this help message and exit
-U SSH_USERNAME, --username SSH_USERNAME
SSH server account username
-p SSH_PORT, --server_port SSH_PORT
SSH server TCP port (default: 22)
-P SSH_PASSWORD, --password SSH_PASSWORD
SSH server account password
-R IP:PORT [IP:PORT ...], --remote_bind_address IP:PORT [IP:PORT ...]
Remote bind address sequence: ip_1:port_1 ip_2:port_2 ... ip_n:port_n
Equivalent to ssh -Lxxxx:IP_ADDRESS:PORT
If port is omitted, defaults to 22.
Example: -R 10.10.10.10: 10.10.10.10:5900
-L [IP:PORT [IP:PORT ...]], --local_bind_address [IP:PORT [IP:PORT ...]]
Local bind address sequence: ip_1:port_1 ip_2:port_2 ... ip_n:port_n
Elements may also be valid UNIX socket domains:
/tmp/foo.sock /tmp/bar.sock ... /tmp/baz.sock
Equivalent to ssh -LPORT:xxxxxxxxx:xxxx, being the local IP address optional.
By default it will listen in all interfaces (0.0.0.0) and choose a random port.
Example: -L :40000
-k SSH_HOST_KEY, --ssh_host_key SSH_HOST_KEY
Gateway's host key
-K KEY_FILE, --private_key_file KEY_FILE
RSA/ECDSA private key file
-S KEY_PASSWORD, --private_key_password KEY_PASSWORD
RSA/ECDSA private key password
-t, --threaded Allow concurrent connections to each tunnel
-v, --verbose Increase output verbosity (default: ERROR)
-V, --version Show version number and quit
-x IP:PORT, --proxy IP:PORT
IP and port of SSH proxy to destination
-c SSH_CONFIG_FILE, --config SSH_CONFIG_FILE
SSH configuration file, defaults to ~/.ssh/config
-z, --compress Request server for compression over SSH transport
-n, --noagent Disable looking for keys from an SSH agent
-d [FOLDER [FOLDER ...]], --host_pkey_directories [FOLDER [FOLDER ...]]
List of directories where SSH pkeys (in the format `id_*`) may be found
okay so now the actual behaviour tea ☕ — sshtunnel initiates SSH tunnels via a remote gateway.
sshtunnel works by opening a port forwarding SSH connection in the background, using threads. 🧵
The connection(s) are closed when explicitly calling the SSHTunnelForwarder.stop() method, or by using it as a context.
Now the module-level constants and friends — these are the knobs, bestie, learn them 🎛️
sshtunnel.SSH_TIMEOUT = 0.1— Timeout (seconds) for transport socket (socket.settimeout)sshtunnel.TUNNEL_TIMEOUT = 10.0— Timeout (seconds) for tunnel connection (open_channeltimeout)sshtunnel.DEFAULT_LOGLEVEL = 40— default level if no logger passed (ERROR)sshtunnel.DEFAULT_SSH_DIRECTORY = '~/.ssh'— path of optional ssh configuration file
💜 sshtunnel.check_address(address) 💜
Check if the format of the address is correct. ✅
Arguments:
address(tuple):(str, int)representing an IP address and port, respectively.- > Note: alternatively a local address can be a
strwhen working with UNIX domain sockets, if supported by the platform.
- > Note: alternatively a local address can be a
Raises:
ValueError: raised when address has an incorrect format.
Example:
>>> check_address(('127.0.0.1', 22))
🧸 Functions & Classes, bestie
<point>okay so now we’re in the API reference and it’s giving “manual but make it cute.”</point> Every signature, default, note, and version note is still here — I would never do you dirty like that. 💖
✨ sshtunnel.check_addresses(address_list, is_remote=False)
This one is the vibe check for addresses. It checks if the format of the addresses is correct. That’s the whole assignment, and she ate.
sshtunnel.check_addresses(address_list, is_remote=False)
Arguments 🎀
address_list(list[tuple]) — a sequence of(str, int)pairs, each representing an IP address and port respectively.
Note: when supported by the platform, one or more of the elements in the list can be of type str, representing a valid UNIX domain socket.
is_remote(boolean) — the docs literally say “Whether or not the address list.” That’s the whole sentence. No cap, I’m not adding to it. 🫶
Raises 💀
AssertionError— raised whenaddress_listcontains an invalid element.ValueError— raised when any address in the list has an incorrect format.
Example 🌸
>>> check_addresses([('127.0.0.1', 22), ('127.0.0.1', 2222)])
🪄 sshtunnel.create_logger(logger=None, loglevel=None, capture_warnings=True, add_paramiko_handler=True)
Attach or create a new logger and add a console handler if not present. Logging era, honestly iconic.
sshtunnel.create_logger(logger=None, loglevel=None, capture_warnings=True, add_paramiko_handler=True)
Arguments 🦋
logger(Optional[logging.Logger]) — alogging.Loggerinstance (docs:https://docs.python.org/3/library/logging.html#logging.Logger); a new one is created if this argument is empty.loglevel(Optional[str or int]) — thelogging.Logger’s level, either as a string (i.e.ERROR) or in numeric format (10 == DEBUG).
Note: a value of 1 == TRACE enables Tracing mode.
capture_warnings(boolean) — enable/disable capturing the events logged by thewarningsmodule into logger’s handlers. Default:True.
Note: ignored in python 2.6.
add_paramiko_handler(boolean) — whether or not add a console handler forparamiko.transport’s logger if no handler present. Default:True.
Return 🌈
logging.Logger — docs at https://docs.python.org/3/library/logging.html#logging.Logger. She returns the logger, periodt.
💀 Exceptions that can pop off
exception sshtunnel.BaseSSHTunnelForwarderError(*args, **kwargs)— Exception raised bySSHTunnelForwardererrors.exception sshtunnel.HandlerSSHTunnelForwarderError(*args, **kwargs)— Exception for Tunnel forwarder errors.
👑 class sshtunnel.SSHTunnelForwarder
class sshtunnel.SSHTunnelForwarder(ssh_address_or_host=None, ssh_config_file='~/.ssh/config', 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, *args, **kwargs)
SSH tunnel class. Main character energy — she’s the whole point of the package.
- Initialize a SSH tunnel to a remote host according to the input arguments.
- Optionally:
- Read an SSH configuration file (typically
~/.ssh/config). - Load keys from a running SSH agent (i.e. Pageant, GNOME Keyring).
- Read an SSH configuration file (typically
Raises 😭
BaseSSHTunnelForwarderError— raised bySSHTunnelForwarderclass methods.HandlerSSHTunnelForwarderError— raised by tunnel forwarder threads.
Note: attributesmute_exceptionsandraise_exception_if_any_forwarder_have_a_problem(deprecated) may be used to silence most exceptions raised from this class.
Keyword Arguments 🎀
ssh_address_or_host(tupleorstr) — IP or hostname of REMOTE GATEWAY. It may be a two-element tuple(str, int)representing IP and port respectively, or astrrepresenting the IP address only. Added in version0.0.4.ssh_config_file(str) — SSH configuration file that will be read. If explicitly set toNone, parsing of this configuration is omitted. Default:SSH_CONFIG_FILE. Added in version0.0.4.ssh_host_key(str) — representation of a line in an OpenSSH-style “known hosts” file. REMOTE GATEWAY’s key fingerprint will be compared to this host key in order to prevent against SSH server spoofing. Important when using passwords in order not to accidentally do a login attempt to a wrong (perhaps an attacker’s) machine. 🚨ssh_username(str) — username to authenticate as in REMOTE SERVER. Default: current local user name.ssh_password(str) — text representing the password used to connect to REMOTE SERVER or for unlocking a private key.
Note: avoid coding secret password directly in the code, since this may be visible and make your service vulnerable to attacks. fr fr, do not do it.
ssh_port(int) — optional port number of the SSH service on REMOTE GATEWAY, whenssh_address_or_hostis astrrepresenting the IP part of REMOTE GATEWAY’s address. Default:22.ssh_pkey(strorparamiko.PKey) — private key file name (str) to obtain the public key from or a public key (paramiko.pkey.PKey, docs:https://docs.paramiko.org/en/latest/api/keys.html#paramiko.pkey.PKey).ssh_private_key_password(str) — password for an encryptedssh_pkey.
Note: avoid coding secret password directly in the code, since this may be visible and make your service vulnerable to attacks. same warning, same energy.
ssh_proxy(socket-like objectortuple) — proxy where all SSH traffic will be passed through. It might be for example aparamiko.proxy.ProxyCommandinstance (docs:https://docs.paramiko.org/en/latest/api/proxy.html#paramiko.proxy.ProxyCommand). See either theparamiko.transport.Transport’ssockparameter documentation (docs:https://docs.paramiko.org/en/latest/api/transport.html#paramiko.transport.Transport) orProxyCommandinssh_config(5)for more information. It is also possible to specify the proxy address as a tuple of type(str, int)representing proxy’s IP and port.
Note: ignored ifssh_proxy_enabledisFalse. Added in version0.0.5.
ssh_proxy_enabled(boolean) — enable/disable SSH proxy. IfTrueand user’sssh_config_filecontains aProxyCommanddirective that matches the specifiedssh_address_or_host, aparamiko.proxy.ProxyCommandobject will be created where all SSH traffic will be passed through. Default:True. Added in version0.0.4.local_bind_address(tuple) — local tuple in the format(str, int)representing the IP and port of the local side of the tunnel. Both elements in the tuple are optional so both('', 8000)and('10.0.0.1', )are valid values. Default:('0.0.0.0', RANDOM_PORT). Changed in version0.0.8: added the ability to use a UNIX domain socket as local bind address.local_bind_addresses(list[tuple]) — in case more than one tunnel is established at once, a list of tuples (in the same format aslocal_bind_address) can be specified, such as[(ip1, port_1), (ip_2, port2), ...]. Default:[local_bind_address]. Added in version0.0.4.remote_bind_address(tuple) — remote tuple in the format(str, int)representing the IP and port of the remote side of the tunnel.remote_bind_addresses(list[tuple]) — in case more than one tunnel is established at once, a list of tuples (in the same format asremote_bind_address) can be specified, such as[(ip1, port_1), (ip_2, port2), ...]. Default:[remote_bind_address]. Added in version0.0.4.allow_agent(boolean) — enable/disable load of keys from an SSH agent. Default:True. Added in version0.0.8.host_pkey_directories(list) — look for pkeys in folders on this list, for example['~/.ssh']. Default:None(disabled). Added in version0.1.4.compression(boolean) — turn on/off transport compression. By default compression is disabled since it may negatively affect interactive sessions. Default:False. Added in version0.0.8.logger(logging.Logger) — logging instance forsshtunnelandparamiko. Default:logging.Loggerinstance with a singlelogging.StreamHandlerhandler (docs:https://docs.python.org/3/library/logging.handlers.html#logging.StreamHandler) andDEFAULT_LOGLEVELlevel. Added in version0.0.3.mute_exceptions(boolean) — allow silencingBaseSSHTunnelForwarderErrororHandlerSSHTunnelForwarderErrorexceptions when enabled. Default:False.
and the keyword args keep going in the next part, because she is not done spilling. 💅
Added in version 0.0.8.
set_keepalive(float) — Interval in seconds defining the period in which, if no data was sent over the connection, a'keepalive'packet will be sent (and ignored by the remote host). This can be useful to keep connections alive over a NAT. You can set to0.0for disable keepalive.- Default:
5.0(no keepalive packets are sent) - Added in version
0.0.7.
- Default:
threaded(boolean) — Allow concurrent connections over a single tunnel 💅- Default:
True - Added in version
0.0.3.
- Default:
ssh_address(str) — Superseded byssh_address_or_host, tuple of type(str, int)representing the IP and port of REMOTE SERVER- Deprecated since version
0.0.4.
- Deprecated since version
ssh_host(str) — Superseded byssh_address_or_host, tuple of type(str, int)representing the IP and port of REMOTE SERVER- Deprecated since version
0.0.4.
- Deprecated since version
ssh_private_key(str orparamiko.PKey) — Superseded byssh_pkey, which can represent either a private key file name (str) or a public key (paramiko.pkey.PKeyhttps://docs.paramiko.org/en/latest/api/keys.html#paramiko.pkey.PKey)- Deprecated since version
0.0.8.
- Deprecated since version
raise_exception_if_any_forwarder_have_a_problem(boolean) — Allow silencingBaseSSHTunnelForwarderErrororHandlerSSHTunnelForwarderErrorexceptions when set toFalse- Default:
True - Added in version
0.0.4. - Deprecated since version
0.0.8: (usemute_exceptionsinstead)
- Default:
💎 Attributes
tunnel_is_up(dict) — Describe whether or not the other side of the tunnel was reported to be up (and we must close it) or not (skip shutting down that tunnel)
Note: This attribute should not be modified 🙈
Note: Whenskip_tunnel_checkupis disabled or the local bind is a UNIX socket, the value will always beTrue
Example, bestie — look at it, it's literally a mood board of tunnels:
{('127.0.0.1', 55550): True, # this tunnel is up
('127.0.0.1', 55551): False} # this one isn't
where 55550 and 55551 are the local bind ports ✨
skip_tunnel_checkup(boolean) — Disable tunnel checkup (default for backwards compatibility).- Added in version
0.1.0.
- Added in version
daemon_forward_servers = True
flag tunnel threads in daemon mode 🧚
daemon_transport = True
flag SSH transport thread in daemon mode 🧚
local_is_up(target)
Check if a tunnel is up (remote target's host is reachable on TCP target's port)
- Arguments:
- target (tuple): tuple of type
(str, int)indicating the listen IP address and port
- target (tuple): tuple of type
- Return:
boolean - Deprecated since version
0.1.0: Replaced bycheck_tunnels()andtunnel_is_up
check_tunnels()
Check that if all tunnels are established and populates tunnel_is_up ⭐
static get_agent_keys(logger=None)
Load public keys from any available SSH agent
- Arguments:
- logger (
Optional[logging.Logger])
- logger (
- Return:
list
static get_keys(logger=None, host_pkey_directories=None, allow_agent=False)
Load public keys from any available SSH agent or local .ssh directory.
- Arguments:
- logger (
Optional[logging.Logger]) - host_pkey_directories (
Optional[list[str]]): List of local directories where host SSH pkeys in the format"id_*"are searched. For example,['~/.ssh']- Added in version
0.1.0.
- Added in version
- allow_agent (
Optional[boolean]): Whether or not load keys from agent- Default:
False
- Default:
- logger (
- Return:
list
static read_private_key_file(pkey_file, pkey_password=None, key_type=None, logger=None)
Get SSH Public key from a private key file, given an optional password 🔑
- Arguments:
- pkey_file (str): File containing a private key (RSA or ECDSA)
- Keyword Arguments:
- pkey_password (
Optional[str]): Password to decrypt the private key - logger (
Optional[logging.Logger])
- pkey_password (
- Return:
paramiko.Pkey
start()
Start the SSH tunnels 🚀
stop(force=False)
Shut the tunnel down. By default we are always waiting until closing all connections. You can use force=True to force close connections
- Keyword Arguments:
- force (bool): Force close current connections
- Default:
False - Added in version
0.2.2.
- Default:
- force (bool): Force close current connections
Note: This had to be handled with care before0.1.0, and the way that whole saga went is iconic in the worst way: - if a port redirection is opened - the destination is not reachable - we attempt a connection to that tunnel (SYN is sent and acknowledged, then a FIN packet is sent and never acknowledged... weird) - we try to shutdown: it will not succeed untilFIN_WAIT_2andCLOSE_WAITtime out.
Note: Handle these scenarios withtunnel_is_up: ifFalse, servershutdown()will be skipped on that tunnel
close()
Stop the an active tunnel, alias to stop() 💀
restart()
Restart connection to the gateway and tunnels 🔁
property local_bind_ports
Return a list containing the ports of local side of the TCP tunnels
property local_bind_hosts
Return a list containing the IP addresses listening for the tunnels
property local_bind_addresses
Return a list of (IP, port) pairs for the local side of the tunnels
property tunnel_bindings
Return a dictionary containing the active local<>remote tunnel_bindings
property is_active
Return True if the underlying SSH transport is up
🎀 sshtunnel.open_tunnel(*args, **kwargs)
Open an SSH Tunnel, wrapper for SSHTunnelForwarder — it's giving convenience-function main character energy 👑
Arguments:
- destination (
Optional[tuple]): SSH server's IP address and port in the format(ssh_address, ssh_port)
Keyword Arguments:
- debug_level (
Optional[int or str]): log level forlogging.Loggerhttps://docs.python.org/3/library/logging.html#logging.Loggerinstance, i.e.DEBUG - skip_tunnel_checkup (boolean): Enable/disable the local side check and populate
tunnel_is_up- Default:
True - Added in version
0.1.0.
- Default:
Note: A value ofdebug_levelset to1 == TRACEenables tracing mode
Note: See SSHTunnelForwarder for keyword arguments
Example:
from sshtunnel import open_tunnel
with open_tunnel(SERVER,
ssh_username=SSH_USER,
ssh_port=22,
ssh_password=SSH_PASSWORD,
remote_bind_address=(REMOTE_HOST, REMOTE_PORT),
local_bind_address=('', LOCAL_PORT)) as server:
def do_something(port):
pass
print("LOCAL PORTS:", server.local_bind_port)
do_something(server.local_bind_port)
okay and now the shoutouts, because this thing was not built by one person and we're gonna name every single one of them 💖🫶
- Cameron Maske —
https://github.com/cameronmaske - Gustavo Machado —
https://github.com/gdmachado - Colin Jermain —
https://github.com/cjermain - JM Fernández —
https://github.com/fernandezcuesta— (big thanks!) - Lewis Thompson —
https://github.com/lewisthompson - Erik Rogers —
https://github.com/ewrogers - Mart Sõmermaa —
https://github.com/mrts - Chronial —
https://github.com/Chronial - Dan Harbin —
https://github.com/RasterBurn - Ignacio Peluffo —
https://github.com/ipeluffo - Niels Zeilemaker —
https://github.com/NielsZeilemaker - Georgy Rylov —
https://github.com/g0djan - Eddie Chiang —
https://github.com/eddie-chiang - kkrasovskii —
https://github.com/kkrasovskii
and here's the receipts, aka the version history, no cap 🧾
v.0.4.0—Pahaz(https://github.com/pahaz)- Change the daemon mod flag for all tunnel threads (is not fully backward compatible) to prevent unexpected hangs (
#219—https://github.com/pahaz/sshtunnel/issues/219) - Add docker based end to end functinal tests for Mongo/Postgres/MySQL (
#219—https://github.com/pahaz/sshtunnel/issues/219) - Add docker based end to end hangs tests (
#219—https://github.com/pahaz/sshtunnel/issues/219)
- Change the daemon mod flag for all tunnel threads (is not fully backward compatible) to prevent unexpected hangs (
v.0.3.2—Pahaz(https://github.com/pahaz),JM Fernández(https://github.com/fernandezcuesta)- Fix host key directory detection
- Unify default ssh config folder to
~/.ssh
v.0.3.1—Pahaz(https://github.com/pahaz)- Increase open connection timeout to 10 secods
v.0.3.0—Pahaz(https://github.com/pahaz)- Change default with context behavior to use
.stop(force=True)on exit (is not fully backward compatible)
- Change default with context behavior to use
- Removed the useless
daemon_forward_servers = Truehack that was hanging around for hangs prevention — heads up bestie, this one is not fully backward compatible 💀 - Transport keepalive is now 5 seconds by default (and it's disabled for versions
< 0.3.0) - Default transport timeout is now
0.1 block_on_closeis deprecated and removed — she's gone, no cap- Fixed the "deadlocks" / "tunneling hangs" situation (issues
#173—https://github.com/pahaz/sshtunnel/issues/173,#201—https://github.com/pahaz/sshtunnel/issues/201,#162—https://github.com/pahaz/sshtunnel/issues/162,#211—https://github.com/pahaz/sshtunnel/issues/211) — iconic fix fr fr 🔥
🐛 v.0.2.2 — Pahaz (https://github.com/pahaz)
- Added
.stop(force=True)so you can force close active connections (#201—https://github.com/pahaz/sshtunnel/issues/201) 💅
🧹 v.0.2.1 — Pahaz (https://github.com/pahaz), Eddie Chiang (https://github.com/eddie-chiang) and kkrasovskii (https://github.com/kkrasovskii)
- Fixes the bug with an orphan thread for a tunnel that is DOWN (
#170—https://github.com/pahaz/sshtunnel/issues/170) 🫶
🌈 v.0.2.0 — Georgy Rylov (https://github.com/g0djan)
- IPv6 works without a proxy command now! It uses the built-in paramiko create socket logic. That logic tries the ipv6 socket family first, then the ipv4 socket family ✨
🎀 v.0.1.5 — JM Fernández (https://github.com/fernandezcuesta)
- Introduced the
block_on_closeattribute 🦋
🔑 v.0.1.4 — Niels Zeilemaker (https://github.com/NielsZeilemaker)
- Allows loading pkeys from
~/.ssh— we love to see it 💖
🛠️ v.0.1.3 — Ignacio Peluffo (https://github.com/ipeluffo) and others
pkey_fileparameter now accepts relative paths to the user folder using~- Several bugfixes 🧚
🩹 v.0.1.2 — JM Fernández (https://github.com/fernandezcuesta)
- Fix
#77— that's it, that's the tweet 💀
🩹 v.0.1.1 — JM Fernández (https://github.com/fernandezcuesta)
- Fix
#72
👑 v.0.1.0 — JM Fernández (https://github.com/fernandezcuesta)
- Added the
tunnel_bindingsproperty - Several bugfixes (
#49,#56,#57,#59,#60,#62,#64,#66, ...) — Pahaz (https://github.com/pahaz), JM Fernández (https://github.com/fernandezcuesta) - Added the
TRACElogging level — JM Fernández (https://github.com/fernandezcuesta) - Code and tests refactoring — JM Fernández (
https://github.com/fernandezcuesta) - Dropped
python3.2support 😭
🐍 v.0.0.8 — JM Fernández (https://github.com/fernandezcuesta)
- Merged
#31—https://github.com/pahaz/sshtunnel/issues/31: support Unix domain socket (local) forwarding — Dan Harbin (https://github.com/RasterBurn) - Simplified the API — JM Fernández (
https://github.com/fernandezcuesta) - Added sphinx-based documentation — JM Fernández (
https://github.com/fernandezcuesta) - Added
allow_agent(fixes#36—https://github.com/pahaz/sshtunnel/issues/36,#46—https://github.com/pahaz/sshtunnel/issues/46) — JM Fernández (https://github.com/fernandezcuesta) - Added
compression— JM Fernández (https://github.com/fernandezcuesta) - Added
__str__method — JM Fernández (https://github.com/fernandezcuesta) - Added test functions — JM Fernández (
https://github.com/fernandezcuesta) - Fixed default username when not provided and the
ssh_configfile is skipped — JM Fernández (https://github.com/fernandezcuesta) - Fixed gateway IP unresolvable exception catching — JM Fernández (
https://github.com/fernandezcuesta) - Minor fixes — JM Fernández (
https://github.com/fernandezcuesta) - Added AppVeyor support — JM Fernández (
https://github.com/fernandezcuesta)
🔁 v.0.0.7 — JM Fernández (https://github.com/fernandezcuesta)
- Tunnels can now be stopped and started safely (
#41—https://github.com/pahaz/sshtunnel/issues/41) — JM Fernández (https://github.com/fernandezcuesta) - Added timeout to SSH gateway and keep-alive messages (
#29—https://github.com/pahaz/sshtunnel/issues/29) — JM Fernández (https://github.com/fernandezcuesta) - Allowed sending a pkey directly (
#43—https://github.com/pahaz/sshtunnel/issues/43) — Chronial (https://github.com/Chronial) - Added
-VCLI option to show the current version — JM Fernández (https://github.com/fernandezcuesta) - Added coverage — JM Fernández (
https://github.com/fernandezcuesta) - Refactoring — JM Fernández (
https://github.com/fernandezcuesta)
🔐 v.0.0.6 — Pahaz (https://github.com/pahaz)
- Added
-SCLI options for ssh private key password support — Pahaz (https://github.com/pahaz)
🧵 v.0.0.5 — Pahaz (https://github.com/pahaz)
- Added
ssh_proxyargument, as well asssh_config(5)ProxyCommandsupport — Lewis Thompson (https://github.com/lewisthompson) - Added some python 2.6 compatibility fixes — Mart Sõmermaa (
https://github.com/mrts) paramiko.transportinherits handlers of loggers passed toSSHTunnelForwarder— JM Fernández (https://github.com/fernandezcuesta)- Fixed
#34—https://github.com/pahaz/sshtunnel/issues/34,#33—https://github.com/pahaz/sshtunnel/issues/33, plus code style and docs — JM Fernández (https://github.com/fernandezcuesta) - Added tests — Pahaz (
https://github.com/pahaz) - Added CI integration — Pahaz (
https://github.com/pahaz) - Normal packaging — Pahaz (
https://github.com/pahaz) - Disabled check of the distenation socket connection by
SSHTunnelForwarder.local_is_up— Pahaz (https://github.com/pahaz) — [changed default behavior] - Use daemon mode =
Falsein all threads by default; detailhttps://github.com/pahaz/sshtunnel/commit/64af238b799b0e0057c4f9b386cda247e0006da9#diff-76bc1662a114401c2954deb92b740081R127— Pahaz (https://github.com/pahaz) — [changed default behavior]
🙈 v.0.0.4.4 — Pahaz (https://github.com/pahaz)
- Fixed issue
#24—https://github.com/pahaz/sshtunnel/issues/24— hide ssh password in logs — Pahaz (https://github.com/pahaz)
🔌 v.0.0.4.3 — Pahaz (https://github.com/pahaz)
- Fixed default port issue
#19—https://github.com/pahaz/sshtunnel/issues/19— Pahaz (https://github.com/pahaz)
🧵 v.0.0.4.2 — Pahaz (https://github.com/pahaz)
- Fixed
Thread.daemonmode for Python < 3.3 —#16—https://github.com/pahaz/sshtunnel/issues/16,#21—https://github.com/pahaz/sshtunnel/issues/21— Lewis Thompson (https://github.com/lewisthompson), Erik Rogers (https://github.com/ewrogers)
🩹 v.0.0.4.1 — Pahaz (https://github.com/pahaz)
- Fixed CLI issues
#13—https://github.com/pahaz/sshtunnel/issues/13— Pahaz (https://github.com/pahaz)
💥 v.0.0.4 — Pahaz (https://github.com/pahaz)
- Daemon mode by default for all threads — JM Fernández (
https://github.com/fernandezcuesta), Pahaz (https://github.com/pahaz) — incompatible - Moved
make_ssh_forward_servertoSSHTunnelForwarder.make_ssh_forward_server— Pahaz (https://github.com/pahaz), JM Fernández (https://github.com/fernandezcuesta) — incompatible - Moved
make_ssh_forward_handlertoSSHTunnelForwarder.make_ssh_forward_handler_class— Pahaz (https://github.com/pahaz), JM Fernández (https://github.com/fernandezcuesta) — incompatible - Renamed
opentoopen_tunnel— JM Fernández (https://github.com/fernandezcuesta) — incompatible - Added CLI interface — JM Fernández (
https://github.com/fernandezcuesta) - Support opening several tunnels at once — JM Fernández (
https://github.com/fernandezcuesta) - Improved stability and readability — JM Fernández (
https://github.com/fernandezcuesta), Pahaz (https://github.com/pahaz) - Improved logging — JM Fernández (
https://github.com/fernandezcuesta), Pahaz (https://github.com/pahaz) - Added
raise_exception_if_any_forwarder_have_a_problemargument for opening several tunnels at once — Pahaz (https://github.com/pahaz) - Added
ssh_config_fileargument support — JM Fernández (https://github.com/fernandezcuesta) - Added Python 3 support — JM Fernández (
https://github.com/fernandezcuesta), Pahaz (https://github.com/pahaz)
🧵 v.0.0.3 — Pahaz (https://github.com/pahaz)
- Added
threadedoption — Cameron Maske (https://github.com/cameronmaske) - Fixed exception error message, correctly printing the destination address — Gustavo Machado (
https://github.com/gdmachado) - Fixed pip install failure — Colin Jermain (
https://github.com/cjermain), Pahaz (https://github.com/pahaz)
🌱 v.0.0.1 — Pahaz (https://github.com/pahaz)
SSHTunnelForwarderclass — Pahaz (https://github.com/pahaz)openfunction — Pahaz (https://github.com/pahaz)
Copyright (c) 2014-2019 Pahaz White
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
okay the legalese is still rolling out, keep scrolling bestie 🫶
okay so this last bit is the legal part and it is not the moment to get cute with the wording, bestie — warranties and liability clauses are load-bearing, so here they are exactly as written, no edits, no vibes-based paraphrasing 💅
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
fr fr read that one: "AS IS", no warranty of any kind, express or implied, and the authors and copyright holders are not liable for any claim, damages or other liability — whether in an action of contract, tort or otherwise — arising from, out of, or in connection with the software or the use or other dealings in the software. that's the deal, no cap 🙈
👑 AUTHOR
Pahaz White — yes, that's the whole author line, we stan a single-name credit 🫶
🎀 COPYRIGHT
2014-2020, Pahaz White and contributors
like, a whole decade-plus of contributors showing up and keeping this thing alive — iconic behavior, honestly 🌈
and that's the whole page, bestie!! you read all of it, every flag and every example, and you ate 💅✨ this copy of sshtunnel is from the sshtunnel source, version 0.4.0, dated Dec 24, 2025 — screenshot the date if you're citing it, iykyk 📸 go build a tunnel, be the main character, and remember the clean-up callback exists for a reason 🦋🎀💖
📦 where this page comes from
- arch rolling: python-sshtunnel 0.4.0-9 · MIT
- 💖 girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms