HTTP/HTTPS Relay

Expose local web applications, APIs, and webhooks with automatic HTTPS, TLS termination, and host-based routing. Supports HTTP/1.1, HTTP/2, and WebSocket upgrades.


Overview

The HTTP/HTTPS relay is designed for web applications and APIs. It provides TLS termination at the relay, allowing you to serve HTTPS without managing certificates locally.

Perfect for:

  • Web Applications: React, Vue, Next.js, Django, Rails apps
  • APIs: REST APIs, GraphQL endpoints
  • Webhooks: GitHub, Stripe, Slack webhook testing
  • Development: Share local dev servers with team members

Key Features:

  • Host-based routing (subdomain.domain.com)
  • TLS termination at relay (automatic HTTPS)
  • HTTP/1.1 and HTTP/2 support
  • WebSocket upgrade support
  • JWT authentication for tunnel access
  • Optional plain HTTP support

How It Works

External Client → HTTP/HTTPS Relay → QUIC Tunnel → LocalUp Client → Local HTTP Server
    connects to      TLS termination      encrypted      forwards to
    myapp.localhost:18443  + host routing     tunnel     localhost:3000

HTTPS Flow:

  1. External client requests https://myapp.localhost:18443
  2. Relay terminates TLS using configured certificate
  3. Relay extracts Host header: myapp.localhost
  4. Relay routes to tunnel with matching subdomain myapp
  5. Request forwarded through QUIC tunnel to LocalUp client
  6. Client forwards HTTP request to local server at localhost:3000
  7. Response flows back through same path

HTTP Flow (optional):

  1. External client requests http://myapp.localhost:18080
  2. Relay extracts Host header: myapp.localhost
  3. Relay routes to tunnel with matching subdomain myapp
  4. Request forwarded through QUIC tunnel to LocalUp client
  5. Client forwards to localhost:3000

Setup Guide

Step 1: Generate TLS Certificates (one-time)

For development, use self-signed certificates:

openssl req -x509 -newkey rsa:4096 -nodes \
  -keyout key.pem -out cert.pem -days 365 \
  -subj "/CN=localhost" -addext "subjectAltName=DNS:localhost"

For production, use Let's Encrypt:

# Use certbot to obtain certificates
sudo certbot certonly --standalone -d example.com -d *.example.com

# Certificates will be in /etc/letsencrypt/live/example.com/
# Use fullchain.pem and privkey.pem

Step 2: Start the HTTP/HTTPS Relay

With both HTTP and HTTPS:

localup relay http \
  --localup-addr "0.0.0.0:14443" \
  --http-addr "0.0.0.0:18080" \
  --https-addr "0.0.0.0:18443" \
  --tls-cert=cert.pem \
  --tls-key=key.pem \
  --jwt-secret "my-jwt-secret"

With HTTPS only:

localup relay http \
  --localup-addr "0.0.0.0:14443" \
  --https-addr "0.0.0.0:18443" \
  --tls-cert=cert.pem \
  --tls-key=key.pem \
  --jwt-secret "my-jwt-secret"

With HTTP only (no TLS):

localup relay http \
  --localup-addr "0.0.0.0:14443" \
  --http-addr "0.0.0.0:18080" \
  --jwt-secret "my-jwt-secret"

Options:

  • --localup-addr: Control plane address for QUIC connections [default: 0.0.0.0:4443]
  • --http-addr: HTTP server address [default: 0.0.0.0:8080]
  • --https-addr: HTTPS server address (optional)
  • --tls-cert: TLS certificate file (PEM format, required if --https-addr used)
  • --tls-key: TLS private key file (PEM format, required if --https-addr used)
  • --jwt-secret: JWT secret for authenticating clients (REQUIRED)
  • --domain: Public domain name [default: localhost]
  • --database-url: Database URL for persistence (optional)

Step 3: Generate Authentication Token

export TOKEN=$(localup generate-token --secret "my-jwt-secret" --sub "myapp" --token-only)

Step 4: Start Your Local HTTP Server

For testing, use Python's built-in HTTP server:

python3 -m http.server 3000

Or run your web application:

# Next.js
npm run dev

# React (Create React App)
npm start

# Django
python manage.py runserver 3000

# Rails
rails server -p 3000

Step 5: Create the Tunnel

localup --port 3000 --protocol https --relay localhost:14443 --subdomain myapp --token "$TOKEN"
# Output: ✅ HTTPS tunnel created: https://myapp.localhost:18443

Note: Use --protocol http for plain HTTP tunnels (no TLS).

Step 6: Test the Connection

# Access via HTTPS
curl -k https://myapp.localhost:18443

# Access via HTTP (if --http-addr was configured)
curl http://myapp.localhost:18080

# Open in browser
open https://myapp.localhost:18443

Host-Based Routing

The HTTP/HTTPS relay uses the Host header to route requests to the correct tunnel.

How Host Routing Works

  1. Client sends request: GET / HTTP/1.1\nHost: myapp.localhost
  2. Relay extracts Host: myapp.localhost
  3. Relay extracts subdomain: myapp
  4. Route to tunnel: Relay matches subdomain to active tunnel
  5. Forward request: Full HTTP request sent through QUIC tunnel
  6. Client proxies: LocalUp client forwards to local server

Multiple Tunnels on One Relay

# Terminal 1: Relay (shared)
localup relay http \
  --localup-addr "0.0.0.0:14443" \
  --https-addr "0.0.0.0:18443" \
  --tls-cert=cert.pem --tls-key=key.pem \
  --jwt-secret "my-jwt-secret"

# Terminal 2: Tunnel 1 (web app)
export TOKEN1=$(localup generate-token --secret "my-jwt-secret" --sub "webapp" --token-only)
localup --port 3000 --protocol https --relay localhost:14443 --subdomain webapp --token "$TOKEN1"

# Terminal 3: Tunnel 2 (API)
export TOKEN2=$(localup generate-token --secret "my-jwt-secret" --sub "api" --token-only)
localup --port 4000 --protocol https --relay localhost:14443 --subdomain api --token "$TOKEN2"

# Clients connect via Host header:
curl -k https://webapp.localhost:18443  # → localhost:3000
curl -k https://api.localhost:18443     # → localhost:4000

Note: All tunnels share the same HTTPS port (18443), routing is based on the Host header.


Complete Example

Here's a complete example exposing a web application:

Terminal 1: Generate Certificates (one-time)

openssl req -x509 -newkey rsa:4096 -nodes \
  -keyout key.pem -out cert.pem -days 365 \
  -subj "/CN=localhost" -addext "subjectAltName=DNS:localhost"

Terminal 2: Start HTTP/HTTPS Relay

localup relay http \
  --localup-addr "0.0.0.0:14443" \
  --http-addr "0.0.0.0:18080" \
  --https-addr "0.0.0.0:18443" \
  --tls-cert=cert.pem --tls-key=key.pem \
  --jwt-secret "my-jwt-secret"

Terminal 3: Start Local Web Server

python3 -m http.server 3000

Terminal 4: Generate Token and Create Tunnel

export TOKEN=$(localup generate-token --secret "my-jwt-secret" --sub "myapp" --token-only)
localup --port 3000 --protocol https --relay localhost:14443 --subdomain myapp --token "$TOKEN"

Terminal 5: Test the Tunnel

# Test HTTPS
curl -k https://myapp.localhost:18443

# Test HTTP
curl http://myapp.localhost:18080

# Open in browser (accept self-signed certificate warning)
open https://myapp.localhost:18443

What's happening:

  1. Relay listens on port 14443 (QUIC control), 18080 (HTTP), and 18443 (HTTPS)
  2. Relay terminates TLS using cert.pem and key.pem
  3. Local HTTP server runs on port 3000
  4. LocalUp client creates tunnel with subdomain myapp
  5. External clients access https://myapp.localhost:18443
  6. Relay routes by Host header, forwards through QUIC tunnel
  7. Client proxies to localhost:3000

Troubleshooting

"No tunnel found for hostname"

Cause: The Host header doesn't match any active tunnel's subdomain.

Solution:

# Verify subdomain matches Host header
# Client: https://myapp.localhost:18443 → Host: myapp.localhost
# Tunnel: --subdomain myapp

# Extract subdomain from Host header: myapp
# These must match (case-sensitive)

"Certificate not valid" browser warning

Cause: Using self-signed certificates.

Solution:

# For development: Accept the browser warning (click "Advanced" → "Proceed")

# For production: Use Let's Encrypt
sudo certbot certonly --standalone -d example.com -d *.example.com

# Then use production certificates
localup relay http \
  --https-addr "0.0.0.0:443" \
  --tls-cert=/etc/letsencrypt/live/example.com/fullchain.pem \
  --tls-key=/etc/letsencrypt/live/example.com/privkey.pem \
  --jwt-secret "my-jwt-secret"

"Connection refused" when connecting to relay

Cause: HTTP/HTTPS relay is not running or firewall is blocking.

Solution:

# Verify HTTP relay is running
lsof -i :18080  # HTTP port
lsof -i :18443  # HTTPS port

# Check QUIC control plane is listening
lsof -i :14443

# Ensure firewall allows traffic
# On macOS: System Preferences → Security & Privacy → Firewall
# On Linux:
sudo ufw allow 18080/tcp  # HTTP
sudo ufw allow 18443/tcp  # HTTPS
sudo ufw allow 14443/udp  # QUIC control

"Tunnel created but no response"

Cause: Local HTTP server is not running or running on wrong port.

Solution:

# Verify local server is running
lsof -i :3000

# Test local server directly (bypass tunnel)
curl http://localhost:3000

# Ensure --port matches local server's port
localup --port 3000 --protocol https --relay localhost:14443 --subdomain myapp --token "$TOKEN"

"Authentication failed"

Cause: JWT token doesn't match relay's --jwt-secret.

Solution:

# Regenerate token with correct secret
export TOKEN=$(localup generate-token --secret "my-jwt-secret" --sub "myapp" --token-only)

# Verify secret matches relay's --jwt-secret flag

WebSocket connections fail

Cause: WebSocket upgrade not supported by relay configuration.

Solution:

# Ensure relay supports WebSocket upgrades (enabled by default in HTTP/HTTPS relay)
# Test WebSocket connection:
wscat -c wss://myapp.localhost:18443/ws

# Verify local server supports WebSocket upgrades

Next Steps:

Was this page helpful?