# frp STCP vs XTCP: Understanding Secure and NAT-Traversal Proxy Types

> Understand the difference between frp STCP and XTCP proxy types. Learn how STCP secures TCP connections and XTCP bypasses NAT using UDP tunnels for reliable remote access.

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

---

**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`](https://github.com/fatedier/frp/blob/main/server/proxy/stcp.go), the STCP proxy registers itself with the `VisitorManager` to create a listener identified by a secret key:

```go
// 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`](https://github.com/fatedier/frp/blob/main/client/visitor/stcp.go) handles incoming proxy requests by establishing a work connection and sending a `NewVisitorConn` message:

```go
// 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:**

```ini
[common]
bind_port = 7000

[stcp-ssh]
type = stcp
secret_key = abcdef12345

```

**frpc.ini:**

```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`](https://github.com/fatedier/frp/blob/main/server/proxy/xtcp.go), the server validates that a `NatHoleController` is available and registers the client to listen for NAT-hole IDs:

```go
// 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`](https://github.com/fatedier/frp/blob/main/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

```go
// 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:**

```ini
[common]
bind_port = 7000
bind_udp_port = 7001

[xtcp-ssh]
type = xtcp
secret_key = xyz987

```

**frpc.ini:**

```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`](https://github.com/fatedier/frp/blob/main/server/proxy/stcp.go) | `NatHoleController.ListenClient` in [`server/proxy/xtcp.go`](https://github.com/fatedier/frp/blob/main/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`](https://github.com/fatedier/frp/blob/main/server/proxy/stcp.go), [`client/visitor/stcp.go`](https://github.com/fatedier/frp/blob/main/client/visitor/stcp.go)), while XTCP depends on `NatHoleController`, `makeNatHole`, and `TunnelSession` abstractions ([`server/proxy/xtcp.go`](https://github.com/fatedier/frp/blob/main/server/proxy/xtcp.go), [`client/visitor/xtcp.go`](https://github.com/fatedier/frp/blob/main/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`](https://github.com/fatedier/frp/blob/main/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.