Skip to main content
Start with the Runloop Quickstart to use the examples below.
When developing software on your devbox, you will often want to expose local services running on your devbox to the outside world. For example, you may want to have your agent start a local web server to serve a frontend application and then expose the live frontend to your users. Other examples include:
  • remotely collaborating on a frontend project
  • testing a web service
  • accessing a Jupyter notebook running on your devbox
  • accessing a local database running on your devbox
Let’s use devbox tunnels to securely access ports on your devbox over a simple url.

Supported Protocols

devbox tunnels support multiple protocols, making them suitable for a wide variety of applications:
  • HTTP/HTTPS: Standard web traffic for REST APIs, web applications, and static content
  • WebSockets: Real-time bidirectional communication for chat applications, live updates, and interactive features
  • Server-Sent Events (SSE): One-way real-time communication from server to client for live data streams and notifications
You will need to explicitly specify the hostname 0.0.0.0 within your service to expose ports to the outside world. Using other IP addresses or localhost is incompatible with tunnels.
WebSockets: tunnel origin must be in your allow-list. If your application validates the Origin header on WebSocket upgrade requests (a standard cross-site WebSocket hijacking defence), you must add the tunnel URL pattern to its allow-list. Tunnel requests arrive with an Origin of https://{port}-{tunnel_key}.tunnel.runloop.ai, which will not match a typical localhost or APP_URL-based allow-list. When the check fails, the WS handshake returns 403 and real-time features break silently — HTTP traffic continues normally, so the tunnel appears healthy.The safest fix is to relax origin enforcement in non-production environments only. Alternatively, add https://*.tunnel.runloop.ai to your allow-list explicitly.

Setting up a tunnel

There are two ways to set up a tunnel: at devbox creation time, or after the devbox is running.

Option 1: Enable tunnel at devbox creation

The simplest approach is to enable the tunnel when creating the devbox. The tunnel will be automatically provisioned and available when the devbox is ready.

Option 2: Enable tunnel on a running devbox

You can also enable a tunnel on an existing running devbox using the enable_tunnel method.
1

Create a devbox

Create a devbox and start a service on it.
2

Enable the tunnel

Enable a tunnel on the running devbox.

Tunnel URL Format

Tunnel URLs follow this format:
Where:
  • {port} is the port number your service is running on (e.g., 8080, 3000)
  • {tunnel_key} is the encrypted key returned when you enable the tunnel
For example, if your tunnel key is abc123xyz and your service runs on port 3000:
You can access any port on your devbox by changing the port number in the URL, as long as your service is bound to 0.0.0.0.

Authentication Modes

Tunnels support two authentication modes:

Open Mode (Public Access)

With auth_mode: "open", anyone with the URL can access your tunnel. This is useful for:
  • Sharing live previews with collaborators
  • Testing webhooks from external services
  • Public demos

Authenticated Mode (Restricted Access)

With auth_mode: "authenticated", requests must include a bearer token. This is useful for:
  • Sensitive development environments
  • APIs that should not be publicly accessible
  • Secure internal tools
To access an authenticated tunnel, include the token in your requests:
While the devbox is active and the tunnel is enabled, the URL has remote access to all of your devbox ports. Treat tunnel URLs with the same care you would treat any exposed endpoint.

Tunnel Lifecycle

  • One tunnel per devbox: Each devbox can have one tunnel enabled at a time.
  • Persistent until shutdown: Once enabled, tunnels remain active until the devbox is shut down.
  • Survives suspend/resume: If you suspend and resume a devbox, the tunnel information is preserved (you’ll need to re-enable the tunnel after resume if needed).
  • Multiple ports: A single tunnel allows access to any port on your devbox - just change the port number in the URL.

Wake on HTTP

With wake_on_http enabled, HTTP traffic to the tunnel URL automatically resumes a suspended devbox. This lets you suspend devboxes when idle and only pay for compute when requests arrive. When a request hits a suspended devbox:
  1. The tunnel returns 503 Service Unavailable with a Retry-After: 5 header
  2. The devbox resumes (typically under a second of infrastructure overhead)
  3. The caller retries and the request is proxied to your running service
Webhook providers like GitHub, Stripe, and Slack automatically retry on 503 responses, so wake-on-HTTP works out of the box for webhook endpoints. For browsers, the tunnel returns an HTML page that auto-refreshes.
When running agents behind wake-on-HTTP webhooks, use Agent Gateways to protect your API credentials. Gateways prevent credential exfiltration by keeping secrets on Runloop’s servers — agents only see temporary gateway tokens.
For a fully automatic sleep/wake cycle, combine wake_on_http with an idle timeout:
With this configuration, the devbox suspends after 5 minutes of inactivity and wakes automatically when HTTP traffic arrives. HTTP traffic through the tunnel counts as activity (via http_keep_alive, enabled by default), so the devbox stays awake while requests are flowing.
WebSocket connections send periodic heartbeat frames that also count as activity for http_keep_alive. If your app rejects WebSocket upgrade requests — for example, because the tunnel origin isn’t in your allow-list — those heartbeats stop flowing and the devbox may suspend sooner than expected, even while a browser tab has the page open. See the WebSocket origin warning above if this affects you.

Example: Complete Tunnel Workflow

Here’s a complete example showing how to create a devbox, start a web server, and access it via a tunnel:

Advanced: Header Handling for Proxies

If you are placing another proxy in front of a Runloop tunnel, it is useful to understand how the tunnel resolves the target host and the exact behavior for headers forwarded upstream. For routing, the tunnel backend resolves the host in this order:
  1. X-Runloop-Host (requires an authenticated tunnel)
  2. Host
The proxy consumes the first value it finds and uses that value for tunnel routing. Standard proxy headers like Forwarded and X-Forwarded-Host are not used for routing — they are forwarded to the backend as-is and remain available for your application.

Forwarded Headers

For plain HTTP proxying, Runloop forwards request headers upstream except for hop-by-hop headers such as:
  • Connection
  • Transfer-Encoding
  • TE
  • Trailer
  • Proxy-Authorization
  • Proxy-Authenticate
  • Keep-Alive
For WebSocket connections, Runloop forwards the request headers as-is. Before proxying, Runloop also adds or updates a small set of headers:
  • x-runloop-request-id is added or replaced
  • x-forwarded-for is added
  • x-real-ip is added
  • x-forwarded-proto is added

Transparent Proxy Routing

The X-Runloop-Host header lets a fronting proxy override which tunnel hostname is used for routing, without rewriting the connection or Host header. This is useful when you want to serve tunnels behind a branded domain — your proxy sets X-Runloop-Host for routing while preserving the customer-facing Host for your application. For example, if your proxy at preview.customer.com needs to route to a Runloop tunnel:
Runloop uses X-Runloop-Host to resolve the tunnel, strips it from the request, and proxies to the devbox. Your application sees Host: preview.customer.com with no trace of the Runloop tunnel hostname, so cookies, redirects, and framework host validation behave normally. This works for both HTTP and WebSocket traffic.
X-Runloop-Host only works with authenticated tunnels and requires a valid Authorization: Bearer <token> header. Using it against an open tunnel or without a valid token returns 401.