Skip to main content

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.

Project description

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>] \
    [--local-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
--local-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 --local-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.

Project details


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.0a1.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.0a1-py2.py3-none-any.whl (10.3 kB view details)

Uploaded Python 2Python 3

File details

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

File metadata

  • Download URL: sshpushpull-0.1.0a1.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.0a1.tar.gz
Algorithm Hash digest
SHA256 d6f905bfb98949c4e89bfd0292b2a2b0940b50d4ab6496578a10b3b284cf9340
MD5 6b8b3e2b799527b561776915de49defb
BLAKE2b-256 07330682610ad629521175b08f95271e5ee286674365c388b67d2779a547f5aa

See more details on using hashes here.

File details

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

File metadata

  • Download URL: sshpushpull-0.1.0a1-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.0a1-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 f2ef65e7c1d8b1e487cd7ee68c2bc73e9c5144d0bea60c6219bce4a4bd285523
MD5 1637a733f3f78a597a5e636a9b95c1ee
BLAKE2b-256 45cae7d893da2354f7871fb8b1b1a6ae49ff60611baba6e486050215d64816f2

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