Skip to main content

Architecture Overview

Portless uses a proxy-based architecture to route requests from stable .localhost URLs to your dev servers running on ephemeral ports.

Proxy Implementation

The proxy is built with Node.js built-in modules (http, http2, net) with no external dependencies for the core routing logic.

Request Routing

The proxy inspects the Host header to determine which backend to route to:
Routing logic:
  1. Exact match: myapp.localhost matches a route registered for myapp.localhost
  2. Wildcard match: tenant1.myapp.localhost matches a route registered for myapp.localhost
  3. No match: Returns 404 with a list of active routes

HTTP/2 + HTTP/1.1 Support

When started with --https, the proxy creates an HTTP/2 secure server with HTTP/1.1 fallback:
The proxy uses byte-peeking to detect whether an incoming connection is TLS or plain HTTP:
This allows the same port to accept both HTTPS and HTTP requests.

Loop Detection

Portless detects forwarding loops (e.g., a Vite dev server proxying back through portless without rewriting the Host header):

Route Registration

Routes are stored in a JSON file at ~/.portless/routes.json (or /tmp/portless/routes.json for privileged ports).

File Locking

Route updates are protected by a directory-based lock to prevent race conditions:

Stale Route Cleanup

When loading routes, portless filters out stale entries whose owning process is no longer alive:

Route Format

  • hostname: The full hostname (e.g., myapp.localhost)
  • port: The local port where the app is listening
  • pid: Process ID of the app (0 for static aliases)

Port Assignment

Portless assigns ports in the range 4000-4999 by default.

Auto Port Discovery

Fixed Port Override

You can specify a fixed port instead of auto-assignment:

Environment Variables

Portless injects environment variables into child processes:

Framework Flag Injection

For frameworks that ignore PORT (Vite, Astro, React Router, Angular, Expo), portless auto-injects --port and --host flags:
Most frameworks respect PORT automatically (Next.js, Express, Nuxt, Hono, Flask, FastAPI). Flag injection is only needed for a small set of frameworks.

State Directory

Portless stores its state in a directory that depends on the proxy port:
  • Port >= 1024 (no sudo): ~/.portless/
  • Port < 1024 (requires sudo): /tmp/portless/

State Files

Override the state directory: