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:
- External client requests
https://myapp.localhost:18443 - Relay terminates TLS using configured certificate
- Relay extracts
Hostheader:myapp.localhost - Relay routes to tunnel with matching subdomain
myapp - Request forwarded through QUIC tunnel to LocalUp client
- Client forwards HTTP request to local server at
localhost:3000 - Response flows back through same path
HTTP Flow (optional):
- External client requests
http://myapp.localhost:18080 - Relay extracts
Hostheader:myapp.localhost - Relay routes to tunnel with matching subdomain
myapp - Request forwarded through QUIC tunnel to LocalUp client
- 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
- Client sends request:
GET / HTTP/1.1\nHost: myapp.localhost - Relay extracts Host:
myapp.localhost - Relay extracts subdomain:
myapp - Route to tunnel: Relay matches subdomain to active tunnel
- Forward request: Full HTTP request sent through QUIC tunnel
- 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:
- Relay listens on port 14443 (QUIC control), 18080 (HTTP), and 18443 (HTTPS)
- Relay terminates TLS using
cert.pemandkey.pem - Local HTTP server runs on port 3000
- LocalUp client creates tunnel with subdomain
myapp - External clients access
https://myapp.localhost:18443 - Relay routes by Host header, forwards through QUIC tunnel
- 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: