Access internal websites from anywhere without a VPN, without editing DNS, and without hand-building port forwards for every service.
Remote Browser gives you a dedicated Chrome/Chromium app that behaves like it is sitting inside your remote Linux network. Open the app on macOS or a Linux desktop, type an internal URL, and the request is resolved and fetched through the remote machine that already has network access.
It is built for the annoying real-world gap between "I can reach this service from the remote machine" and "I need to inspect it in a normal browser on my laptop".
Modern teams hide the important stuff behind private DNS, split networks, private routes, remote development hosts, staging clusters, intranet portals, admin UIs, local dashboards, and environments that your laptop browser cannot see directly.
The usual workarounds are slow and fragile:
- Start a VPN, hope DNS routes correctly, and leak all browsing into the VPN.
- Forward one port, then another, then another, until the login redirect breaks.
- Copy service URLs into curl because the browser cannot resolve the hostname.
- Reconfigure Chrome proxy settings globally and accidentally affect normal browsing.
- Keep a terminal full of tunnel commands alive just to view one internal page.
Remote Browser replaces that mess with one focused tool:
- a separate desktop browser app for internal sites,
- a remote-side HTTP proxy where internal DNS actually works,
- an authenticated Dev Tunnel bridge for the proxy port,
- an isolated Chrome profile so cookies, extensions, history, and sessions stay separate from your daily browser,
- scripts for macOS, Linux clients, and remote Linux hosts.
Use Remote Browser when a machine you can reach already has access to internal websites, but your local browser does not.
Good examples:
- Open
https://grafana.internal,https://argocd.company.local, orhttps://service.namespace.svcfrom your laptop. - Test staging web apps that only resolve inside a remote Linux host or a private network.
- Access internal admin panels without routing your whole laptop through a VPN.
- Browse many internal hosts without creating a new port forward for each one.
- Keep internal sessions and certificates out of your normal Chrome profile.
- Give developers a repeatable "open the internal browser" workflow instead of a tribal-knowledge tunnel recipe.
Remote Browser is not a security bypass. The remote Linux machine must already be allowed to reach the internal sites, and Dev Tunnels authentication still controls who can connect to the bridge.
The key idea is simple: do DNS and HTTP(S) proxying on the remote side, then make only that proxy reachable to the client.
flowchart LR
user["Developer"]
app["Remote Browser app<br/>macOS .app or Linux client"]
profile["Dedicated Chrome profile<br/>cookies, history, extensions"]
localBridge["Local Dev Tunnel bridge<br/>127.0.0.1:18080"]
tunnel["Authenticated Dev Tunnel<br/>single proxy port"]
remoteHost["Remote Linux host<br/>inside private network"]
tinyproxy["tinyproxy<br/>127.0.0.1:18080"]
dns["Remote DNS resolver"]
internal["Internal sites<br/>intranet, staging, dashboards, admin UIs"]
user --> app
app --> profile
app -->|"Chrome proxy flag"| localBridge
localBridge --> tunnel
tunnel --> remoteHost
remoteHost --> tinyproxy
tinyproxy --> dns
tinyproxy --> internal
normal["Normal Chrome<br/>unchanged"]
user -.-> normal
Request flow:
- You open
Remote Browser.appon macOS or the Linux client. - The launcher starts Chrome/Chromium with an isolated profile and proxy flags.
- The local client exposes
http://127.0.0.1:18080through Dev Tunnels. - The remote Linux side runs
tinyproxyon the same port. - Internal hostnames are resolved by the remote machine, not by your laptop.
- The page renders locally while the network lookup happens remotely.
That is the whole product: local browser experience, remote network reach.
- One-click internal browsing on macOS through
/Applications/Remote Browser.app. - Linux desktop client and optional AppImage.
- Remote Linux setup for
tinyproxyand tunnel hosting. - No global browser proxy changes.
- No per-service port forwarding.
- No dependency on the VS Code Ports panel once the bridge is configured.
- Separate browser profile for internal sessions.
- Logs and status commands that make failures diagnosable.
- A single shared
remote-browser.envfor tunnel name, proxy port, browser path, start URL, and prestart hooks.
There are two sides:
- Remote side: Linux machine that can resolve and reach internal sites.
- Client side: macOS or Linux desktop where you want the browser window.
Run this inside the remote Linux checkout:
./remote-browser-setup.shChoose:
install remote Linux side
The setup installs the needed CLI tools, configures tinyproxy, prepares the
tunnel helper, starts the remote proxy, and hosts the proxy port through Dev
Tunnels.
Check it:
./scripts/remote-browser-proxy-remote-start.sh status
./scripts/remote-browser-remote-start.sh status
./scripts/remote-browser-remote-start.sh proxy-host-statusThe first run may ask for a GitHub device-code login for the Dev Tunnels CLI. Later runs reuse that login.
Run this in the macOS checkout:
./remote-browser-setup.shChoose:
install/update macOS app
Direct non-interactive install:
./scripts/remote-browser-install.sh \
--mac \
--mac-app \
--app-name "Remote Browser" \
--app-force \
--app-openThe app is installed here:
/Applications/Remote Browser.appOpen it like any other Mac app. Only Remote Browser appears in the Dock, and
the app uses its own Chrome profile.
Run this in the Linux desktop checkout:
./remote-browser-setup.shChoose:
install/update Linux client
Direct install:
./scripts/remote-browser-install.sh --linux-clientBuild an AppImage:
./scripts/remote-browser-linux-appimage.sh --forceRun it:
./scripts/remote-browser-linux-client.shor:
./dist/remote-browser-x86_64.AppImageCreate a local config if you need to override defaults:
cp config/remote-browser.env.example remote-browser.envUse the same tunnel name on the remote side and every client.
Important values:
RB_TUNNEL_NAME- Dev Tunnel label/name shared by the remote host and clients.RB_REMOTE_TUNNEL_LABEL- optional label override used for lookup.RB_PROXY_PORT- proxy and bridge port, default18080.RB_CONNECT_TUNNEL_ID- fixed Dev Tunnel ID when label lookup is not enough.RB_CHROME_APP- local Chrome app used as the macOS runtime source.RB_CHROME_BIN- local Chrome/Chromium executable used on Linux.RB_CHROME_PROFILE_DIR- dedicated Remote Browser Chrome profile.RB_START_URL- default Linux startup URL, defaultabout:blank.RB_REMOTE_PRESTART_CMD- optional command run before app launch to start remote Linux services, for example through SSH.
Example prestart hook:
RB_REMOTE_PRESTART_CMD="ssh <user>@<host> '/path/to/repo/scripts/remote-browser-proxy-remote-start.sh start && /path/to/repo/scripts/remote-browser-remote-start.sh host-proxy'"The most important proof is not "the tunnel exists". The proof is that the client has a local listener and can fetch an internal URL through it.
On the remote Linux side:
./scripts/remote-browser-proxy-remote-start.sh status
./scripts/remote-browser-proxy-remote-start.sh test
./scripts/remote-browser-remote-start.sh proxy-host-statusOn the client side:
./scripts/remote-browser-devtunnel.sh statusOn macOS, verify the local proxy listener:
lsof -nP -iTCP:18080 -sTCP:LISTENVerify a real internal site:
curl -k -x http://127.0.0.1:18080 -I -L \
"https://internal-service.local/healthz"Expected result: the request reaches the internal service and returns a real
HTTP response, for example a redirect followed by 200 OK.
Start from macOS:
open "/Applications/Remote Browser.app"Start from Linux:
./scripts/remote-browser-linux-client.shStop the macOS app:
./scripts/remote-browser-stop.commandStop the client tunnel bridge:
./scripts/remote-browser-devtunnel.sh stopStop remote tinyproxy:
./scripts/remote-browser-proxy-remote-start.sh stopremote-browser-setup.sh- interactive installer with arrow-key menus.scripts/remote-browser-install.sh- installs macOS, Linux client, or remote Linux dependencies.scripts/remote-browser-macos-app.sh- builds/Applications/Remote Browser.app.scripts/remote-browser-linux-client.sh- Linux client launcher using local Chrome/Chromium.scripts/remote-browser-linux-appimage.sh- builds a Linux client AppImage.scripts/remote-browser.command- macOS gateway check used by the app.scripts/remote-browser-devtunnel.sh- client-side Dev Tunnels bridge for port18080.scripts/remote-browser-proxy-remote-start.sh- remote Linux tinyproxy manager.scripts/remote-browser-remote-start.sh- remote Linux tunnel host helper.scripts/remote-browser-stop.command- closes the Remote Browser app.config/remote-browser.env.example- shared config template.config/remote-browser-proxy-remote.tinyproxy.conf- remote Linux tinyproxy template.assets/- app icon assets.
macOS app launcher:
tail -f ~/Library/Logs/remote-browser/launcher.logClient Dev Tunnel bridge:
tail -f ~/.local/state/remote-browser-devtunnel/connect.logRemote Linux tunnel host:
./scripts/remote-browser-remote-start.sh logsRemote Linux tinyproxy:
tail -f ~/.remote-browser-proxy/tinyproxy.logIf the browser opens but internal pages fail, check in this order:
-
Remote proxy running:
./scripts/remote-browser-proxy-remote-start.sh status
-
Remote tunnel host running:
./scripts/remote-browser-remote-start.sh proxy-host-status
-
Client bridge running:
./scripts/remote-browser-devtunnel.sh status
-
Local listener exists on the client:
lsof -nP -iTCP:18080 -sTCP:LISTEN
-
Internal URL works through the proxy:
curl -k -x http://127.0.0.1:18080 -I -L "https://internal-service.local"
Common failure meanings:
- No listener on
127.0.0.1:18080: the client bridge is not connected. - Remote tunnel host offline: start
remote-browser-remote-start.sh host-proxyon the remote Linux machine. tinyproxynot listening: startremote-browser-proxy-remote-start.sh starton the remote Linux machine.- Dev Tunnel login required: run
./scripts/remote-browser-remote-start.sh loginon the remote side or authenticate the client-sidedevtunnelCLI.