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

# Internal App Support

> Perfai's agents run from the cloud and need a public URL. Use ngrok to create a secure reverse tunnel into apps running on localhost, a private network, or behind a firewall.

## Overview

Perfai's Vision, Security, and Fix agents run in Perfai's cloud — they open a cloud browser and navigate to your app's URL over the public internet. If your app only exists on `localhost`, inside a corporate VPN, on a private staging network, or behind a firewall with no public DNS entry, Perfai's agents can't reach it directly.

[ngrok](https://ngrok.com) solves this with a **reverse tunnel**: a lightweight agent you run next to your app opens an outbound connection to ngrok's edge, which then hands you a public HTTPS URL that forwards every request straight back to your local or internal app. Paste that public URL into Perfai like you would any other app URL, and testing proceeds exactly as normal — your app never needs to be deployed anywhere public.

<Note>
  Nothing changes on the Perfai side. Perfai always works from a single URL — see [Adding your first app](/docs/core-concepts/apps#adding-your-first-app). An ngrok URL is just a public URL that happens to forward to a non-public destination.
</Note>

***

## When you need this

<CardGroup cols={2}>
  <Card title="Local development" icon="laptop">
    Your app only runs on `localhost` and hasn't been deployed to a staging or production environment yet.
  </Card>

  <Card title="Private/internal networks" icon="network-wired">
    Your staging environment lives behind a VPN, on a corporate intranet, or on internal-only DNS.
  </Card>

  <Card title="Firewalled environments" icon="shield-halved">
    Your app is reachable only from an allowlisted set of IPs that doesn't include Perfai's cloud infrastructure.
  </Card>

  <Card title="Pre-launch products" icon="rocket">
    You want a full security scan before your app is ever exposed to the public internet.
  </Card>
</CardGroup>

***

## Install ngrok

Pick the install method for your platform.

<Steps>
  <Step title="Install the ngrok agent">
    <CodeGroup>
      ```bash macOS (Homebrew) theme={null}
      brew install ngrok/ngrok/ngrok
      ```

      ```bash Windows (Chocolatey) theme={null}
      choco install ngrok
      ```

      ```bash Linux (Snap) theme={null}
      snap install ngrok
      ```

      ```bash npm theme={null}
      npm install -g ngrok
      ```
    </CodeGroup>

    No package manager? Download a prebuilt binary for your OS from [ngrok.com/download](https://ngrok.com/download) and unzip it somewhere on your `PATH`.

    Confirm it's installed:

    ```bash theme={null}
    ngrok version
    ```
  </Step>

  <Step title="Create a free ngrok account">
    Sign up at [dashboard.ngrok.com/signup](https://dashboard.ngrok.com/signup). The free tier is enough to run tunnels for Perfai testing — no paid plan required.
  </Step>

  <Step title="Connect your agent with an authtoken">
    Every ngrok agent must be authenticated once. Copy your token from [dashboard.ngrok.com/get-started/your-authtoken](https://dashboard.ngrok.com/get-started/your-authtoken) and run:

    ```bash theme={null}
    ngrok config add-authtoken <YOUR_AUTHTOKEN>
    ```

    This writes the token to ngrok's config file so you don't need to pass it again.
  </Step>
</Steps>

***

## Start a tunnel to your app

Run ngrok against the port your app is already listening on. For an app running on `localhost:3000`:

```bash theme={null}
ngrok http 3000
```

For an app on an internal host or a non-standard port, pass the full origin:

```bash theme={null}
ngrok http https://internal-staging.mycompany.local:8443
```

ngrok prints a status screen with a **Forwarding** line — this is the public URL Perfai will use:

```
Session Status                online
Account                       you@example.com (Plan: Free)
Forwarding                    https://a1b2-203-0-113-42.ngrok-free.app -> http://localhost:3000
```

<Warning>
  Copy the `https://...ngrok-free.app` address, not the `http://localhost` one. Perfai's cloud agents can only reach the public ngrok URL.
</Warning>

Leave this terminal window running — closing it, or your machine going to sleep, ends the tunnel and Perfai loses access to your app.

***

## Point Perfai at the tunnel

Once the tunnel is up, use the ngrok URL exactly like any other app URL.

<Steps>
  <Step title="Copy the Forwarding URL">
    From the ngrok status screen, copy the full `https://` forwarding address, e.g. `https://a1b2-203-0-113-42.ngrok-free.app`.
  </Step>

  <Step title="Add it as a new app">
    In Perfai, go to **Apps → + New App** and paste the ngrok URL into the input field, then hit the arrow button — the same flow described in [Adding your first app](/docs/core-concepts/apps#adding-your-first-app).
  </Step>

  <Step title="Let the agents run">
    Perfai's Vision Agent opens a cloud browser and connects to the ngrok URL over the public internet, which ngrok forwards straight through to your local or internal app. Account creation, app mapping, and security testing proceed exactly as they would for any public app — see the [Quickstart](/docs/quickstart) for the full walkthrough.
  </Step>
</Steps>

<Tip>
  Keep the app itself running for the full duration of the scan, not just the tunnel. App mapping can take up to 10 minutes, and security testing continues after that — the tunnel has nothing to forward to if your local server has stopped.
</Tip>

***

## Keeping the tunnel stable for continuous testing

Perfai re-maps and re-tests apps on an ongoing schedule (see [Managing an app](/docs/use-cases/managing-an-app)). A tunnel that disappears or changes URL between scans breaks that schedule, so treat the tunnel as part of your app's uptime for as long as you want continuous coverage.

<AccordionGroup>
  <Accordion title="The free ngrok URL changes every time I restart the tunnel">
    Free ngrok URLs are randomly generated per session. If you stop and restart `ngrok http`, you get a **different** `.ngrok-free.app` address, which breaks Perfai's stored URL for that app.

    Fix it with a **static domain** — ngrok's free plan includes one reserved domain that stays the same across restarts. Claim one from [dashboard.ngrok.com/domains](https://dashboard.ngrok.com/domains), then start the tunnel with:

    ```bash theme={null}
    ngrok http --url=your-reserved-name.ngrok-free.app 3000
    ```

    Use that fixed URL in Perfai and it survives tunnel restarts.
  </Accordion>

  <Accordion title="Can ngrok run unattended, e.g. for nightly scans?">
    Yes — run the agent as a background service instead of a foreground terminal command so it survives logout and reboots. On Linux, run it under `systemd` or a process manager like `pm2`; on macOS, a `launchd` agent or a terminal multiplexer (`tmux`/`screen`) works well. The command is the same either way: `ngrok http --url=your-reserved-name.ngrok-free.app 3000`.

    Combine this with a static domain so the URL Perfai has on file never goes stale.
  </Accordion>

  <Accordion title="My app's URL changed — how do I update it in Perfai?">
    Open the app's dashboard, go to **Actions → Edit → Advanced Options**, and update the app URL there. See [Managing an app](/docs/use-cases/managing-an-app) for the full Configuration panel reference.
  </Accordion>
</AccordionGroup>

***

## Securing the tunnel

Exposing an internal app publicly, even temporarily, widens its attack surface beyond Perfai's own agents. Apply these restrictions so only Perfai — and you — can reach it:

* **Restrict by IP where possible.** ngrok's [traffic policy](https://ngrok.com/docs/traffic-policy/) lets you allow only specific source IPs on a tunnel. This is the strongest option if your network setup supports it.
* **Add basic auth as a lightweight gate.** Perfai's cloud browser doesn't support interactive auth prompts, so don't combine this with Perfai testing unless you're only using the tunnel for manual access. For agent-driven scans, prefer IP restriction or simply limiting the tunnel's lifetime instead.
* **Only run the tunnel while you need it.** Stop `ngrok http` when a scan finishes if you don't need continuous coverage — a tunnel that isn't running can't be reached by anyone.
* **Don't tunnel into production.** Point ngrok at a staging or test environment with non-production data and test accounts, consistent with how Perfai testing works generally — see [Bring your own accounts](/docs/use-cases/bring-your-own-accounts).

***

## Frequently asked questions

<AccordionGroup>
  <Accordion title="Do I need a paid ngrok plan?">
    No. The free tier — including one free static domain — is enough to run Perfai scans against an internal app. Paid plans add more static domains, higher connection limits, and team-shared tunnels, which matter more for larger or longer-running setups.
  </Accordion>

  <Accordion title="Does Perfai support other tunneling tools, like Cloudflare Tunnel or localtunnel?">
    Perfai only needs a stable, public HTTPS URL that forwards to your app — it has no dependency on ngrok specifically. Any reverse-tunnel tool that gives you a persistent public URL works the same way. ngrok is documented here because it's the fastest to set up and has a generous free tier.
  </Accordion>

  <Accordion title="Will Perfai's traffic show up in my ngrok dashboard?">
    Yes. ngrok's web interface (`http://127.0.0.1:4040` while a tunnel is running, or the dashboard at [dashboard.ngrok.com](https://dashboard.ngrok.com)) logs every request passing through the tunnel, including Perfai's Vision and Security Agent traffic — useful for confirming the agents are actually reaching your app.
  </Accordion>

  <Accordion title="Testing stalled partway through — what should I check first?">
    In order: confirm the `ngrok http` process is still running, confirm your local app is still up (the tunnel has nothing to forward to otherwise), and confirm the URL on file in Perfai's **Advanced Options** still matches the current ngrok forwarding address.
  </Accordion>
</AccordionGroup>
