Portal - Self-Hostable Relay Tunnel for Localhost

Expose local services through self-hosted or public relays.
No port forwarding. No inbound firewall rules. No manual DNS setup. No accounts.
Why Portal?
Portal is a local tunnel runtime and relay network for publishing services to the agentic web. It publishes local apps, APIs, tools, and agents through self-hosted or public relays, keeps routing and x402 payment policy in the tunnel process, and avoids requiring a hosted vendor account.
- Self-Hostable, Fully Open Source - Run your own relay with a single
- Anonymous Relay Network - Connect to public relays without a hosted
- Opt-in Static Cache -
portal expose --serve ./dist --cacheoffloads
- End-to-End Tenant TLS And ECH - For ordinary uncached exposures, Portal
- Built-in MITM Detection - Portal actively self-probes its own connection
- No Accounts, No API Keys - Authentication uses SIWE-compatible signing
- Built-in x402 Payments - Routed HTTP paths can require Sui gasless
/x402/client.js, and native clients can call /x402/prepare directly and
send X-PAYMENT.
Comparison
| | Portal | ngrok | Cloudflare Tunnel | frp | |---|---|---|---|---| | Public localhost URL | Yes | Yes | Yes | Yes | | Self-hostable | Yes | Enterprise only | No | Yes | | Open source | MIT | No | Client only | Apache 2.0 | | Custom domain | Yes | Paid plans | Yes | Yes | | End-to-end tenant TLS | Yes (uncached exposures) | No | No | No | | SNI hiding (ECH) | Yes (uncached exposures) | No | No | No | | MITM self-probe | Built-in (uncached exposures) | No | No | No | | Multi-relay failover | Yes | Managed | Built-in | No | | Account required | No | Yes | Yes | No | | Native x402 payments | Yes | No | No | No |
Quick Start
Use the local AI agent plugin
The repository includes a portal-deploy plugin for Codex, Claude Code, and Cursor. The shared portal-expose skill inspects a local app, opens a Portal tunnel, configures explicitly requested x402 paid routes, verifies the public URL and payment challenge, and hands off the lifecycle.
Install the skill with either CLI:
# GitHub CLI
gh skill install gosuda/portal-tunnel portal-deploy/portal-expose
skills CLI
npx skills add gosuda/portal-tunnel --skill portal-expose
Then ask your agent:
- Temporary preview: “Expose this app with Portal and verify the public URL.”
- x402 paid route: “Expose this app with Portal, protect
GET /paidwith x402, and verify the payment challenge.” - Persistent tunnel: “Keep this app available with a persistent Portal agent tunnel and verify the public URL.”
Expose a local service
macOS / Linux:
curl -fsSL https://github.com/gosuda/portal-tunnel/releases/latest/download/install.sh | bash
portal expose 3000
Windows (PowerShell):
$ProgressPreference = 'SilentlyContinue'
irm https://github.com/gosuda/portal-tunnel/releases/latest/download/install.ps1 | iex
portal expose 3000
Portal prints a public HTTPS URL for your local app instantly. More examples:
# Custom name and relay
portal expose 3000 --name myapp --relays https://portal.example.com --discovery=false
Prefer IVNP overlay transport for the reverse stream
portal expose 3000 --overlay
Mount frontend and API behind one URL
portal expose --name myapp \
--http-route /api=http://127.0.0.1:3001 \
--http-route /=http://127.0.0.1:5173
Require Sui USDC x402 payment before proxying a route
portal expose --name paid-app \
--http-route "/paid=http://127.0.0.1:3001 GET:0.01" \
--http-route /=http://127.0.0.1:5173 \
--x402-pay-to 0x...
Raw TCP port (Minecraft, databases, SSH)
portal expose localhost:25565 --name minecraft --tcp
IVNP overlay transport. --overlay prefers IVNP-routed overlay transport: Portal still selects and authorizes the two endpoints (public ingress and overlay gateway) and issues the delegated reverse capability, while IVNP owns the network path between gateway and ingress — it may carry that one logical hop over multiple internal I2P-style router hops that are invisible to Portal. Direct reverse transport remains the default and the fallback; Portal-level multi-hop routing is not a feature (the old relay-chain model and WireGuard mesh were removed). Portal selects and authorizes endpoints. IVNP connects destinations. Portal does not own the path between them. See IVNP overlay transport for the canonical explanation.
See CLI Reference for the full route syntax and API Reference for the x402 helper endpoints.
Manage persistent tunnels manually
Use Portal Agent directly when tunnels should keep running outside your terminal. It runs as a local OS service, keeps every tunnel in one TOML config alive, and provides a dashboard for public relay management.
portal agent run --config config.toml
portal agent dashboard --config config.toml
portal agent restart --config config.toml
portal agent stop --config config.toml
Foreground mode skips OS service installation.
portal agent run --config config.toml --foreground
See Portal Agent for the config format.
Run your own relay
git clone https://github.com/gosuda/portal-tunnel
cd portal-tunnel && cp .env.example .env
docker compose up
For public deployment with DNS automation (ACME), TCP/UDP port ranges, and relay policy, see Deployment.
How End-to-End Encryption Works
Browser
-> Relay SNI router (reads only routing token, forwards raw bytes)
-> Reverse session
-> Portal tunnel (performs TLS handshake locally, derives session keys)
-> Local service
- The relay accepts the incoming connection and reads only the TLS ClientHello
- It forwards the raw encrypted stream over the reverse session without
- The Portal tunnel on your side completes the TLS handshake locally. Session
- For relay-hosted domains, the tunnel obtains certificate signatures via
/v1/sign, using the relay only as a keyless signing oracle. The relay signs
handshake digests but never receives session keys.
- After the handshake, the relay continues forwarding ciphertext without access
When ECH is enabled, the relay also cannot see the actual tenant hostname. It routes by an opaque token derived from the tunnel identity, while the real SNI stays inside the ECH-protected ClientHello.
Public Relay Registry
Portal's official public relay registry is:
https://raw.githubusercontent.com/gosuda/portal-tunnel/main/registry.json
Tunnel clients include this registry by default. If you operate a public Portal
relay, open a pull request to add your relay URL to registry.json.
Documentation
- CLI Reference
- Concepts
- Portal Agent
- Wallet and ENS
- Security Model
- Architecture
- Deployment
- Configuration Reference
Contributing
See CONTRIBUTING.md.
License
MIT License - see LICENSE.