# Inside PortHub: Shared Terminals, Private Networks, and the Tools Between Them

Author: AlgoFly Team
Published: 2026-05-15T09:32:21+00:00
Canonical: https://algofly.ai/blog/porthub

PortHub can turn a screenshot into a public link, a local terminal into a shared workspace, and a directory into a resumable transfer. It can expose a development server, connect private machines, route an application through a proxy, and show what the agent is doing—all through the same toolkit.


That breadth is what makes [PortHub](https://github.com/algofly-oss/porthub) interesting. We have built a collection of tools around the everyday work of developing, debugging, and operating software across machines. Networking is the foundation. The useful part is what we can build on top of it.


This is a tour of those capabilities, the engineering choices behind them, and how they fit together in a real workflow.


![PortHub connects private networking and public forwarding with terminal sharing, file transfers and sync, screenshot links, proxies, and diagnostics.](/content/porthub/feature-map.svg)

One networking foundation, with tools for the work that happens around it.


## A quick look at what we have built




| When you need to… | PortHub gives you… |
| --- | --- |
| Show someone a local app | Temporary public exposure, optional subdomains, and persistent mappings |
| Work across your own machines | Private names, explicit access rules, peer connectivity, and exit routing |
| Debug together | Persistent terminal workspaces shared through a browser or ordinary SSH |
| Move datasets or build artifacts | Parallel downloads, resumable uploads, directory transfers, and watch sync |
| Share what you are seeing | Clipboard image uploads, short raw\-image links, and anonymous image rooms |
| Publish a service across backends | Health\-aware web proxy bindings, load balancing, and optional affinity |
| Choose an application's outbound route | Authenticated local HTTP/SOCKS5 proxies and Tor integration |
| Understand a connection problem | Live agent stats, on\-demand logs, traffic history, and layered diagnostics |


## Expose a service, then keep its address when it matters


A local service should be easy to share while you are working on it. An installed PortHub registration can expose an HTTP or TCP port with a single command:



```
porthub expose 8000
# Keep the exposure running in the background:
porthub expose 8000 --detach
# Optionally request a web hostname under your configured proxy suffix:
porthub expose 8000 --subdomain demo --detach
```

The command uses the installed agent's authenticated registration and the same port reservations as the dashboard. A CLI\-created exposure appears in the portal, so it remains visible alongside mappings created through the UI.


Foreground exposure ends when you stop it. Detached mappings save their desired configuration and recover after worker failures or restarts. Public exposures retain their assigned port when reconnecting; if that port is unavailable, they retry instead of quietly handing you a different address. Linux recovery can run at boot through its user service; macOS and Windows recover user mappings at login.


There is also a useful distinction between *exposing* a service through the public gateway and *forwarding* a reachable service onto your own machine:



```
# Reach a private service through a local port:
porthub forward peer.phub:3000 --bind localhost --local-port 8080
# Inspect and stop mappings explicitly:
porthub forward ls
porthub expose ls
porthub expose stop EXPOSURE_ID
```

The forwarded target needs neither SSH nor its own PortHub agent, as long as the forwarding machine can reach it. A private PortHub target still follows its access rules. This is useful when a browser or development tool expects a local endpoint but the application lives elsewhere.


![PortHub forwarding rule editor with internal host, internal port, external port, and an enable switch.](/content/porthub/forwarding.jpg)

The repository's forwarding editor: the dashboard and CLI manage the same underlying mappings.


## Private connectivity with names and explicit permissions


Public exposure is only one side of the system. PortHub also connects enrolled machines privately, with machine identities, private names, and workspace access rules.


A name such as `peer.phub` makes a machine convenient to address. Authorization remains tied to enrolled identities and policy. Rules can select machines, owners, and groups, and restrict direction, protocols, or port ranges. Membership in a group helps select machines; it does not automatically grant unrestricted access.


PortHub\-aware traffic can use an authorized direct peer path when available and a gateway relay when needed. A direct path matters particularly for large transfers: when that route is selected, the payload can move between peers without consuming gateway bandwidth.


The interesting engineering is in keeping identity and permissions consistent across these paths. Discovery, transport, and reachability do not get to override an access decision. A useful private network needs predictable access control as well as a way to move packets.


The current Rust implementation makes that separation concrete. In `porthub-api::domain::private_policy`, `compile_for` expands only the grants incident to the requesting machine. Selectors resolve enrolled machines, owners, and workspace groups; malformed stored rules are omitted as whole rules rather than having an invalid port restriction become an unrestricted grant. Rule\-book writes compare the saved revision in the MongoDB predicate, so two competing edits cannot both overwrite the same revision.


On Linux, the native private runtime owns a nonpersistent TUN interface and private /32 routes. A local DNS responder registers its private namespace with systemd\-resolved over D\-Bus. A friendly `.phub` name resolves to the assigned virtual IP; packet permissions and peer authentication still decide whether a connection can proceed. The runtime refuses conflicting routes or an existing interface name, and dropping its TUN removes the owned interface and routes.


`porthub-mesh` implements UDP ICE checks using the webrtc\-rs protocol components and carries IP records over mutually certificate\-pinned DTLS. Each session binds an enrolled peer ID, process context, and exact SHA256 certificate fingerprint. Local interface addresses and STUN\-derived addresses are candidate paths, rather than credentials. STUN responses must match both the requested server and a fresh transaction ID.


TURN relay allocations are prepared over a TLS WebSocket on the existing API port and carry the same pinned DTLS records. A separate compatibility gateway relay uses TLS on each gateway connection, with source/destination bindings and expiring policy leases; its encryption boundary differs from the DTLS peer path. Neither relay becomes permission to open arbitrary gateway sockets. Bounded preparation workers and peer queues keep discovery and a slow peer away from the packet\-forwarding loop.


Route choice also has state. When both paths are ready, authenticated background probes estimate payload rate and RTT. Automatic selection requires a meaningful improvement—at least 20% in measured rate or 10 ms in RTT—and expires old scores. Measurements choose paths for new flows; established flows retain their route. This is implemented path selection with local integration coverage. Physical NAT behavior, sustained WAN bandwidth, large\-fleet scheduling, and Windows/macOS private\-runtime lifecycle remain separate acceptance work.


## A terminal you can leave, return to, and share


Terminal sharing is one of the most substantial tools we have added. Start with a named local workspace:



```
porthub terminal work
# Reopen the same name later to return to its running workspace:
porthub terminal work
porthub terminal ls
```

The workspace persists when you detach, close your terminal, or lose an SSH connection. Its shell and background tasks belong to the workspace's persistent multiplexer. Local workspaces do not require a PortHub registration or a reachable gateway once the required tool is available.


That persistence comes from process ownership. In `porthub-terminal`, each attachment gets a PTY connected to the same tmux workspace. Dropping an attachment reaps its tmux client, while the shared tmux server and shell continue. The PTY implementation coalesces small input writes, bounds pending input, and duplicates its writer descriptor so dropping a writer does not accidentally type EOF into the shared shell. A browser disconnect therefore has a deliberately smaller lifetime than the workspace.


When you want someone else to join, share that existing workspace:



```
porthub terminal share work --detach
# Browser-only, read-only access for a walkthrough:
porthub terminal share work --access web --read-only --detach
# Retrieve the current browser links and SSH invitation commands:
porthub terminal url work
```

Guests can join in a browser or through an ordinary SSH client. They do not need to install a PortHub agent just to participate. The gateway supplies the terminal\-sharing SSH endpoint; this does not require opening the host machine's system SSH server to every guest.


We also built the controls that make sharing usable beyond a quick demo: read\-only viewing, scrollback, participant controls, targeted removal, invitation management, and reattachment to the same shell. Removing a participant revokes that participant's reconnect access while the other people and the workspace continue.


Invitation expiry has a specific meaning: it prevents new joins. It does not arbitrarily terminate an already attached participant or kill the running shell. Ending the workspace is an explicit operation.


On Linux and macOS, the terminal tool supplies a bundled tmux engine, including panes, windows, history, and copy mode. `Ctrl+A`, then `d`, detaches; `Ctrl+A`, then `?`, opens help; F7 enters or leaves scroll/copy mode. Native Windows hosting currently requires WSL; Windows guests can use the browser or standard SSH.


## File transfers built for interruptions and large artifacts


Moving a model, dataset, or directory should not mean starting again every time a connection drops. PortHub includes HTTP serving, upload receivers, parallel downloading, and resumable transfers.



```
# Serve a directory from one machine:
porthub http.server 8000 ./artifacts
# Download an artifact with parallel connections and an expected checksum:
porthub get http://machine:8000/model.bin \
  --connections 16 --sha256 EXPECTED_HEX_DIGEST
# Receive uploads on another machine:
porthub http.receive 8001 ./incoming
porthub push ./dataset http://machine:8001/
```

The downloader uses a shared queue of missing pieces across concurrent range workers. A worker that finishes its current piece can pick up more work, rather than waiting behind a slow segment assigned elsewhere. Completed pieces are checkpointed, and a resumed download checks the remote size and validators before trusting earlier progress.


Uploads have durable resume checkpoints too. The documented defaults use 16 parallel connections and 8 MiB chunks. Those are configurable concurrency choices, not a promise that every disk or network becomes faster with more connections.


Correct completion matters as much as parallelism. Downloads validate range responses; an optional SHA256 check happens before publishing the final artifact. Upload receivers replace a named file after the new upload is complete. The directory workflows preserve unrelated destination files.


These operations live in `porthub-transfer`: `download.rs` owns range workers and resume state, `upload.rs` owns chunked sending, and `server.rs` serves files and receives writes. The server supports GET/HEAD, ranges, validators, and safe resolution below its document root. Its process\-control listener is a separate authenticated loopback endpoint with a private per\-user capability; it does not expose list/stop controls on the public file listener.


Directory transfer is differential at file granularity: matching size and modification time can skip a file, while changed files use resumable chunk uploads. It does not claim rsync's rolling block\-delta protocol. Payload bypasses the gateway only when the selected private route is direct; an ordinary public exposure or gateway\-selected path still uses the gateway. More parallel chunks can help a capable link, but staging and assembly also add disk I/O.


There is a handy shortcut when you want to serve a directory publicly and create its mapping together:



```
porthub http.server ./public --expose
porthub http.receive ./drop --expose --subdomain drop -d
```

Basic serving and file transfers are implemented in the native core. You do not have to install a Python HTTP server or a separate download manager on every machine.


## Watch a directory and keep shipping its changes


The same transfer workflow extends into one\-way directory synchronization. Add `--watch` to perform an initial sync and then send incremental changes:



```
porthub push ./outputs http://peer.phub:8001/experiments/ --watch
# Run the watch job independently of this terminal:
porthub push ./outputs http://peer.phub:8001/experiments/ --watch --detach
porthub sync list
porthub sync status JOB_ID
porthub sync stop JOB_ID
```

Filesystem notifications trigger debounced syncs. Ordinary edits check affected paths; initial sync, recovery, and notification overflow can trigger a full\-tree check. If the connection fails, the job backs off and reuses its upload checkpoints.


The watch loop has no idle full\-scan timer. It records changes arriving during an upload and schedules another pass afterward; failed connections retry with a bounded 5–60 second backoff. Receiver paths create nested destination folders beneath the configured root and reject traversal or symlinked receiving directories. This combines event\-driven discovery with checkpointed transfer rather than repeatedly uploading the complete tree.


This fits workflows such as moving experiment outputs to a server while you work, collecting generated reports, or keeping a receiving directory supplied with new artifacts. Destination\-only files remain intact: deletion is not propagated, and this is not a two\-way conflict resolver. Detached watch jobs survive closing the terminal but do not automatically restart after reboot.


## Clipboard screenshots become links you can paste anywhere


Some of the best tools remove a small interruption repeated many times a day. PortHub's hosted\-image workflow does exactly that: take a screenshot, upload the clipboard image, and get a short raw\-image URL.



```
# Upload the current clipboard image and copy its public URL:
porthub image
# On supported desktops, also paste the URL into the focused input:
porthub image --mode paste
# Opt in to automatic shortcut registration on GNOME Linux:
porthub image shortcut enable
```

The portal also accepts pasted images, drag\-and\-drop uploads, and file selection. Links use a short path such as `/i/aB3dE.png`, and the image is stored on the server independently of the uploading machine. An uploaded screenshot can stay available even after the laptop that created it goes offline.


The implementation decodes the image and publishes a PNG with metadata removed. The desktop command talks to the running agent's authenticated local broker, so the shortcut does not need access to the machine's credential file. If the focused window changes during an upload, paste mode leaves the link copied rather than typing into the wrong window. Wayland currently supports copy mode.


We also added full\-screen viewing with zoom and pan, image browsing, bulk selection and deletion, source labels, and explicit tenant selection for machines enrolled with more than one PortHub account.


### Public rooms make screenshot collection collaborative


A public image room gives other people an upload page without requiring an account. Share the room, let someone paste or drop screenshots, and collect the resulting image links in one place.


Owners can pause uploads, manage images, and configure expiry for future uploads. Anonymous visitors can delete their own uploads using their browser\-held identity. Room expiry and quotas are separate from personal hosted images. These are public sharing links, so choose the room's lifetime and deletion controls for the kind of exchange you need.


## One web hostname, several backends


The public web proxy has grown beyond a single hostname pointing to one port. A route can contain multiple owned machine\-port mappings or standalone HTTP(S) backends.


You can choose manual priority, health\-based priority, or load balancing. Load\-balancing choices include round robin, weighted round robin, and least active connections, with optional cookie affinity. Active health checks, passive transport failures, thresholds, and cooldowns help determine which bindings are available.


One important detail is what happens during failure. Bounded failover can retry eligible empty\-body GET or HEAD requests. Writes, uploads, and WebSocket upgrades are not replayed across backends; an established response cannot simply jump to another application instance.


Health and affinity solve different problems here. Active checks and passive transport failures feed the binding's eligibility state, while cookie affinity tries to return a client to a usable binding. A backend can become eligible again after its recovery conditions and cooldown are met. Least\-active selection uses current connection activity; weighted round robin expresses configured preference. None of these choices replicates an application's session data or makes an in\-flight response transferable.


That distinction makes the feature more useful for real applications: publishing a service across two machines should not accidentally submit a form twice. Application state and session design still belong to the application when separate instances are balanced.


## Choose an exit route—or give one app its own proxy


PortHub also lets you control outbound connectivity. Private routing policy can select exit peers, with sticky, round\-robin, or random assignment for new connections. Existing flows retain their assigned exit rather than being silently moved to a different public source address.


For a single application, the CLI can start an authenticated loopback proxy that accepts HTTP and SOCKS5 on the same port:



```
porthub proxy start --via tor --port 1080 --detach
porthub proxy list
porthub proxy status
# Display this user's generated proxy credentials when configuring an app:
porthub proxy credentials
porthub proxy stop
```

This gives a browser, command\-line tool, or other proxy\-aware application its own route without changing the whole machine's default route. Tor is integrated through PortHub's optional tool architecture. A separate administrator\-authorized system\-wide Tor mode is available for a broader policy override.


The local proxy distinguishes application\-level proxying from packet routing: HTTP CONNECT or SOCKS5 establishes a requested destination through the selected worker, while the loopback listener checks generated credentials. Native exit routing instead tracks IP flows and keeps their assigned exit identity. Reusing one HTTP/2 or keep\-alive connection therefore reuses that connection's exit even when round robin is selected for new connections.


These routing choices have specific boundaries. Tor cannot carry every IP protocol, and the documented exit forwarding is IPv4 rather than an IPv6 full tunnel. The useful feature is deliberate route selection with explicit behavior, not a claim that every application and protocol works identically everywhere.


## Know what the agent and gateway are actually doing


A dashboard that only says “online” leaves a lot unanswered. PortHub's machine inventory, traffic history, live logs, and native stats help connect a symptom to the part of the system involved.



```
porthub ls
porthub status REGISTRATION_ID
porthub stats
porthub diagnose --profile /private/path/machine.json
```

The local stats path observes agent traffic, service health, and process resources. Its collection does not need to create an application connection just to measure activity. On\-demand diagnostic logs are bounded and redacted; forwarding does not wait for a log upload.


The diagnose command separates DNS, TCP reachability, verified TLS, and authenticated control. That distinction is valuable: reaching an HTTPS endpoint does not by itself prove that the enrolled machine has valid control access.


On the server, public\-port traffic history is collected into the existing MongoDB database, while the firewall keeps a bounded local replay buffer for recovery. The dashboard can expose history without giving the firewall direct database access.


![PortHub dashboard showing registered machines, online state, port counts, local addresses, groups, and last-seen status.](/content/porthub/dashboard.jpg)

Machine inventory in the project repository's dashboard screenshot. Networking tools remain tied to visible, managed registrations.


## A modular native agent, with tools that can stand on their own


Adding terminal sharing, sync, Tor, and live stats raises an engineering question: how do we keep useful new tools from making every basic networking operation depend on all of them?


PortHub now separates the networking core from optional executables for terminal workspaces, directory sync, Tor, and the stats interface. The regular `porthub` commands remain the user\-facing interface; the core resolves compatible tools and manages their preparation. Enrollment and healthy tunnels can become ready while optional tools are prepared in the background.


Components have independent versions. Selection checks the operating system, architecture, core compatibility, and IPC protocol; downloads are verified against catalog hashes and published atomically. A failed optional\-tool update does not have to turn a healthy core update into a failure.


Compatible cached tools can keep working when the gateway is unavailable. Discovery can fall back to official GitHub release assets. The tools also expose standalone entry points for deliberate use without an installed PortHub core, where the operation itself does not require a gateway.


We have gone beyond building a modular interface: the repository records cross\-platform release gates, installed\-agent checks, offline workspace checks, and live Tor worker handover. The release records distinguish those checks from untested host\-specific scenarios. That is a more useful signal than a feature merely appearing in a design document.


## The gateway is split by responsibility


The server follows the same preference for explicit boundaries. Its Compose deployment separates three Rust roles:




| Role | What it owns | Why the boundary matters |
| --- | --- | --- |
| Core | API, application data, and the Next.js portal | Application state stays away from the public streaming role |
| Edge | Public listeners and socket\-to\-socket streaming | The public traffic path does not need database access |
| Firewall | Network policy and traffic measurement | Network administration privileges are confined to this role |


Scoped Unix sockets connect the roles. The edge reuses buffers and pooled data connections, while application state stays in MongoDB. Forwarding shares the existing API listeners for its control transport, avoiding a separate Rathole control port or a separately managed Rathole executable.


Configuration changes can remove a service or revoke access without restarting unrelated forwarding services. For endpoint changes, TCP copies can drain on their existing route while new connections use the selected route. These details are less visible than a new dashboard button, but they are essential when people depend on a connection staying useful.


## How the pieces work together


Imagine debugging an application on a remote workstation. You reach the machine through its permitted private route, open a persistent terminal, and share a read\-only browser invitation with a teammate. A local development service gets a temporary exposure for a demo. Its generated reports are sent through a watched directory transfer, and a screenshot becomes a short image link for the discussion.


Each step has its own controls and lifetime. Stopping the demo mapping need not end the terminal. Closing the terminal window need not stop the workspace. A screenshot remains on the server independently of the workstation. The same registration and networking foundation connect the workflow without forcing those lifetimes to become one.


That is the part of PortHub we want to highlight: a growing set of carefully connected developer tools, with enough independence to use each one for the job at hand.


## Explore PortHub


Start with the repository's setup instructions for your gateway, then try one workflow: a temporary exposure, a named terminal, or a directory transfer. The current source and feature\-specific docs are the best place to check platform support and command options.


* [PortHub repository, setup, image hosting, and public rooms](https://github.com/algofly-oss/porthub)
* [Transfers, resumable downloads, and watch sync](https://github.com/algofly-oss/porthub/blob/main/docs/native-transfers.md)
* [Terminal sharing and verification records](https://github.com/algofly-oss/porthub/blob/main/docs/terminal-server-state.md)
* [Multi\-backend web proxy behavior](https://github.com/algofly-oss/porthub/blob/main/docs/design/proxy-bindings.md)
* [Independent tools, compatible updates, and release verification](https://github.com/algofly-oss/porthub/blob/main/docs/design/on-demand-components.md)


*Reviewed against source [d0e2664](https://github.com/algofly-oss/porthub/tree/d0e2664). Release context: v1\.11\.18\. Product screenshots are from the repository. Native private\-network mechanisms above are implemented and locally integration\-tested; platform and physical\-network acceptance remain scoped as described in the linked documentation.*


