How Exposr Works

Understand the reverse tunneling architecture and connection flow.

Overview

Exposr uses a relay server architecture. The client (agent) initiates an outbound TCP connection to the server. This is important because it means the client doesn't need any inbound ports open — it works behind NAT and firewalls.

The relay server has two internal ports: a control port (9000) for managing tunnels, and a data port (9001) for forwarding traffic.

Connection flow

Here is the step-by-step flow when you run exposr expose 3000:

1

Agent connects to control port

The client creates a persistent TCP connection to SERVER_IP:9000 and sends the agent token for authentication.

2

Agent registers a public port

The agent sends REGISTER 25565 to request a public port. The server checks availability and confirms.

3

Server starts listening

The server begins accepting public connections on the registered port (e.g., SERVER_IP:25565).

4

Internet user connects

An external user connects to the public port. The server generates a UUID for this connection.

5

Server notifies the agent

The server sends CONNECT <connection-id> over the control channel.

6

Agent creates data connection

The agent connects to SERVER_IP:9001 and identifies itself with DATA <connection-id>.

7

Traffic flows bidirectionally

The server connects the public socket to the data connection. All TCP data is forwarded in both directions between the internet user and the local service.

Port registration

When no public port is specified, Exposr follows this strategy:

  1. Try the default preferred port: 25565
  2. If unavailable, pick a random port from 20000-30000
  3. Send the port to the server for registration
  4. If still unavailable, try another random port (up to 100 attempts)

When a public port is explicitly specified with the to keyword, Exposr requests that exact port and does not fall back.

bash
# Default port assignment
exposr expose 3000

# Explicit port
exposr expose 3000 to 21342

Data forwarding

Each incoming public connection receives its own dedicated data tunnel. The agent opens a new connection to port 9001 for every public connection, identified by a UUID. Simultaneously, the agent connects to the local service on the specified port.

Traffic is forwarded using Python asyncio — data read from one socket is immediately written to the other, creating a transparent bidirectional pipe.

Multiple connections

Multiple users can connect to the same public port simultaneously. Each connection gets a unique UUID and a separate data tunnel:

text
Client A --+
           |
Client B --+----> Exposr Server
           |             |
Client C --+             +-- Tunnel A --> Local Service
                         +-- Tunnel B --> Local Service
                         +-- Tunnel C --> Local Service

The server also supports multiple agents, each owning different public ports.