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() on 127.0.0.1:6768.
  • ss showed the Paseo socket in LISTEN.
  • Local curl connections 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.