I set up Paseo this way for two main reasons. First, I wanted a single interface
that works across coding agents. I had switched to Pi to use ChatGPT agents while
retaining more control over the models assigned to subagents than Codex provides.
Second, I wanted the same seamless connection between my laptop and phone that
Claude’s /remote-control offered. As an added bonus, Paseo gives me a cleaner
way to organize multiple agents than a row of terminal windows side by side.
This setup runs the Paseo daemon and coding agents inside WSL, uses the native Paseo GUI on Windows, and allows a phone running Tailscale to connect directly to the same WSL-hosted Paseo daemon without enabling Paseo Relay.
Final architecture
Windows Paseo GUI
|
| localhost:6768
v
Windows localhost
|
| WSL localhost forwarding (NAT mode)
v
WSL Paseo daemon
|
+-- Claude
+-- Codex
+-- OpenCode
+-- Pi
+-- etc.
Phone Paseo app
|
| Tailscale
v
Windows Tailscale IP:16768
|
| tailscale serve TCP forwarding
v
Windows 127.0.0.1:6768
|
| WSL localhost forwarding
v
WSL Paseo daemon
Optionally, Tailscale Serve can also expose Paseo's web UI over HTTPS for access from Safari.
The important design choices are:
- Paseo daemon: WSL
- Coding agents: WSL
- Windows Paseo app: connects to WSL daemon through
localhost:6768 - Tailscale: installed on Windows and phone
- WSL networking: NAT mode, not mirrored mode
- Paseo Relay: disabled
Port convention used in this guide:
- 6768 = Paseo's internal daemon port in WSL and Windows-local connection port
- 16768 = Tailscale-facing raw TCP port used by the phone
1. Use WSL NAT networking, not mirrored networking
For this setup, use WSL's normal NAT networking mode.
Edit this Windows file:
%UserProfile%\.wslconfig
Use:
[wsl2]
networkingMode=NAT
Then apply the change from PowerShell:
wsl --shutdown
Start WSL again normally.
Why NAT instead of mirrored mode?
In testing, mirrored networking caused a pathological interaction with Node/Paseo:
- Paseo's Node process successfully called
listen()on127.0.0.1:6768. ssshowed the Paseo socket inLISTEN.- Local
curlconnections nevertheless timed out. - A minimal Node HTTP server behaved the same way.
- A Python HTTP server on the same address and port worked normally.
That isolated the failure to Node under WSL mirrored networking rather than Paseo itself.
NAT mode avoids this problem and is sufficient for this architecture because Windows can reach WSL services through localhost, while Tailscale remains entirely on the Windows side.
Using different inside/outside ports also avoids having Windows Tailscale Serve and WSL both trying to own the same port.
2. Install and run Paseo inside WSL
Install Paseo and the coding agents you want to use inside WSL.
Verify that WSL sees them:
which codex
which claude
which opencode
which pi
Paseo should also see them:
paseo provider ls
A useful sanity check is:
paseo daemon status
The daemon should listen on:
127.0.0.1:6768
3. Configure the WSL Paseo daemon
Edit:
~/.paseo/config.json
A working example is:
{
"$schema": "https://paseo.sh/schemas/paseo.config.v1.json",
"version": 1,
"daemon": {
"listen": "127.0.0.1:6768",
"hostnames": [
"localhost",
".localhost",
"127.0.0.1",
"YOUR_WINDOWS_TAILSCALE_IP",
"YOUR_WINDOWS_TAILSCALE_HOSTNAME"
],
"mcp": {
"injectIntoAgents": false
},
"browserTools": {
"enabled": false
},
"autoArchiveAfterMerge": false,
"enableTerminalAgentHooks": false,
"appendSystemPrompt": "",
"cors": {
"allowedOrigins": [
"https://app.paseo.sh",
"https://YOUR_WINDOWS_TAILSCALE_HOSTNAME"
]
},
"relay": {
"enabled": false
}
},
"app": {
"baseUrl": "https://app.paseo.sh"
},
"agents": {
"providers": {
"copilot": {
"enabled": false
}
}
},
"features": {
"webUi": {
"enabled": true
}
}
}
Keeping Paseo bound to 127.0.0.1 is intentional. It avoids exposing the daemon directly to the ordinary LAN.
Restart Paseo after changing the config:
paseo daemon stop
paseo daemon start
4. Connect the Windows Paseo GUI to WSL
In the Windows Paseo application, connect to:
localhost:6768
With WSL in NAT mode, Windows normally forwards localhost connections into WSL automatically.
Test from PowerShell:
curl http://localhost:6768/api/health
or:
curl http://localhost:6768/
If that works, the Windows Paseo GUI should also be able to use:
localhost:6768
The coding agents are discovered and launched by the WSL daemon, so Codex/Claude/Pi/etc. need to be installed in WSL rather than Windows.
5. Install Tailscale on Windows and the phone
Install Tailscale on:
- the Windows laptop
- the phone
Sign both into the same tailnet.
Do not also run Tailscale inside WSL for this setup. Tailscale stays on Windows and forwards into the already-working Windows → WSL localhost path.
Find the Windows laptop's Tailscale IP:
tailscale ip -4
It will look like:
100.x.x.x
You can also use its MagicDNS hostname.
6. Expose Paseo's raw port to the phone
The Paseo phone app's Direct connection needs a raw TCP endpoint.
Use a different external Tailscale port from Paseo's internal WSL port. In this guide:
- WSL/Windows-local Paseo:
6768 - Tailscale-facing phone port:
16768
On Windows PowerShell:
tailscale serve --bg --tcp=16768 tcp://127.0.0.1:6768
You should get output similar to:
Available within your tailnet:
|-- tcp://your-laptop.tailnet.ts.net:16768
|-- tcp://100.x.x.x:16768
|--> tcp://127.0.0.1:6768
Verify it:
tailscale serve status --json
The relevant part should look like:
{
"TCP": {
"16768": {
"TCPForward": "127.0.0.1:6768"
}
}
}
The complete path is now:
Phone
-> Tailscale
-> Windows Tailscale IP:16768
-> Tailscale Serve
-> Windows 127.0.0.1:6768
-> WSL localhost forwarding
-> Paseo 127.0.0.1:6768
No Paseo Relay is involved.
7. Configure the Paseo phone app
In Paseo on the phone, choose Direct connection.
Use:
Host/IP: <Windows Tailscale IP>
Port: 16768
SSL/TLS: off
The connection is already encrypted by Tailscale, so TLS on the raw Paseo connection is unnecessary.
The Tailscale hostname should also work:
your-laptop.tailnet.ts.net
but using the 100.x.x.x address is a useful first test because it removes DNS from the equation.
8. Optional: expose the Paseo web UI through Tailscale
If you also want Safari/browser access, expose the web UI through Tailscale HTTPS:
tailscale serve --bg https / http://127.0.0.1:6768
Then:
tailscale serve status
will show an address such as:
https://your-laptop.tailnet.ts.net
Open that URL from Safari while Tailscale is connected.
A combined Serve configuration can therefore contain both:
HTTPS :443 -> http://127.0.0.1:6768
TCP :16768 -> tcp://127.0.0.1:6768
The HTTPS path is useful for Safari. The raw TCP path is what the Paseo phone app's Direct connection uses.
9. Pairing vs relay
Paseo may still ask to pair/authenticate a new client. That does not mean Relay needs to be enabled.
These are separate concerns:
Tailscale
= private network path to the laptop
Paseo pairing
= authorization of the client
Paseo Relay
= optional Paseo-hosted connectivity mechanism
With this topology, keep:
"relay": {
"enabled": false
}
Tailscale provides the network path instead.
10. Reboots and persistence
Tailscale Serve rules started with --bg persist across reboot.
After a reboot, verify them with:
tailscale serve status
You should still see:
TCP :16768 -> 127.0.0.1:6768
and, if enabled:
HTTPS :443 -> http://127.0.0.1:6768
The WSL Paseo daemon is a separate concern. Make sure it starts after WSL starts, either manually:
paseo daemon start
or through an appropriate WSL/systemd startup mechanism.
Troubleshooting
Paseo says the port is in use, but ss shows nothing
First test the port with a simple server:
python3 -m http.server 6768 --bind 127.0.0.1
If Python works but Paseo/Node does not, test Node directly:
/usr/bin/node -e "require('http').createServer((req,res)=>res.end('OK\n')).listen(6768,'127.0.0.1',()=>console.log('listening'))"
Then, from another WSL shell:
curl --max-time 5 http://127.0.0.1:6768/
If Python works but Node times out, check whether WSL is using mirrored networking. In this setup, switch back to NAT mode:
[wsl2]
networkingMode=NAT
then:
wsl --shutdown
This was the key fix in our tested setup.
Paseo daemon status says unresponsive
Do not rely only on paseo daemon status.
Check whether the socket actually exists:
ss -ltnp | grep 6768
Then test it:
curl -v --max-time 5 http://127.0.0.1:6768/api/health
If the socket is listening but Node requests time out, investigate WSL networking mode as above.
Stale Paseo PID
If:
paseo daemon status
reports:
stale_pid
and the recorded PID is genuinely no longer running, remove:
rm ~/.paseo/paseo.pid
Then start Paseo again.
Windows GUI works, but phone cannot connect
Check:
tailscale serve status --json
You need a raw TCP rule like:
"16768": {
"TCPForward": "127.0.0.1:6768"
}
If it is missing:
tailscale serve --bg --tcp=16768 tcp://127.0.0.1:6768
The phone should then connect to:
<Windows Tailscale IP>:16768
not :6768.
Summary
The working arrangement is:
+---------------------+
| Windows Paseo GUI |
+----------+----------+
|
localhost:6768
|
v
+---------------+ +---------+----------+
| iPhone Paseo | | Windows + Tailscale|
+-------+-------+ +---------+----------+
| |
| Tailscale | localhost forwarding
| 100.x.x.x:16768 |
+------------------------>|
v
+---------+----------+
| WSL Paseo daemon |
| 127.0.0.1:6768 |
+---------+----------+
|
+---------+---------+
| coding agents |
| Codex/Claude/Pi… |
+-------------------+
The key configuration choices are:
WSL networking: NAT
Paseo daemon: 127.0.0.1:6768
Windows GUI: localhost:6768
Phone Paseo: <Windows Tailscale IP>:16768
Paseo Relay: disabled
The key Windows command is:
tailscale serve --bg --tcp=16768 tcp://127.0.0.1:6768
This keeps the development environment entirely in WSL, allows the native Windows GUI to control it locally, provides private phone access through Tailscale, avoids the Node/mirrored-networking problem, and leaves Paseo Relay disabled.