# How do I expose a local AI agent to the internet?

*2026-10-21 · By Naol Ketema, founder of Agent Identity*

**Short answer:** Run a tunnel: a small client on your machine dials out to a public edge server over a WebSocket, and the edge forwards inbound HTTPS requests back down that connection to your local port. With Agent Identity, aid tunnel create reserves a hostname for an agent and aid tunnel connect forwards it to localhost. ngrok and Cloudflare Tunnel work the same way and offer more general features.

## Why a local agent cannot be reached

An agent on a laptop, behind a home router's NAT, or in a container with no ingress can make outbound requests but cannot receive inbound ones. There is no public address that routes to it. That is a problem as soon as something needs to call the agent: a webhook, a callback from another API, or another agent sending it work.

A tunnel solves this without opening ports. The local side dials out, which networks allow, and keeps the connection open. A public edge server accepts requests for a hostname and sends them down that connection. Responses travel back the same way.

## How Agent Identity tunnels work

An Agent Identity tunnel belongs to an agent. Creating one reserves a public hostname; connecting one opens the WebSocket to the edge and starts forwarding. Tunnels are Phase 2 scope in Agent Identity: the edge server, the frame protocol and the CLI client are implemented, but they sit outside the core of identity, email and durable events.

- Step 1: create the tunnel for an agent. The hostname defaults to the agent's handle.
- Step 2: connect it to a local URL. The process stays in the foreground and holds the WebSocket open.
- Step 3: stop it with Ctrl-C. The hostname goes offline immediately.

```bash
aid tunnel create --agent "$AID_AGENT" --hostname support
# Created tunnel 3c9f… — https://support.<tunnel domain>

aid tunnel connect 3c9f… --local http://localhost:3000
# Connecting tunnel 3c9f… -> http://localhost:3000... (Ctrl-C to stop)
# Connected. Forwarding requests to http://localhost:3000

aid tunnel list --agent "$AID_AGENT"
```

--local defaults to http://localhost:3000. The same create call over HTTP:

```bash
curl -sS -X POST "https://api.agent-identity.dev/v1/identities/$AID_AGENT/tunnels" \
  -H "authorization: Bearer $AID_KEY" \
  -H 'content-type: application/json' \
  -d '{"hostname":"support"}'
```

The response includes the hostname, status, an edge_url and connected: false. Creating a tunnel only reserves the name. connected becomes true when a client dials in, and GET /v1/tunnels/:id reports the live state.

## Authentication

The CLI authenticates the tunnel with the API key it is logged in with, sent as x-api-key alongside x-tunnel-id on the WebSocket upgrade. There is no separate session token to mint. Log the CLI in with the agent's own key rather than the org key, so a leaked laptop config exposes one agent and not the organization.

A tunnel makes your local server public. Anything reachable on that port is reachable from the internet while the tunnel is connected. Authenticate requests in your own server, and verify signatures on webhooks rather than trusting that only the sender knows the hostname.

## Agent Identity tunnels compared with ngrok and Cloudflare Tunnel

- ngrok: a mature, general-purpose tunnel. It handles HTTP and raw TCP, has a local request inspector with replay, custom domains, and traffic policies. It is the obvious choice when you just need a public URL for any local service.
- Cloudflare Tunnel: free to run and suited to long-lived exposure of services on a domain you manage in Cloudflare. Setup is heavier than ngrok for a quick test.
- Agent Identity tunnels: the hostname belongs to an agent, is created through the same API and CLI as its mailbox and keys, and is authenticated with that agent's key. The docs describe HTTP request forwarding; they do not describe raw TCP, a request inspector or replay.

The honest summary: if you only need a public URL for local development, ngrok or Cloudflare Tunnel will do it and do more. An Agent Identity tunnel makes sense when the agent already lives on Agent Identity and you want its public endpoint managed and authenticated alongside its other identity, with the same key and the same organization.

## Common uses

- Receiving webhooks during development. Agent Identity refuses webhook URLs that point at private or internal addresses, so a laptop needs a public hostname.
- Serving an HTTP endpoint for your own agent, for example an API another service calls.
- Testing callbacks from third-party APIs against code running on your machine.

## Self-hosting the edge

If you run Agent Identity yourself, the edge is a separate service, apps/tunnel-edge, that scales and deploys apart from the API. Two settings need real values in production: PUBLIC_TUNNEL_DOMAIN, the parent domain hostnames hang off, which needs a wildcard DNS record and a wildcard TLS certificate; and TUNNEL_EDGE_URL, the WebSocket URL clients dial.

## FAQ

### Is a tunnel safe for production?

A tunnel is a reasonable way to reach an agent that cannot have inbound networking, but the hostname is only up while the client process runs. For a service that must always answer, deploy it somewhere with real ingress.

### Do I need a tunnel to receive email or Agent2Agent tasks?

No. Agent Identity receives mail and Agent2Agent tasks on the platform and appends them to the event log. A local agent can long-poll the log from behind NAT, because that is an outbound request.

### Can I choose the hostname?

Yes, with --hostname on aid tunnel create or hostname in the API body. It defaults to the agent's handle, under the edge's public tunnel domain.

### Should I use ngrok instead?

If you just need a public URL for a local service, ngrok is mature and does more, including TCP and request inspection. Use an Agent Identity tunnel when you want the endpoint tied to an agent's identity and key.

---
Docs: https://www.agent-identity.dev/docs · All guides: https://www.agent-identity.dev/guides · llms.txt: https://www.agent-identity.dev/llms.txt