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

# Persistent Tunnels

> Use a stable localhost tunnel hostname for repeat local tests and OAuth callbacks.

# Persistent Tunnels

Swarm can reuse the same public tunnel hostname for local tests instead of creating a new temporary URL on every run.

Persistent tunnels are useful when your app depends on callback URLs, redirect URLs, or OAuth flows that break when the public tunnel URL changes.

<Note>
  Persistent tunnels are provisioned automatically after `swarm login` or `useswarm-mcp login` when your Swarm workspace has the feature enabled. If provisioning is unavailable, local tests keep using Cloudflare Quick Tunnels automatically.
</Note>

## How it works

When persistent tunneling is enabled, Swarm assigns one stable hostname to your user and organization:

```text theme={null}
<user-id>-<org-slug>.swarmtunnel.com
```

For every local test, the CLI or MCP server:

1. Starts Swarm's local proxy in front of your app.
2. Opens a Cloudflare named tunnel to that proxy.
3. Reuses your stable hostname for the test URL.
4. Runs Swarm's cloud agents against that hostname.
5. Closes the local tunnel process when the test is done.

The local proxy behavior is the same as normal local testing: split frontend/backend routing, auth-origin rewriting, and localhost URL rewriting still work.

## CLI usage

Log in normally:

```bash theme={null}
swarm login
```

If your account can provision a persistent tunnel, login prints the hostname:

```text theme={null}
Persistent tunnel created
Hostname: https://<user-id>-<org-slug>.swarmtunnel.com
```

Run local tests the same way you already do:

```bash theme={null}
swarm test --url localhost:3000 --goal "Sign up for an account"
```

When the stored persistent tunnel is available, Swarm uses it automatically.

## Manual retry or revoke

Retry provisioning:

```bash theme={null}
swarm setup-tunnel
```

Revoke the current tunnel and clear local tunnel credentials:

```bash theme={null}
swarm setup-tunnel --revoke
```

After revocation, local tests fall back to temporary tunnels until you provision again.

## MCP usage

The MCP package uses the same persistent tunnel flow.

```bash theme={null}
npx @useswarm/mcp login
npx @useswarm/mcp setup-tunnel
npx @useswarm/mcp setup-tunnel --revoke
```

Inside Claude Code, Cursor, or another MCP host, `dev_test` automatically uses the persistent hostname when testing localhost.

## OAuth and localhost callbacks

OAuth providers often receive localhost callback URLs during development:

```text theme={null}
https://accounts.example.com/oauth?redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fauth%2Fcallback
```

That URL is valid on your machine, but many providers require the exact callback origin to be allowlisted. When Swarm runs through a persistent tunnel, the browser agent rewrites common redirect params such as `redirect_uri`, `callbackURL`, `redirectTo`, and `returnTo` to your stable tunnel hostname before the provider receives the request:

```text theme={null}
https://accounts.example.com/oauth?redirect_uri=https%3A%2F%2F<user-id>-<org-slug>.swarmtunnel.com%2Fauth%2Fcallback
```

Swarm also rewrites provider redirect responses that point back to localhost:

```text theme={null}
http://localhost:3000/auth/callback?code=abc
```

to:

```text theme={null}
https://<user-id>-<org-slug>.swarmtunnel.com/auth/callback?code=abc
```

The rewrite preserves the callback path, query string, and hash. Your app still receives the request through the tunnel and local proxy.

If your OAuth provider requires HTTPS callback URLs and does not allow localhost, add the persistent HTTPS callback URL to the provider allowlist once:

```text theme={null}
https://<user-id>-<org-slug>.swarmtunnel.com/auth/callback
```

Because the hostname is stable, you do not need to update the allowlist for every test run.

<Warning>
  Use test OAuth apps and test accounts for Swarm runs. Do not use production credentials for automated development testing.
</Warning>

## Split frontend/backend apps

Persistent tunnels still support split frontend/backend setups:

```bash theme={null}
swarm test \
  --url localhost:3000 \
  --backend localhost:8080 \
  --goal "Create a project"
```

Swarm opens one tunnel to the local proxy. The proxy routes API paths to the backend and everything else to the frontend.

Default backend paths:

| Path            | Destination |
| --------------- | ----------- |
| `/api/*`        | Backend     |
| `/auth/*`       | Backend     |
| `/graphql`      | Backend     |
| `/trpc/*`       | Backend     |
| Everything else | Frontend    |

Use `--backend-paths` for custom backend prefixes.

## One active session

Only one active test session can use a persistent tunnel at a time. If a second local test starts while the first still holds the tunnel, Swarm returns a clear conflict message.

If a client crashes, Swarm automatically treats the lease as stale after missed heartbeats and lets the next run recover.

## Troubleshooting

### Login says persistent tunnel was not provisioned

Your test can still run. Swarm falls back to temporary Cloudflare Quick Tunnels.

Try again later:

```bash theme={null}
swarm setup-tunnel
```

### A second test says the tunnel is already in use

Wait for the first run to finish, call `dev_close` in your MCP host, or revoke and re-provision:

```bash theme={null}
swarm setup-tunnel --revoke
swarm setup-tunnel
```

### OAuth redirects still fail

Check which callback URL your provider receives in `redirect_uri` or sends back in `Location`. Localhost callback values are rewritten automatically. HTTPS provider allowlists still need to include the stable tunnel callback if the provider does not allow localhost redirect URIs.

## Operator setup

Self-hosted or internal deployments need Cloudflare credentials on the Swarm API before users can provision persistent tunnels. See the repository operator runbook at `docs/persistent-tunnels.md`.
