Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

Dead simple, jargon-free Python tool to make a local TCP port available on a remote host or make a remote TCP port available locally, with auto-reconnect.

This is a Python reimplementation of push-pull-port that uses paramiko instead of shelling out to autossh, making it cross-platform and self-contained.

Motivation

Modern work is mobile. Whether you're at home, in a cafe, or on the move with 4G, you need secure, on-demand access to your devices and services - without wrestling with complex forwarding rules or VPNs.

sshpushpull lets you instantly "push" or "pull" any TCP port via SSH with human-friendly commands.

  • Expose your device's SSH, VNC, or web apps at a moment's notice.
  • Stop and start tunnels with a single command.
  • Failures show up immediately - no silent, sneaky background errors.
  • Auto-reconnects after transient network drops with exponential backoff.

It's the Unix philosophy, everywhere: portable, composable, and under your control.

Prerequisites

  • Python 2 or Python 3
  • paramiko (installed automatically via pip install sshpushpull)
  • SSH access to the remote host
  • For unattended tunnels, set up SSH key-based authentication so you're not prompted for a password on every reconnect.

Note: When using push, to make the pushed port accessible from other hosts, ensure the remote SSH server has GatewayPorts clientspecified set in /etc/ssh/sshd_config.

Installation

pip install sshpushpull

Usage

sshpushpull provides two subcommands:

  • push — Make a local TCP port available on a remote host (equivalent to ssh -R)
  • pull — Make a remote TCP port available locally (equivalent to ssh -L)

Both commands run in the foreground and auto-reconnect after transient network failures with exponential backoff (up to 10 seconds).

Push: Expose a local port on a remote host

sshpushpull push \
    --local-port <local_port> \
    --remote-port <remote_port> \
    --username <user> \
    --host <host> \
    [--port <ssh_port>] \
    [--password <pwd> | --rsa-key <path> | --ed25519-key <path>] \
    [--localhost-only]
Option Description
--local-port Local TCP port to push from
--remote-port Remote TCP port to push to
--username SSH username on the remote host
--host Remote SSH host name or address
--port SSH server port (default: 22)
--password Password for SSH authentication
--rsa-key Path to RSA private key for SSH authentication
--ed25519-key Path to Ed25519 private key for SSH authentication
--localhost-only Open remote port on localhost only (default: open on all interfaces)

Examples:

# Make local port 3000 available on dev.example.com:3001 using password auth
sshpushpull push --local-port 3000 --remote-port 3001 --username dev --host dev.example.com --password secret

# Using an Ed25519 key
sshpushpull push --local-port 3000 --remote-port 3001 --username dev --host dev.example.com --ed25519-key ~/.ssh/id_ed25519

# If SSH is running on port 2222
sshpushpull push --local-port 3000 --remote-port 3001 --username dev --host dev.example.com --port 2222 --rsa-key ~/.ssh/id_rsa

# Only allow access from the remote host's own localhost
sshpushpull push --local-port 3000 --remote-port 3001 --username dev --host dev.example.com --ed25519-key ~/.ssh/id_ed25519 --localhost-only

Pull: Access a remote port locally

sshpushpull pull \
    --remote-port <remote_port> \
    --local-port <local_port> \
    --username <user> \
    --host <host> \
    [--port <ssh_port>] \
    [--password <pwd> | --rsa-key <path> | --ed25519-key <path>]
Option Description
--remote-port Remote TCP port to pull from
--local-port Local TCP port to pull to
--username SSH username on the remote host
--host Remote SSH host name or address
--port SSH server port (default: 22)
--password Password for SSH authentication
--rsa-key Path to RSA private key for SSH authentication
--ed25519-key Path to Ed25519 private key for SSH authentication

Examples:

# Access remote port 3306 (database) through local port 3307
sshpushpull pull --remote-port 3306 --local-port 3307 --username admin --host db.internal --password secret

# Using an Ed25519 key
sshpushpull pull --remote-port 3306 --local-port 3307 --username admin --host db.internal --ed25519-key ~/.ssh/id_ed25519

# If SSH is running on port 2222
sshpushpull pull --remote-port 3306 --local-port 3307 --username admin --host db.internal --port 2222 --rsa-key ~/.ssh/id_rsa

Foreground Operation: Visibility Over Stealth

We intentionally run all tunnels in the foreground. This ensures:

  • Immediate Error Visibility: Any connection issues, authentication failures, or port conflicts are clearly printed to your terminal, so you can respond and debug without guessing.
  • No Silent Failures: By avoiding background daemons, you won't miss subtle (or catastrophic) tunnel dropouts that go unnoticed.
  • Stopping the tunnel: Simply press Ctrl-C to stop the tunnel. You can also close the terminal window/tab to end it.

Tip: If you ever want to run the tunnel in the background, you can use a terminal multiplexer like tmux to keep tunnels running while detached.

Auto-Reconnect

Both push and pull automatically reconnect after transient network failures. The reconnect strategy uses exponential backoff:

  1. First retry: 1 second
  2. Second retry: 2 seconds
  3. Third retry: 4 seconds
  4. ...up to a maximum of 10 seconds

After a successful reconnection, the backoff resets to 1 second.

Why We Use Push/Pull Instead of Forward/Reverse

The Problem with Traditional Terms

The standard SSH port forwarding terms (local forwarding vs remote forwarding) are notoriously confusing because:

  1. Perspective Dependence
    The "remote" and "local" labels depend on which machine initiates the SSH connection, not the actual service exposure direction users care about.

  2. Cognitive Mismatch
    When developers want to:

    • Expose a local service remotely, they must remember this is called "remote forwarding" (-R)
    • Access a remote service locally, this is called "local forwarding" (-L)
  3. Implementation-First Naming
    The terms describe SSH's technical implementation rather than user intent.

Our Push/Pull Metaphor

We intentionally avoid forward/reverse terminology in favor of intuitive action verbs:

User Goal Traditional Term Our Term SSH Option
Make local service available on remote host Remote Forwarding Push ssh -R
Access remote service through local port Local Forwarding Pull ssh -L

Key Advantages

  1. Intent-Oriented

    • push: "I want to make this local port available there"
    • pull: "I want to access that remote port here"
  2. Directionally Clear
    Eliminates ambiguity about "whose local/remote" we're referring to.

  3. Cloud-Native Alignment
    Matches modern service mesh concepts (ingress/egress) better than SSH's 1990s perspective.

Contributing

Contributions are welcome! Please submit pull requests or open issues on the GitHub repository.

License

This project is licensed under the MIT License.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sshpushpull-0.1.0a2.tar.gz (10.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sshpushpull-0.1.0a2-py2.py3-none-any.whl (10.3 kB view details)

Uploaded Python 2Python 3

File details

Details for the file sshpushpull-0.1.0a2.tar.gz.

File metadata

  • Download URL: sshpushpull-0.1.0a2.tar.gz
  • Upload date:
  • Size: 10.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.7

File hashes

Hashes for sshpushpull-0.1.0a2.tar.gz
Algorithm Hash digest
SHA256 7ea754025a74a01cd711963021e5d467a9740680f72471b8a4eb543cf9592c5e
MD5 c9692b6cd70fcc3de18b31656864370f
BLAKE2b-256 95c2015b3a5d2ef1682c41da9b9264e323a2675757803b4f341cc083d7578cfc

See more details on using hashes here.

File details

Details for the file sshpushpull-0.1.0a2-py2.py3-none-any.whl.

File metadata

  • Download URL: sshpushpull-0.1.0a2-py2.py3-none-any.whl
  • Upload date:
  • Size: 10.3 kB
  • Tags: Python 2, Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.7

File hashes

Hashes for sshpushpull-0.1.0a2-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 7dfb1c80ebe0cfed1d5ee1f37c803512e5bdf7d8aa4311b0eae2a690d89713eb
MD5 44473200146db85af7ed2f109326b8e4
BLAKE2b-256 c0b4b4cd461ddbd9bc80c3260d4e5e5535cbd471ffbf857ffd910000a724c836

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page