A modern, open-source alternative to localtunnel. Bundles client & server to host your own tunnel infrastructure.
npm install pipenet
Local Development
Share your local server with teammates, test webhooks, or demo work without deploying.
SDK Integration
Embed pipenet in your own tools to provide tunneling capabilities. mcp-proxy uses pipenet to connect local MCP servers with remote AI clients.
Self-Hosted Infrastructure
Run your own tunnel server for full control over security, domains, and availability.
Client
# Expose local port npx pipenet client --port 3000 # Custom subdomain npx pipenet client --port 3000 \ --subdomain myapp # Your own server npx pipenet client --port 3000 \ --host https://tunnel.example.com
Server
# Start server npx pipenet server --port 3000 # Custom domain npx pipenet server --port 3000 \ --domain tunnel.example.com # Cloud-ready npx pipenet server --port 3000 \ --tunnel-port 3001
| Feature | pipenet | localtunnel |
|---|---|---|
| Cloud deployment | single-port | random ports |
| Multiple domains | ✓ | — |
| TypeScript | ✓ | — |
| ES Modules | ✓ | — |
| Maintenance | Active | Limited |
| WebSocket | ✓ | ✓ |
| Protocol | Notes |
|---|---|
| HTTP / HTTPS | Standard request/response |
| WebSocket | Full duplex via HTTP upgrade |
| SSE | Long-lived HTTP connections |
| HTTP Streaming | Chunked transfer encoding |
import { pipenet } from 'pipenet'; const tunnel = await pipenet({ port: 3000 }); console.log(tunnel.url); // https://abc123.pipenet.dev tunnel.on('request', (info) => console.log(info.method, info.path)); tunnel.on('close', () => console.log('closed'));
Client Options
| port | number Local port to expose |
| host | string Tunnel server URL |
| subdomain | string Request specific subdomain |
| localHost | string Proxy to this hostname instead of localhost |
| localHttps | boolean Tunnel to local HTTPS server |
| allowInvalidCert | boolean Skip cert validation |
Events
| request | Fired on each proxied request with method and path |
| error | Fired when an error occurs |
| close | Fired when tunnel closes |
import { createServer } from 'pipenet/server'; const server = createServer({ domains: ['tunnel.example.com'], secure: true, tunnelPort: 3001, // Lifecycle hooks onTunnelCreated: (tunnel) => { console.log(`Tunnel created: ${tunnel.id} at ${tunnel.url}`); }, onTunnelClosed: (tunnel) => { console.log(`Tunnel closed: ${tunnel.id}`); }, onRequest: (req) => { console.log(`${req.method} ${req.path} via ${req.tunnelId}`); }, }); await server.tunnelServer.listen(3001); server.listen(3000);
Server Options
| domains | string[] Custom domain(s) for tunnel server |
| secure | boolean Require HTTPS |
| landing | string Redirect URL for root requests |
| maxTcpSockets | number Max sockets per client (default: 10) |
| tunnelPort | number Shared port for cloud deployments |
Lifecycle Hooks
| onTunnelCreated | Called when a new tunnel is created |
| onTunnelClosed | Called when a tunnel is closed |
| onRequest | Called on each proxied request |
Endpoints
| GET /api/status | Server status and tunnel count |
| GET /api/tunnels/:id/status | Status of specific tunnel |
| GET /:id | Request new tunnel with ID |