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 (repeatable for multiple ports)
--remote-port Remote TCP port to push to (repeatable for multiple ports)
--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

# Push multiple ports at once
sshpushpull push \
    --host dev.example.com \
    --username dev \
    --ed25519-key ~/.ssh/id_ed25519 \
    --local-port 3000 --remote-port 3001 \
    --local-port 4000 --remote-port 4001 \
    --local-port 8080 --remote-port 9090

# 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 (repeatable for multiple ports)
--local-port Local TCP port to pull to (repeatable for multiple ports)
--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

# Pull multiple ports at once
sshpushpull pull \
    --host db.internal \
    --username admin \
    --password secret \
    --local-port 3307 --remote-port 3306 \
    --local-port 5433 --remote-port 5432

# 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

Multiple Ports

Both push and pull support forwarding multiple ports over a single SSH connection. Simply repeat --local-port and --remote-port in matched pairs:

# Push: expose three local services on the remote host
sshpushpull push \
    --host dev.example.com \
    --username dev \
    --ed25519-key ~/.ssh/id_ed25519 \
    --local-port 3000 --remote-port 3001 \
    --local-port 4000 --remote-port 4001 \
    --local-port 8080 --remote-port 9090

# Pull: access remote database and cache locally
sshpushpull pull \
    --host db.internal \
    --username admin \
    --password secret \
    --local-port 3307 --remote-port 3306 \
    --local-port 6380 --remote-port 6379

Ports are paired by position: the first --local-port with the first --remote-port, the second with the second, and so on. All mappings share a single SSH connection and reconnect together.

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.0a3.tar.gz (10.7 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.0a3-py2.py3-none-any.whl (11.0 kB view details)

Uploaded Python 2Python 3

File details

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

File metadata

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

File hashes

Hashes for sshpushpull-0.1.0a3.tar.gz
Algorithm Hash digest
SHA256 47cb85fe1b2e9e2d1c1df19b7ccad1fcfb9fb8838d6e116ade0d232f9accb3ae
MD5 11703a728310c63019c0768e19e05806
BLAKE2b-256 6a93d47d9609b733564922bc11beff83f1184c58f3f4f2ded01dd7d1f17088db

See more details on using hashes here.

File details

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

File metadata

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

File hashes

Hashes for sshpushpull-0.1.0a3-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 aadb339b9dcdec948dcd7ec863807772f2ee5627f7cc12696f954920528efa5a
MD5 9ceff7bc761d12a875f47944addbea8c
BLAKE2b-256 3db394e409d221fdde010dc75b5d22814104c737cd1a4f16eb4dfd9872ad4706

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