frp STCP vs XTCP: Understanding Secure and NAT-Traversal Proxy Types
TLDR: STCP (Secure TCP) provides authenticated TCP proxying where the frp server directly accepts client connections, while XTCP (NAT-traversal TCP) uses UDP-based tunnels (KCP or QUIC) to punch through NAT and firewall restrictions when the client cannot accept inbound connections.
The fatedier/frp repository implements multiple proxy types to handle different network topologies. When exposing local services through the frp client (frpc) to remote users, choosing between STCP and XTCP depends on whether the client operates behind restrictive NAT or firewall rules that block direct TCP connections.
What Is STCP in frp?
STCP (Secure TCP) creates an authenticated TCP proxy where the frp server (frps) listens for incoming connections and forwards them to the client over the existing control connection. This mode assumes the server can reach the client directly or that the client maintains an outbound connection to the server.
Server-Side Implementation
In server/proxy/stcp.go, the STCP proxy registers itself with the VisitorManager to create a listener identified by a secret key:
// server/proxy/stcp.go: Run()
listener, errRet := pxy.rc.VisitorManager.Listen(pxy.GetName(), pxy.cfg.Secretkey, allowUsers)
The VisitorManager handles the TCP listener lifecycle, accepting connections from visitors (remote users) and routing them to the appropriate client.
Client-Side Implementation
On the client side, client/visitor/stcp.go handles incoming proxy requests by establishing a work connection and sending a NewVisitorConn message:
// client/visitor/stcp.go: handleConn()
visitorConn, err := sv.helper.ConnectServer()
err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
libio.Join(userConn, remote)
The data flows directly over the TCP work connection without additional encapsulation or NAT traversal logic.
STCP Configuration Example
frps.ini:
[common]
bind_port = 7000
[stcp-ssh]
type = stcp
secret_key = abcdef12345
frpc.ini:
[common]
server_addr = x.x.x.x
server_port = 7000
[ssh]
type = stcp
local_port = 22
secret_key = abcdef12345
What Is XTCP in frp?
XTCP (NAT-traversal TCP) solves the problem of exposing services when the frp client resides behind a NAT or firewall that blocks inbound TCP connections. Instead of relying on the server to forward TCP traffic, XTCP establishes a UDP-based tunnel (using KCP or QUIC) directly between the visitor and the client, punching a hole through the NAT.
Server-Side Implementation
In server/proxy/xtcp.go, the server validates that a NatHoleController is available and registers the client to listen for NAT-hole IDs:
// server/proxy/xtcp.go: Run()
if pxy.rc.NatHoleController == nil {
err = fmt.Errorf("xtcp is not supported in frps")
return
}
sidCh, err := pxy.rc.NatHoleController.ListenClient(pxy.GetName(), pxy.cfg.Secretkey, allowUsers)
The server acts as a signaling coordinator, exchanging NAT-hole information between the visitor and client without handling the actual data payload.
Client-Side Implementation
The client implementation in client/visitor/xtcp.go performs a complex NAT-traversal workflow:
- Pre-check: Determines NAT type using
nathole.PreCheck - Preparation: Gathers STUN and assisted addresses
- Info Exchange: Signs and exchanges NAT-hole information with the server
- Hole Punching: Creates the UDP tunnel via
makeNatHole - Tunnel Session: Establishes
TunnelSession(KCP or QUIC) and opens logical connections
// client/visitor/xtcp.go: handleConn()
tunnelConn, err := sv.openTunnel(ctx) // includes NAT-hole preparation
_, _, errs := libio.Join(userConn, muxConnRWCloser)
The openTunnel method encapsulates the entire NAT-traversal logic, returning a multiplexed connection over the UDP tunnel.
XTCP Configuration Example
frps.ini:
[common]
bind_port = 7000
bind_udp_port = 7001
[xtcp-ssh]
type = xtcp
secret_key = xyz987
frpc.ini:
[common]
server_addr = x.x.x.x
server_port = 7000
udp_port = 7001
[ssh]
type = xtcp
local_port = 22
secret_key = xyz987
protocol = kcp
keep_tunnel_open = true
max_retries_an_hour = 5
fallback_to = tcp
fallback_timeout_ms = 3000
XTCP supports protocol = kcp or protocol = quic for the underlying UDP tunnel, and includes fallback logic to standard TCP if NAT traversal fails within the specified timeout.
Key Differences Between frp STCP and XTCP
Understanding the architectural distinction helps determine which proxy type fits your network topology.
Connection Architecture
STCP follows a straightforward client-server model where the frp server acts as a TCP proxy:
- Data path:
Visitor → frps (TCP) → frpc → Local Service - Transport: Pure TCP over the existing work connection
- Requirement: The server must be able to reach the client, or the client must maintain an outbound connection that the server can use for forwarding
XTCP establishes a peer-to-peer UDP tunnel that bypasses the server for data transfer:
- Data path:
Visitor → frpc (UDP tunnel/KCP/QUIC) → Local Service(after initial signaling through frps) - Transport: UDP with KCP or QUIC encapsulation for NAT traversal
- Requirement: Both sides must support UDP and successfully punch through NAT; the server only handles initial signaling
Implementation Contrast
| Feature | STCP | XTCP |
|---|---|---|
| Server component | VisitorManager.Listen in server/proxy/stcp.go |
NatHoleController.ListenClient in server/proxy/xtcp.go |
| Client workflow | NewVisitorConn message and direct stream joining |
openTunnel, makeNatHole, TunnelSession with KCP/QUIC |
| Protocol overhead | Minimal (TCP only) | Higher (UDP + KCP/QUIC headers, NAT traversal logic) |
| Latency | Lower | Slightly higher due to tunnel encapsulation |
| NAT compatibility | Requires server-accessible client or open ports | Works through most NAT/firewall configurations |
Configuration Complexity
STCP requires minimal configuration—just a secret_key and optional allow_users. XTCP demands additional parameters for NAT traversal behavior:
protocol: Choose betweenkcpandquickeep_tunnel_open: Maintain the tunnel for subsequent connectionsmax_retries_an_hour: Limit NAT punching attemptsfallback_toandfallback_timeout_ms: Failover to standard TCP if traversal fails
When to Use STCP vs XTCP
Choose STCP when:
- Both the frp server and client have public IP addresses or reside in the same network
- The client can accept inbound TCP connections through the server
- You need minimal latency and overhead
- You want simple configuration without NAT traversal complexity
Choose XTCP when:
- The frp client operates behind a home router, mobile network, or corporate firewall that blocks inbound TCP
- You need to expose services from a device without public IP access
- UDP traffic is permitted (required for KCP/QUIC tunnels)
- You can tolerate slightly higher latency in exchange for connectivity through NAT
Summary
-
STCP implements a secure TCP proxy using the frp server as a direct forwarding agent, suitable for environments with clear network paths between server and client.
-
XTCP enables TCP proxying through restrictive NAT and firewalls by establishing UDP-based KCP or QUIC tunnels between visitors and clients, using the server only for initial signaling.
-
Architecture: STCP relies on
VisitorManagerand direct TCP work connections (server/proxy/stcp.go,client/visitor/stcp.go), while XTCP depends onNatHoleController,makeNatHole, andTunnelSessionabstractions (server/proxy/xtcp.go,client/visitor/xtcp.go). -
Selection criteria: Use STCP for low-latency, server-forwarded connections; use XTCP for NAT-traversal scenarios where direct connectivity is impossible.
Frequently Asked Questions
Can I use XTCP without UDP support?
No. XTCP requires UDP to establish the NAT-traversal tunnel using either KCP or QUIC protocols. If your network blocks UDP traffic or the firewall restricts UDP ports, XTCP will fail to establish the tunnel and will only work if you configure fallback_to = tcp with a fallback_timeout_ms value, which reverts to standard TCP proxying through the server.
Does XTCP provide better performance than STCP?
XTCP typically introduces slightly higher latency than STCP due to the overhead of UDP encapsulation, KCP/QUIC protocol headers, and the NAT hole-punching process. However, XTCP enables connectivity in scenarios where STCP would fail entirely, such as when the client is behind a restrictive NAT without port forwarding. For direct server-to-client connectivity without NAT barriers, STCP offers lower latency and simpler data paths.
What happens if XTCP NAT traversal fails?
If XTCP cannot punch through the NAT within the configured fallback_timeout_ms duration, and you have specified fallback_to = tcp in the configuration, the connection will automatically fall back to standard TCP proxying through the frp server. If no fallback is configured, the connection attempt will fail with a timeout error. You can monitor these failures through the frp logs and adjust max_retries_an_hour to control how aggressively the client attempts NAT traversal.
Can I run both STCP and XTCP proxies on the same frp server?
Yes. The frp server supports simultaneous operation of multiple proxy types including STCP, XTCP, TCP, UDP, and HTTP. You configure each proxy independently in the frps.ini file or through the dashboard API. Ensure that you allocate distinct bind_udp_port for XTCP functionality while STCP operates over the standard bind_port TCP connection. The VisitorManager and NatHoleController operate independently, allowing both proxy types to serve different network requirements simultaneously.
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 →