How TCP Port Multiplexing in frp Enables Multiple Services on a Single Port
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 (lines 54-58), the ServerConfig struct defines two critical fields:
TCPMuxHTTPConnectPort: The single TCP port that receives all multiplexed connectionsTCPMuxPassthrough: Whether frp handles the CONNECT response or forwards it unchanged
# 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 (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:
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 handles the protocol-level logic for TCP port multiplexing. When a client connects, the multiplexer performs the following steps:
- Accepts the raw TCP connection and wraps it in a shared connection object (
libnet.NewSharedConn) at lines 105-124 ingetHostFromHTTPConnect - Parses the HTTP CONNECT request (
readHTTPConnectRequest, lines 49-60) to validate the method and extract headers - Extracts routing metadata including the
Hostheader,HTTPUser, andHTTPPwd, storing them in areqInfoMap(lines 14-18) - Completes the handshake by sending a
200 OKresponse ifpassthroughis 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. The TCPMuxProxy.httpConnectListen function (lines 48-64) creates a vhost.RouteConfig containing the domain, HTTP user credentials, and routing rules:
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 (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:
# 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 (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
TCPMuxHTTPConnectPortinpkg/config/v1/server.goto enable the multiplexer listener. HTTPConnectTCPMuxerinpkg/util/tcpmux/httpconnect.goparses HTTP CONNECT requests to extractHostheaders and authentication credentials.server/proxy/tcpmux.goregisters each service with avhost.RouteConfigthat maps domains or users to specific backends.- Clients use
type = "tcpmux"andmultiplexer = "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), 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, preventing duplicate route configurations and ensuring deterministic routing behavior.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →