TCP Relay

Expose local TCP services like databases, SSH servers, and custom TCP applications through LocalUp's TCP relay with automatic port allocation and port-based routing.


Overview

The TCP relay enables you to expose any TCP-based service running on your local machine to the internet. Perfect for:

  • Databases: PostgreSQL, MySQL, MongoDB, Redis
  • SSH Access: Remote access to development machines
  • Custom TCP Services: Game servers, IoT devices, custom protocols

Key Features:

  • Port-based routing with automatic port allocation
  • Configurable port ranges (e.g., 10000-20000)
  • Request specific ports or let the relay auto-allocate
  • JWT authentication for secure tunnel access
  • No TLS termination - pure TCP passthrough

How It Works

Local Service → LocalUp Client → TCP Relay → External Client
     :5432           QUIC          :16432        connects
                   tunnel                        to :16432

Flow:

  1. TCP relay starts with a configured port range (e.g., 10000-20000)
  2. Client requests a tunnel (auto-allocate or specific port)
  3. Relay allocates a port from the range and binds it
  4. External connections to that port are forwarded through the QUIC tunnel to your local service
  5. Port is released when tunnel closes

Setup Guide

Step 1: Start the TCP Relay

localup relay tcp \
  --localup-addr "0.0.0.0:14443" \
  --tcp-port-range "10000-20000" \
  --jwt-secret "my-jwt-secret"

Options:

  • --localup-addr: Control plane address for QUIC connections [default: 0.0.0.0:4443]
  • --tcp-port-range: Port range for TCP tunnels [default: 10000-20000]
  • --jwt-secret: JWT secret for authenticating clients (REQUIRED)
  • --domain: Public domain name [default: localhost]
  • --database-url: Database URL for persistence (optional)

Step 2: Generate Authentication Token

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

Note: The --sub value is just an identifier for the token. TCP tunnels don't require special JWT claims.

Step 3: Create the Tunnel

Option A: Auto-allocate port (recommended)

localup --port 5432 --protocol tcp --relay localhost:14443 --token "$TOKEN"
# Output: ✅ TCP tunnel created: localhost:16234

The relay automatically selects an available port from the configured range.

Option B: Request specific port

localup --port 5432 --protocol tcp --relay localhost:14443 --remote-port 15432 --token "$TOKEN"
# Output: ✅ TCP tunnel created: localhost:15432

The requested port must be:

  • Within the relay's --tcp-port-range (e.g., 10000-20000)
  • Not in use by the OS or another tunnel
  • Available (check with lsof -i :PORT)

Port Allocation

Auto-Allocation (Recommended)

Let the relay choose an available port from the configured range:

localup --port <LOCAL_PORT> --protocol tcp --relay <RELAY_ADDR> --token "$TOKEN"

Advantages:

  • No port conflicts
  • Automatic retry if port is unavailable
  • Simpler client command

Manual Port Selection

Request a specific port within the relay's range:

localup --port <LOCAL_PORT> --protocol tcp --relay <RELAY_ADDR> --remote-port <SPECIFIC_PORT> --token "$TOKEN"

Requirements:

  • Port must be within --tcp-port-range (e.g., 10000-20000)
  • Port must not be in use by OS or other tunnels
  • Check availability: lsof -i :PORT

When to use:

  • You need a consistent port for external clients
  • You have firewall rules configured for specific ports
  • You're documenting connection instructions

Complete Example

Here's a full example exposing a PostgreSQL database:

Terminal 1: Start Relay

localup relay tcp \
  --localup-addr "0.0.0.0:14443" \
  --tcp-port-range "10000-20000" \
  --jwt-secret "my-jwt-secret"

Terminal 2: Generate Token

export TOKEN=$(localup generate-token --secret "my-jwt-secret" --sub "mydb" --token-only)
echo "Token: $TOKEN"

Terminal 3: Expose Local PostgreSQL

Assuming PostgreSQL is running on localhost:5432:

# Auto-allocate port
localup --port 5432 --protocol tcp --relay localhost:14443 --token "$TOKEN" --remote-port=16432
# Wait for: ✅ TCP tunnel created: localhost:16432

Terminal 4: Connect from Anywhere

# Connect using the allocated port
psql -h localhost -p 16432 -U postgres

# Or with connection string
psql "postgresql://postgres@localhost:16432/mydb"

What's happening:

  1. TCP relay listens on port 14443 for QUIC control connections
  2. Client authenticates with JWT and requests tunnel for local port 5432
  3. Relay allocates port 16432 and binds it
  4. External clients connect to localhost:16432
  5. Relay forwards traffic through QUIC tunnel to client
  6. Client forwards traffic to local PostgreSQL at localhost:5432

Troubleshooting

"Address already in use" or "Failed to bind to port"

Cause: The requested port is in use by another process or tunnel.

Solution:

# Check what's using the port
lsof -i :16432

# If it's a lingering process, kill it
kill -9 <PID>

# Or use auto-allocation instead of --remote-port
localup --port 5432 --protocol tcp --relay localhost:14443 --token "$TOKEN"

Note: TCP ports stay in TIME_WAIT for 60 seconds after closing. The relay automatically retries binding up to 3 times with 1-second delays.

"Connection refused" when connecting to relay

Cause: Relay is not running or firewall is blocking QUIC.

Solution:

# Verify relay is running
lsof -i :14443

# Check firewall allows UDP (QUIC uses UDP)
# On macOS: System Preferences → Security & Privacy → Firewall
# On Linux: sudo ufw allow 14443/udp

"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 "mydb" --token-only)

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

"Requested port outside allowed range"

Cause: --remote-port is outside the relay's --tcp-port-range.

Solution:

# Check relay's configured range (e.g., 10000-20000)
# Request a port within that range, or use auto-allocation
localup --port 5432 --protocol tcp --relay localhost:14443 --token "$TOKEN"

Tunnel hangs on startup

Cause: Relay server is not running or address is incorrect.

Solution:

# Verify relay is listening
lsof -i :14443

# Ensure relay address matches client's --relay flag
# Client: --relay localhost:14443
# Relay: --localup-addr 0.0.0.0:14443

Next Steps:

Was this page helpful?