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:

  1. Pre-check: Determines NAT type using nathole.PreCheck
  2. Preparation: Gathers STUN and assisted addresses
  3. Info Exchange: Signs and exchanges NAT-hole information with the server
  4. Hole Punching: Creates the UDP tunnel via makeNatHole
  5. 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 between kcp and quic
  • keep_tunnel_open: Maintain the tunnel for subsequent connections
  • max_retries_an_hour: Limit NAT punching attempts
  • fallback_to and fallback_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 VisitorManager and direct TCP work connections (server/proxy/stcp.go, client/visitor/stcp.go), while XTCP depends on NatHoleController, makeNatHole, and TunnelSession abstractions (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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →