Skip to main content
Teleoperation normally runs over your local network, with the headset and the robot’s computer on the same Wi-Fi. To drive Axol from anywhere over the internet, put the Meta Quest and the robot’s computer on the same Tailscale network (a “tailnet”). The headset then reaches the robot by its Tailscale address as if it were on the same network, and the traffic is encrypted. Tailscale isn’t in the Meta Quest Store, so you have to sideload it. This guide covers that, then the connection.
Remote teleop is sensitive to latency. Tailscale usually connects the two devices directly. When it can’t, it sends traffic through a relay, which adds latency. You get the best results on connections with a low round-trip time.

Sideload Tailscale on the Quest

The Quest runs Horizon OS, which is based on Android, so you can install Tailscale’s Android app from its APK file.
1

Enable Developer Mode on the Quest

  1. Open the Meta Quest app on your phone, with your headset connected.
  2. Go to Menu → Devices, select your headset, then Headset Settings → Developer Mode, and turn it on. You may need to create a free Meta “organization” first, which takes a minute.
  3. Restart the headset.
2

Download the Tailscale APK

Download the Android APK from Tailscale’s package server at pkgs.tailscale.com/stable/#android onto your computer.
Download the APK only from Tailscale’s site, not from third-party APK mirrors.
3

Sideload it with SideQuest

The easiest tool is SideQuest:
  1. Install SideQuest on your computer from sidequestvr.com.
  2. Connect the Quest to your computer with a USB-C cable.
  3. Put the headset on and accept the Allow USB debugging? prompt. Check Always allow from this computer.
  4. In SideQuest, press Install APK from folder and select the Tailscale APK.
If you already use ADB, you can skip SideQuest and run adb install tailscale.apk.
4

Launch and log in

Tailscale doesn’t appear in your normal app grid. Find it under Unknown Sources in your Quest app library (the folder icon with a ”?”). Open it, sign in to your tailnet, and approve the VPN connection prompt.
The Android app isn’t built for VR, so signing in inside the headset can be awkward. Signing in with an auth key or QR code is often easier than typing a password.
A sideloaded Tailscale doesn’t update itself. Sideload a newer APK from time to time.

Connect the robot’s computer to the same tailnet

On the robot’s computer, install Tailscale and start it (see Tailscale’s Linux install docs):
Sign in to the same tailnet as the headset. Note the computer’s Tailscale address: its 100.x.y.z IP or its MagicDNS name. Run tailscale ip -4, or check the Tailscale admin console.

Drive the robot

With both devices on the tailnet, use teleoperation as usual, with the Tailscale address instead of the local one:
1

Start teleop on the robot's computer

Start it from the control panel or with axol teleop, as for local teleop.
2

Check that Tailscale is connected in the headset

Open the Tailscale app on the Quest and check that the VPN is on.
3

Enter the Tailscale address in the VR app

Open the VR app at axol.almond.bot on the headset. In the Connect field, enter the robot computer’s Tailscale IP or MagicDNS name instead of its local IP. Press Connect, then Start.
4

Authorize the certificate

As with local teleop, accept the self-signed certificate once. Tap Authorize certificate when prompted, or open https://<tailscale-address>:8000 in the headset browser and continue past the warning. See Authorize the TLS certificate.
The session then works like local teleop, with the same controls and camera views.

Drive from outside your tailnet (Funnel + TURN)

The steps above assume the operator can join your tailnet. To let someone drive without installing Tailscale, publish the robot on the internet with Tailscale Funnel. You need two pieces: Funnel carries the control connection, and a TURN server carries the camera video.
Funnel puts the robot on the public internet, and the teleop connection has no password. Anyone with the URL can drive the arms. Turn Funnel on only during a session, and turn it off as soon as you’re done:

1. Publish the control connection with Funnel

Funnel publishes only on ports 443, 8443 and 10000, and it handles TLS itself. Point it at the VR server’s HTTPS port and tell it to skip the self-signed certificate check:
This publishes the control connection at https://<your-machine>.tailXXXX.ts.net/ on port 443. Check it with tailscale serve status. If your Tailscale version doesn’t accept this command, use these two instead:

2. Add a TURN server for camera video

Camera video travels as a separate stream that doesn’t go through Funnel. When the headset and the robot are on different networks, they need a public TURN server to relay the video. Use a hosted TURN provider, or run coturn on a machine with a public IP. The repo includes a coturn setup for Fly.io in deploy/turn/. Fly supports the UDP traffic TURN needs, which most hosting services don’t. Follow deploy/turn/README.md to launch your own server with a few flyctl commands. Then set these variables on the robot’s computer before you start teleop:
AXOL_TURN_URL is a comma-separated list of turn:, turns: or stun: URLs. Without these variables, an operator outside your tailnet can still drive the arms through Funnel, but sees no camera video. On a local network or a tailnet, leave them unset.
Instead of exporting the variables in every shell, put them in a .env.local file in the directory you run axol from. axol loads it at startup. The file is ignored by git, so your TURN password stays out of version control.

3. Connect from the headset

In the VR app’s Connect field, enter the Funnel address as an https:// URL, or add :443. Otherwise the app uses the local default port, 8000.
Funnel uses a valid public certificate, so there’s no certificate to authorize. Press Connect, then Start.

Next steps

Teleoperation

The full teleop flow and controller layout.

VR Interface

The WebXR app, camera views, and connection details.