Access Remote Machines with rstream WebTTY
Use rstream WebTTY to reach remote machines from the rstream Dashboard, from browser-based clients, or from the rstream CLI without exposing a conventional remote shell service publicly.
This guide explains how to publish, connect to, and automate an rstream WebTTY server when a remote machine is not publicly reachable, or when exposing a conventional remote shell service would create unnecessary exposure. The same pattern applies to administrative hosts, personal workstations, homelabs, embedded devices, and machines that sit behind NAT, private addressing, or restrictive firewalls.
The remote machine runs a WebTTY server process that establishes the outbound connection to the rstream edge network. Operators can then connect through the native rstrm:// dialer from the rstream CLI, through the rstream Dashboard, or through a published wss:// URL from another browser-based client. That combination of web-native transports, outbound-only connectivity, and direct integration with rstream tunnels makes WebTTY a practical way to reach machines that are otherwise not addressable from the public Internet.
WebTTY is available on Basic, Pro, Enterprise, and self-hosted deployments. The published workflow below works across that full range. When browser access is not required, the same server can also be kept private and reached from the CLI only.
WebTTY vs SSH
rstream WebTTY is built on web protocols and on the rstream tunnel model itself. The same terminal can therefore be reached from the rstream Dashboard, from browser-based clients, and from the rstream CLI while the remote machine only maintains an outbound connection to the edge. That model fits naturally on machines that do not have a public IP address, that sit behind NAT or private addressing, or that are not meant to expose inbound services.
SSH remains the better fit when the workflow depends on the SSH ecosystem itself, such as ssh_config, agent forwarding, scp, sftp, or existing SSH host-key and bastion patterns. When that is the requirement, use the SSH workflow covered in Access Remote Machines over SSH with rstream.
Prepare access to the project
The machine that runs the WebTTY server needs an rstream CLI configuration that resolves to the target project. If the native CLI client flow is also used, the client machine needs the same project selection.
For a quick hosted validation, the simplest preparation on each CLI machine is:
rstream login
rstream project use <project-endpoint> --defaultThat setup is convenient for operator workstations. For long-lived servers and devices, a dedicated project-scoped context is generally preferable so the machine only holds the project-scoped access it actually needs. Background on installation, authentication, and context management is covered in Installation, CLI Login, and CLI Workflow.
Publish a WebTTY server
rstream webtty server --rstream --name homelab-macminiThis starts a published WebTTY server with the tunnel name homelab-macmini. The name is only an example, but in practice it should identify the machine itself so that several servers remain distinguishable in inventory, automation, and browser sessions. The published form is the one used by the rstream Dashboard and by browser-based clients because it provisions the wss:// entrypoint required by those sessions.
The server process itself is the long-running workload. Unlike the SSH pattern, there is no separate sshd process to pair with a tunnel. rstream webtty server provides the terminal server and the tunnel-facing endpoint in the same binary, so the remote access path does not require an inbound listener or a local port binding.
Access control and execution model
Published and private WebTTY servers follow the same access policy. The rstream edge network verifies the caller before the session reaches the remote machine. Native CLI sessions rely on the authenticated local rstream context. Browser sessions rely on a short-lived token issued for that session. Publishing a WebTTY server therefore adds a browser-capable wss:// entrypoint, but it does not turn the remote terminal into an anonymously reachable service.
Terminal sessions execute in the operating-system context of the process that started rstream webtty server. When the server is launched by a given user, commands run as that user by default. The client can request a different target user, working directory, or environment only when the host policy allows it. Privilege boundaries therefore remain the ones enforced by the remote machine itself.
Connect from the CLI
Once the server is online, the native CLI workflow is direct:
rstream webtty client --url rstrm://homelab-macmini -- printf 'hello world\n'For an interactive session:
rstream webtty client --url rstrm://homelab-macminiEven though the server is published, the CLI does not need the published address for this path. It dials rstrm://homelab-macmini directly through the local rstream configuration. In day-to-day operator workflows, this is usually the simplest CLI form because it avoids a separate published address lookup.
Connect from the Dashboard
The same published server can also be opened from the Dashboard. Open the target project, navigate to the WebTTY page, select the server, and start the browser session from there. The Dashboard resolves the published wss:// address, requests a short-lived token through the control plane API, and opens the remote terminal in the browser.
This browser workflow depends on the server being published. A private --no-publish server remains available to native CLI clients, but it does not expose the published entrypoint that the browser requires.


Inspect available WebTTY servers from the CLI
When more than one WebTTY server is online, the dedicated inventory command is:
rstream webtty listThis inventory only includes online WebTTY servers. It shows the native rstrm:// target used by the CLI, the host metadata generated by the server, and the published host when browser access is enabled.
For live operator workflows across WebTTY servers, tunnels, and clients, use the terminal UI:
rstream uirstream ui keeps the inventory up to date from the live signaling API. From the WebTTY view, pressing Enter opens the selected server directly inside the UI. This is the most practical CLI console when discovery and connection happen in the same operator session.
The same operator workflow is shown below from the CLI inventory and from an embedded WebTTY session inside rstream ui.


When raw tunnel properties or label-level filtering are needed, the lower-level inventory view remains available:
rstream tunnel list --filter 'labels.application-protocol=rstream.webtty'Private CLI access
When browser access is not part of the requirement, the same WebTTY server can be kept private:
rstream webtty server --rstream --name homelab-macmini --no-publishPrivate tunnel dialing requires Pro, Enterprise, or self-hosted deployments. This private WebTTY form is therefore not available on the Basic plan.
The corresponding CLI workflow keeps the same target and simply drops the browser path:
rstream webtty client --url rstrm://homelab-macminiThis mode removes the browser-facing entrypoint entirely. It is the tighter option when native rstream clients are the only intended consumers. Authorized rstream clients can still dial the server natively through rstrm://homelab-macmini, but the Dashboard and other browser-based clients no longer have a published wss:// address to use.
Published WSS access
The native rstrm:// form is usually the better CLI workflow, but it is not the only option. A published WebTTY server also exposes a wss:// URL, and the CLI can connect to it directly when a URL-based flow is required.
rstream tunnel list --filter 'name=homelab-macmini,labels.application-protocol=rstream.webtty' -o json | jq -r '.[0].host'Then:
rstream webtty client --url 'wss://<published-host>?rstream.token=<token>'The Dashboard uses this same published transport. The difference is token handling. In the Dashboard, the browser session obtains a short-lived token through the control plane API and injects it automatically. For direct CLI use, the token has to be supplied explicitly in the URL.
This path is useful when URL-based access is needed, but it is not the primary CLI recommendation. When the CLI already has project access, rstrm://homelab-macmini is simpler and avoids a separate published address lookup.
Adjust the remote session
The WebTTY client can also set the working directory, environment variables, or the target user of the remote process:
rstream webtty client \
--url rstrm://homelab-macmini \
-e FOO=bar \
-w /tmp \
-u admin \
-- sh -lc 'printf "%s|%s|%s\n" "$PWD" "$FOO" "$(id -un)"'These options are useful when the remote machine needs a specific runtime context, but they should be treated as part of the operational contract of the server. The full client flag reference is covered in WebTTY.
Start the WebTTY server automatically
Unlike the SSH workflow, there is no separate resident system service such as sshd to pair with rstream run. Here the WebTTY server itself is the long-running process, so automation is handled directly by the operating system service manager.
To keep the service definition generic across machines, move the machine-specific parts into a small settings file. The examples below use homelab-macmini as the tunnel name and the published form by default. For private CLI-only access, set the flag value to --no-publish in the settings file instead of editing the service unit itself.
Linux
Create the settings file first:
RSTREAM_BIN="$(command -v rstream)"
mkdir -p ~/.rstream ~/.config/systemd/user
cat > ~/.rstream/webtty.env <<EOF
RSTREAM_BIN=${RSTREAM_BIN}
RSTREAM_WEBTTY_NAME=homelab-macmini
RSTREAM_WEBTTY_FLAGS=
EOFThen create the generic user-scoped service:
cat > ~/.config/systemd/user/rstream-webtty.service <<EOF
[Unit]
Description=rstream WebTTY server
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
EnvironmentFile=%h/.rstream/webtty.env
ExecStart=/bin/sh -lc 'exec "$RSTREAM_BIN" webtty server --rstream --name "$RSTREAM_WEBTTY_NAME" $RSTREAM_WEBTTY_FLAGS'
Restart=always
RestartSec=5
[Install]
WantedBy=default.target
EOFEnable it with:
systemctl --user daemon-reload
systemctl --user enable --now rstream-webtty.service
loginctl enable-linger "$(id -un)"loginctl enable-linger allows the user service to stay active after logout and across reboots.
macOS
Use the same idea with a small settings file and a generic LaunchAgent:
RSTREAM_BIN="$(command -v rstream)"
mkdir -p ~/.rstream ~/Library/LaunchAgents
cat > ~/.rstream/webtty.env <<EOF
RSTREAM_BIN=${RSTREAM_BIN}
RSTREAM_WEBTTY_NAME=homelab-macmini
RSTREAM_WEBTTY_FLAGS=
EOF
cat > ~/Library/LaunchAgents/io.rstream.webtty.plist <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>io.rstream.webtty</string>
<key>ProgramArguments</key>
<array>
<string>/bin/sh</string>
<string>-lc</string>
<string>set -a; . "$HOME/.rstream/webtty.env"; exec "$RSTREAM_BIN" webtty server --rstream --name "$RSTREAM_WEBTTY_NAME" $RSTREAM_WEBTTY_FLAGS</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
</dict>
</plist>
EOF
launchctl bootout "gui/$(id -u)" ~/Library/LaunchAgents/io.rstream.webtty.plist 2>/dev/null || true
launchctl bootstrap "gui/$(id -u)" ~/Library/LaunchAgents/io.rstream.webtty.plist
launchctl kickstart -k "gui/$(id -u)/io.rstream.webtty"Windows
Create a small settings file and a generic launcher script:
$settings = "$env:USERPROFILE\.rstream\webtty-settings.ps1"
$launcher = "$env:USERPROFILE\.rstream\webtty-launch.ps1"
New-Item -ItemType Directory -Force "$env:USERPROFILE\.rstream" | Out-Null
@"
\$RstreamBin = (Get-Command rstream).Source
\$WebTTYName = "homelab-macmini"
\$WebTTYFlags = @()
"@ | Set-Content $settings
@"
. `"$settings`"
& \$RstreamBin webtty server --rstream --name \$WebTTYName @WebTTYFlags
"@ | Set-Content $launcher
$action = New-ScheduledTaskAction -Execute "pwsh" -Argument "-NoProfile -File `"$launcher`""
$trigger = New-ScheduledTaskTrigger -AtLogOn
Register-ScheduledTask `
-TaskName "rstream WebTTY Server" `
-Action $action `
-Trigger $trigger `
-User $env:USERNAME `
-Force
Start-ScheduledTask -TaskName "rstream WebTTY Server"For a private CLI-only server, change $WebTTYFlags = @() to $WebTTYFlags = @("--no-publish").
Operational notes
The published workflow above is the broadest deployment shape. It supports browser sessions from the Dashboard, native rstrm:// sessions from the CLI, and direct wss:// connections when URL-based access is required.
The private --no-publish form narrows that surface for CLI-only environments. The browser path disappears, but the operational model stays simple: one long-running server process on the remote machine and native WebTTY clients on the operator side.
Taken together, these flows make WebTTY a practical remote access option when browser access matters, when SSH is not the right fit, or when the same remote terminal should be available both in the Dashboard and in the CLI.