> ## Documentation Index
> Fetch the complete documentation index at: https://docs.asteroid.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Asteroid Tunnel

> Run a small tunnel connector inside your network. It dials out over HTTPS, so your agents reach internal apps with no inbound ports and no VPN.

**Asteroid Tunnel** lets agents reach apps that only exist inside your network, such as an EMR on a clinic LAN. You install a small program, the **tunnel connector**, on one or more machines inside that network. Each tunnel connector dials out to `tunnel.asteroid.ai` on port 443 and holds the connection open. Agent traffic comes back down that connection and leaves from the tunnel connector, so the apps see a request from inside your network.

Nothing listens on your network. You open no inbound ports and run no VPN.

## Prerequisites

Before you start, ensure that:

* **Your plan includes Asteroid Tunnel.** It is not in any standard plan. Contact the Asteroid team to add it.
* **You have two or more machines to run the tunnel connector on.** One works, but restarting or updating it stops your runs. With two, runs move to the second machine while the first restarts or updates.
* **Each machine can reach the apps your agents use**, and is one of:
  * 64-bit Windows. The tunnel connector runs as a service.
  * Linux with systemd and `dpkg` or `rpm`.
  * Any host that runs containers.

## Open the firewall

| Direction | Destination | Port | Why |
| - | - | - | - |
| Outbound | `tunnel.asteroid.ai` | 443 | The tunnel itself, and the install scripts |
| Outbound | `storage.googleapis.com` | 443 | Installers and updates |
| Outbound | `ghcr.io` | 443 | Container image only |
| Inbound | Nothing | None | The tunnel connector only dials out |

**TLS inspection breaks the tunnel.** The tunnel connector holds a long-lived WebSocket. A proxy that decrypts and re-signs TLS, or that cuts idle connections, drops it. Exempt `tunnel.asteroid.ai` from inspection.

**Outbound proxy.** If the machines reach the internet only through a proxy, set `HTTPS_PROXY` for the tunnel connector:

* **Windows:** set it as a machine environment variable, then restart the `AsteroidTunnel` service.
* **Linux:** run `systemctl edit asteroid-tunnel`, add `Environment=HTTPS_PROXY=http://proxy.example:3128` under `[Service]`, then restart.
* **Container:** pass `-e HTTPS_PROXY=...`.

## Create the tunnel

1. In **Environments → Gateways**, click **New gateway** and pick **Asteroid Tunnel**.
2. Name it. Set [failover](#failover) and [health probes](#health-probes) now, or later with **Edit**.
3. Click **Create gateway**.
4. [Pick the tunnel on an environment](/networking/overview#use-a-gateway).

## Install a tunnel connector

1. Open the tunnel, go to **Setup keys**, and click **Create setup key**.
2. Choose whether the key is reusable and when it expires, then click **Create key**.
3. Copy the key and the install command. **The key is shown once.** It enrolls machines only. Revoking it later leaves enrolled machines running.
4. Run the command on each machine.

**Windows**

In an elevated PowerShell:

```powershell theme={null}
& ([scriptblock]::Create((irm https://tunnel.asteroid.ai/tunnel/install.ps1))) -Key astun_...
```

The script downloads the MSI and checks its checksum. It installs to `C:\Program Files\Asteroid\Tunnel` and starts the `AsteroidTunnel` service. The service starts on boot and restarts if it fails.

**MSI instead.** Deploy the MSI from the release with your own tooling. The MSI installs the binary only. Then run `"C:\Program Files\Asteroid\Tunnel\asteroid-tunnel.exe" install --key astun_...` once on each machine.

Logs: `C:\ProgramData\Asteroid\Tunnel\asteroid-tunnel.log` and the Application event log, source `AsteroidTunnel`.

**Linux**

As root:

```bash theme={null}
curl -fsSL https://tunnel.asteroid.ai/tunnel/install.sh | sudo sh -s -- --key astun_...
```

The script installs the `.deb` or `.rpm` for your architecture, enrolls the machine, and enables the `asteroid-tunnel` systemd unit. The machine's identity lives in `/var/lib/asteroid-tunnel`, readable by root only.

Logs: `journalctl -u asteroid-tunnel`.

**Container**

```bash theme={null}
docker run -d --restart=always --name asteroid-tunnel \
  -e ASTEROID_TUNNEL_SETUP_KEY=astun_... \
  -v asteroid-tunnel:/var/lib/asteroid-tunnel \
  ghcr.io/asteroidai/asteroid-tunnel:stable
```

The tunnel connector enrolls on first start and keeps its identity in the volume. **Keep the volume.** Without it, the next start enrolls a new tunnel connector. On Kubernetes, use a StatefulSet with a persistent volume, or a Deployment with one replica per volume. The container runs as user 1001, so set `securityContext.fsGroup: 1001` on the pod to let it write the volume.

Containers do not update themselves. To update, pull the new image, then recreate the container with the same volume. Pin a version tag instead of `:stable` to control when that happens.

Within a minute the machine shows as **Online** under **Machines**.

Check a machine locally at any time. On Windows, run it from `C:\Program Files\Asteroid\Tunnel`.

```bash theme={null}
asteroid-tunnel status
```

## Health probes

Asteroid can tell a tunnel connector whose network is broken from one that is just idle. Open the tunnel, click **Edit**, and set **Probe targets**: one `host:port` per line inside your network that your agents need, such as `emr.clinic.local:443`. Each tunnel connector connects to them every 15 seconds. Change that with **Probe every (seconds)**. A tunnel connector that fails two checks in a row shows as **Unhealthy** and takes no runs. A new tunnel connector counts as unhealthy until its first check passes. Environments on it follow the [failover policy](#failover). The failing target and its error show under the tunnel connector.

The tunnel shows **Degraded** when fewer tunnel connectors are healthy than **Healthy machines to be ready**. It shows **Connecting** when none are.

## Failover

Each environment uses one tunnel connector for as long as it runs, so the apps always see the same source IP. If that tunnel connector goes away, the tunnel does what **When a machine is lost** says:

* **Move** (default). The environment moves to another tunnel connector, and the next request comes from that machine's IP. Most apps do not notice.
* **Fail.** The environment's network calls fail until it ends. Choose this for apps that bind a login session to one IP and would log the agent out, or worse, mix sessions.

Change it with **Edit** on the tunnel.

## What a tunnel connector can reach

A tunnel connector reaches anything the machine can reach. To limit that, firewall the machine's outbound traffic to the systems Asteroid needs.

A tunnel connector never reaches its own machine (`localhost`, `127.0.0.0/8`, `::1`) or the cloud metadata address.

## Maintenance and updates

**Drain before planned work.** A reboot or a stopped service cuts the runs on that machine. Before you patch or restart a machine:

1. Click **Drain** on the tunnel connector, or run `asteroid-tunnel drain --wait` on the machine.
2. New runs go to other tunnel connectors. Runs already on this one finish first. `--wait` returns when they have.
3. Do the work. The tunnel connector takes runs again after it restarts.

Stopping the Windows service or the systemd unit also drains first, for up to 5 minutes. A system shutdown gives far less time than that, so drain first.

**Updates.** Tunnel connectors update themselves to new Asteroid releases, one machine per tunnel at a time. A machine drains, updates and restarts. The next one waits until it is back and healthy. A tunnel with one tunnel connector is never updated this way. Every release is signed, and a tunnel connector rejects a release whose signature does not match.

To control updates yourself, turn auto-update off when you enroll:

* **Windows:** run `asteroid-tunnel.exe install --key astun_... --no-auto-update` after installing the MSI.
* **Linux:** install the package yourself, then run `asteroid-tunnel enroll --key astun_... --no-auto-update` and `systemctl enable --now asteroid-tunnel`.

Then upgrade with your own tooling. The [releases endpoint](https://tunnel.asteroid.ai/tunnel/v1/releases/stable) lists the current version and its files. Asteroid may refuse tunnel connectors that are many releases behind. If that happens, the tunnel connector shows as offline, and its log says the version is too old.

## Remove a machine

1. Drain it, and wait for its runs to finish.
2. Click **Revoke machine** on the tunnel connector. Its tunnels close now, and that identity can never connect again.
3. Uninstall the tunnel connector from the machine:
   * **Windows:** `asteroid-tunnel uninstall`, then remove **Asteroid Tunnel** in **Apps & features**
   * **Linux:** `apt remove asteroid-tunnel` or `dnf remove asteroid-tunnel`, then delete `/var/lib/asteroid-tunnel`
   * **Container:** delete the container and its volume.

A revoked machine can rejoin only with a new setup key.

## Troubleshooting

| You see | Likely cause | Fix |
| - | - | - |
| The tunnel connector never appears | Port 443 to `tunnel.asteroid.ai` is blocked, or TLS inspection is on | Check the firewall table above. Read the tunnel connector's log. |
| It flips between Online and Offline | A proxy or firewall cuts long-lived connections | Exempt `tunnel.asteroid.ai` from inspection and idle timeouts. |
| `401` in the log | The tunnel connector was revoked, or its identity file was replaced | Enroll the machine again with a new setup key. |
| **Unhealthy** with a probe error | The machine cannot reach that target | Check DNS and routes from the machine to the target. |
| The log says the relay refused this version as too old | The tunnel connector missed updates and auto-update is off | Upgrade it with the installer. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.