# How TCP Port Multiplexing in frp Enables Multiple Services on a Single Port

> Discover how frp's TCP port multiplexing lets you run multiple services on a single port. Learn how tcpmux routes traffic efficiently using HTTP CONNECT requests.

- Repository: [fatedier/frp](https://github.com/fatedier/frp)
- Tags: internals
- Published: 2026-02-26

---

**TCP port multiplexing in frp allows the server to host multiple logical services behind one TCP port by using HTTP CONNECT requests to route traffic based on domain names or user credentials.**

TCP port multiplexing (tcpmux) is a core feature of the **frp** (Fast Reverse Proxy) project that solves port exhaustion problems by letting a single listening socket handle many distinct services. According to the fatedier/frp source code, this mechanism relies on the HTTP CONNECT method to inspect incoming connections and demultiplex them to the appropriate backend based on virtual host routing rules.

## Server Configuration for TCP Port Multiplexing

The server-side configuration determines which port will accept multiplexed connections. In [`pkg/config/v1/server.go`](https://github.com/fatedier/frp/blob/main/pkg/config/v1/server.go) (lines 54-58), the `ServerConfig` struct defines two critical fields:

- `TCPMuxHTTPConnectPort`: The single TCP port that receives all multiplexed connections
- `TCPMuxPassthrough`: Whether frp handles the CONNECT response or forwards it unchanged

```toml

# frps.toml configuration

[common]
bind_addr = "0.0.0.0"
bind_port = 7000

# Enable tcpmux on port 5002

tcpmuxHTTPConnectPort = 5002
tcpmuxPassthrough = false   # frps responds with 200 OK to complete handshake

```

When `TCPMuxHTTPConnectPort` is set to a non-zero value, [`server/service.go`](https://github.com/fatedier/frp/blob/main/server/service.go) (lines 85-99) initializes the multiplexer during server startup. The code creates a TCP listener on the configured address and wraps it in an `HTTPConnectTCPMuxer` instance:

```go
l, err := net.Listen("tcp", address)
svr.rc.TCPMuxHTTPConnectMuxer, err = tcpmux.NewHTTPConnectTCPMuxer(
    l, cfg.TCPMuxPassthrough, vhostReadWriteTimeout)

```

## The HTTP-CONNECT Multiplexer Implementation

The `tcpmux.HTTPConnectTCPMuxer` struct in [`pkg/util/tcpmux/httpconnect.go`](https://github.com/fatedier/frp/blob/main/pkg/util/tcpmux/httpconnect.go) handles the protocol-level logic for TCP port multiplexing. When a client connects, the multiplexer performs the following steps:

1. **Accepts the raw TCP connection** and wraps it in a shared connection object (`libnet.NewSharedConn`) at lines 105-124 in `getHostFromHTTPConnect`
2. **Parses the HTTP CONNECT request** (`readHTTPConnectRequest`, lines 49-60) to validate the method and extract headers
3. **Extracts routing metadata** including the `Host` header, `HTTPUser`, and `HTTPPwd`, storing them in a `reqInfoMap` (lines 14-18)
4. **Completes the handshake** by sending a `200 OK` response if `passthrough` is disabled (`sendConnectResponse`, lines 70-78)

This implementation allows frp to inspect the HTTP CONNECT request without consuming the payload, enabling layer-7 routing decisions on a single layer-4 port.

## Routing and Proxy Registration

Each logical service registers itself with the multiplexer through [`server/proxy/tcpmux.go`](https://github.com/fatedier/frp/blob/main/server/proxy/tcpmux.go). The `TCPMuxProxy.httpConnectListen` function (lines 48-64) creates a `vhost.RouteConfig` containing the domain, HTTP user credentials, and routing rules:

```go
func (pxy *TCPMuxProxy) httpConnectListen(domain, routeByHTTPUser,
        httpUser, httpPwd string, addrs []string) ([]string, error) {
    routeConfig := &vhost.RouteConfig{
        Domain:          domain,
        RouteByHTTPUser: routeByHTTPUser,
        Username:        httpUser,
        Password:        httpPwd,
    }
    l, err = pxy.rc.TCPMuxHTTPConnectMuxer.Listen(pxy.ctx, routeConfig)
    // ...
}

```

The **vhost router** maintains a registry of these route configurations. When an HTTP CONNECT request arrives, it matches the `Host` header (or `Proxy-Authorization` credentials when `RouteByHTTPUser` is enabled) against registered proxies. Upon finding a match, the router hands the connection to the specific proxy handler, which forwards traffic to the actual backend service.

## Client Configuration and Connection Establishment

Clients specify the multiplexer type in [`pkg/config/v1/proxy.go`](https://github.com/fatedier/frp/blob/main/pkg/config/v1/proxy.go) (line 225) using `type = "tcpmux"` and `multiplexer = "httpconnect"`. The client opens **one persistent TCP connection** to the server's `TCPMuxHTTPConnectPort`, then tunnels multiple logical services through HTTP CONNECT requests:

```toml

# frpc.toml configuration

[common]
server_addr = "frps.example.com"
server_port = 5002          # Connect to the multiplexed port

[[proxies]]
name = "ssh_service"
type = "tcpmux"
multiplexer = "httpconnect"
custom_domains = ["ssh.example.com"]
http_user = "alice"
http_pwd = "secret"

```

Each proxy defined in the client configuration sends an HTTP CONNECT request with the configured `Host` header. The server demultiplexes these requests to separate handlers while the underlying TCP connection remains shared.

## Resource Efficiency and Performance Benefits

TCP port multiplexing in frp provides significant operational advantages. The implementation shares a **single OS-level socket** for all services, reducing port consumption and NAT/firewall state table entries. As noted in [`pkg/config/v1/server.go`](https://github.com/fatedier/frp/blob/main/pkg/config/v1/server.go) (lines 57-60), the TCPMux transport includes built-in heartbeat mechanisms at the stream level, eliminating the need for additional application-layer heartbeats when the feature is enabled.

This architecture allows operators to expose dozens of services through a single firewall rule while maintaining logical isolation through domain-based or credential-based routing.

## Summary

- **TCP port multiplexing** allows frp to host multiple services on one TCP port using HTTP CONNECT protocol inspection.
- The server configures `TCPMuxHTTPConnectPort` in [`pkg/config/v1/server.go`](https://github.com/fatedier/frp/blob/main/pkg/config/v1/server.go) to enable the multiplexer listener.
- `HTTPConnectTCPMuxer` in [`pkg/util/tcpmux/httpconnect.go`](https://github.com/fatedier/frp/blob/main/pkg/util/tcpmux/httpconnect.go) parses HTTP CONNECT requests to extract `Host` headers and authentication credentials.
- [`server/proxy/tcpmux.go`](https://github.com/fatedier/frp/blob/main/server/proxy/tcpmux.go) registers each service with a `vhost.RouteConfig` that maps domains or users to specific backends.
- Clients use `type = "tcpmux"` and `multiplexer = "httpconnect"` to route traffic through the shared port.
- The mechanism reduces resource consumption by maintaining single TCP connections that carry multiple logical streams.

## Frequently Asked Questions

### How does frp distinguish between different services on the same port?

frp inspects the HTTP CONNECT request's `Host` header or `Proxy-Authorization` credentials. The `HTTPConnectTCPMuxer` extracts this metadata in `getHostFromHTTPConnect` (lines 105-124) and routes the connection to the matching `TCPMuxProxy` based on the `vhost.RouteConfig` registered for that domain or user combination.

### What is the difference between tcpmux and regular TCP proxies in frp?

Standard TCP proxies in frp require a dedicated port per service (`remote_port`), while **tcpmux** allows unlimited services to share the single `TCPMuxHTTPConnectPort`. Regular TCP proxies operate at layer 4 without inspecting application data, whereas tcpmux operates at layer 7 by parsing HTTP CONNECT requests to make routing decisions.

### Can I use tcpmux with HTTPS or encrypted traffic?

Yes, but with limitations. Since tcpmux relies on reading the HTTP CONNECT headers, the initial handshake must be unencrypted to allow header inspection. However, once the `200 OK` response is sent (handled by `sendConnectResponse` in [`httpconnect.go`](https://github.com/fatedier/frp/blob/main/httpconnect.go)), the subsequent traffic flows through untouched, allowing TLS handshakes between the client and backend service to occur over the multiplexed connection.

### What happens if two clients register the same domain in tcpmux?

The `vhost` router in frp handles conflicts based on registration order and proxy type. If a second proxy attempts to register a domain already in use, the `TCPMuxHTTPConnectMuxer.Listen` call will return an error during proxy initialization in [`server/proxy/tcpmux.go`](https://github.com/fatedier/frp/blob/main/server/proxy/tcpmux.go), preventing duplicate route configurations and ensuring deterministic routing behavior.