frp’s Exposure Modes: Raw Ports, HTTP Routes, and Private Visitors

Once frp can carry a request from frps to frpc, a more important design choice appears: what should that request mean? This chapter compares frp’s exposure modes by their ingress contract—what the public side presents, what frps must understand, and who is allowed to start the path. The goal is not to memorize a long proxy-type list, but to choose between raw bytes, datagrams, HTTP routing, shared-port access, and private visitor access without granting a broader audience or a richer interpretation than the service requires.

The same local service can need different public contracts

Suppose a team has three needs inside one private network. They want occasional SSH access to a development machine, a web preview that several people can visit under a domain, and a database-like service that should be reachable only by another authorized client. All three requests can travel through the frps/frpc lifecycle described earlier. They should not use the same ingress model, because their callers, protocols, and acceptable exposure are different.

A useful first distinction is whether the service speaks an opaque transport or an application protocol that frps can route. A raw TCP proxy does not need to understand the application’s messages. It needs a public listener, a work connection, and a local stream. A UDP proxy carries datagrams rather than one ordered byte stream. An HTTP proxy, by contrast, can use the host, path, and configured HTTP identity to choose among registered backends. The choice is therefore about meaning, not just convenience: interpreting more metadata makes richer routing possible, but it also creates more policy and matching rules.

The second distinction is who should be able to initiate access. A public listener is designed for a caller that can reach the public address and port or resolve the public domain. A private visitor design avoids the same kind of public remote port and asks another frp client to present credentials for the service. The two modes can carry similar application bytes after admission, but they expose different doors.

TCP and UDP: forward the transport itself

TCP is the most direct expression of frp’s reverse-proxy model. The server acquires a public listening port for the proxy. When a user connects, frps obtains a work connection, and the client-side proxy handler connects to the configured local address and port. The two streams are joined. The server does not need to understand whether those bytes represent SSH, a custom binary protocol, a database conversation, or something else.

That opacity is the strength of TCP mode. It can support protocols that are not HTTP and does not require the service owner to model application routes in frp. It is also the reason the public port is a blunt exposure surface. Anyone who can reach the listener can attempt to speak to the represented service; frp’s TCP mapping does not automatically add the service’s own authentication or understand whether a connection is benign. The local application remains responsible for its protocol-level decisions, and the operator remains responsible for deciding whether a public listener is appropriate.

UDP has a similar public-port idea but a different transport contract. The proxy carries discrete datagrams through a UDP-specific path rather than presenting a single ordered stream. That matters for DNS-like services, discovery protocols, and other applications that depend on datagram boundaries or UDP behavior. It also means that TCP assumptions—connection lifetime, stream ordering, and a simple one-to-one join—do not describe the mode accurately. The server and client must agree on the UDP packet handling used by the selected wire path, and the application must tolerate the characteristics of the resulting network route.

In both modes, frp’s job is deliberately narrow: connect the public transport to the configured local endpoint. If the requirement is “make this one TCP service reachable” or “forward this UDP service through a public port,” raw forwarding is often the clearest contract. If the requirement is “send different URLs to different local services,” raw forwarding is the wrong abstraction because the transport does not carry the routing criterion in a form frps is meant to interpret.

HTTP and HTTPS: the public request becomes a routing key

An HTTP proxy changes the server’s role from port forwarder to request router. Instead of assigning only a public port, the configuration can register a custom domain or a subdomain under a shared host. The incoming HTTP request supplies a host and path, and frp uses those values to select a registered route. HTTPS provides the corresponding secure-facing mode, with the exact certificate and backend arrangement depending on the configuration.

The route can be more specific than a domain alone. Locations represent URL prefixes, and the router chooses the most specific matching prefix rather than treating the order of configuration entries as the meaning of the route. A team might send /news and /about to one local service while leaving other paths on a default service under the same domain. This is not a capability that TCP mode can provide without an application-aware router, because TCP sees the connection as bytes rather than a structured host-and-path request.

HTTP mode can also rewrite the Host header before forwarding, set request headers, and set response headers. These features solve a real integration problem: the public name used by a browser may not be the name expected by the local web server. They also create a responsibility boundary. A rewritten host can change virtual-host selection inside the backend, while injected headers can affect application behavior. Such transformations should be treated as deliberate interface policy, not harmless formatting.

The router can distinguish an HTTP user as well as a domain and path. HTTP Basic Auth can be configured for a web proxy, and routing can use the authenticated HTTP user. Health checks can send an HTTP request to a local service and expect a successful response before keeping the route eligible. Together, these controls make an HTTP proxy more expressive than “public port forwards to local port,” but they do not turn it into a complete application security system. Basic Auth is an access condition at the proxy; it does not replace TLS, application authorization, or careful handling of credentials.

HTTP mode is therefore a good fit when the public request already contains the routing information the operator wants to use: a domain, URL prefix, or controlled HTTP identity. It is a poor fit for an arbitrary non-HTTP service, for a protocol whose bytes merely resemble HTTP, or for an operator who wants frp to remain completely unaware of application content. HTTPS can protect the public web-facing conversation, but the security and certificate choices still need to match the local backend and the trust model.

tcpmux: one public listener, many named services

TCP multiplexing through tcpmux addresses a different constraint: several services need public access, but the operator wants them to share one server-side port. In frp’s documented implementation, the shared listener accepts HTTP CONNECT requests. The host in the CONNECT request becomes the routing key, and the registered tcpmux proxy’s custom domain or subdomain identifies the local service.

This is not the same as making arbitrary TCP protocols self-describing. The caller must know how to use the supported HTTP CONNECT mechanism and must supply a host that matches a registered route. An SSH client using a suitable proxy command can select one internal SSH service, while another host value selects a different service through the same public listener. The port is reused because the ingress protocol carries a name, not because frp can infer every target from opaque bytes.

The gain is operational economy: one listener can represent several services and reduce the number of public ports that must be opened or advertised. The cost is a shared routing and failure boundary. A conflict in domain registration, a bad CONNECT request, or a problem in the shared listener can affect more than one logical proxy. The caller also inherits a protocol requirement that ordinary TCP clients may not meet. tcpmux is a good choice when the clients already support HTTP CONNECT and port reuse matters; it is not a universal replacement for independent TCP listeners.

STCP: a service without a public remote port

STCP takes the opposite approach from a public TCP proxy. The server does not expose the service through an ordinary public remote port. Instead, the STCP proxy creates an internal visitor listener in the server’s frp machinery. A second frpc, configured as a visitor, opens a connection to frps and identifies the service it wants to reach. The server then checks the visitor’s signed request and user authorization before handing the connection to the private service’s proxy.

The signature is derived from the shared secret and a timestamp. This gives the server a way to reject a visitor that does not know the service secret, while the user allow-list narrows which client identity may use the proxy. If no allow-list is configured, the source defaults access to the proxy’s owning user; an explicit wildcard broadens access. That default is meaningful because it makes an unqualified STCP design narrower than a public listener, but it is not a reason to stop reviewing identities and secrets.

STCP therefore changes the exposure question from “which public port should anyone reach?” to “which authorized visitor may request this named service?” The public frps server still carries the control and relay path, but the service is not advertised as an ordinary Internet endpoint. This is useful for private administration, service-to-service access, or a team that can distribute frp visitor configuration to known clients rather than publish a port to an unknown audience.

The private label also has limits. The shared secret and user check authorize the visitor connection; they do not prove that the local application is safe, that the visitor host is uncompromised, or that the traffic is confidential under every transport configuration. Optional visitor-side encryption and compression are separate choices. STCP narrows who can enter the frp path, but ordinary application authentication and transport protection may still be necessary.

Choosing the smallest useful exposure

The modes can be compared by the question they answer:

  • Choose TCP when the service is a stream and the public caller should connect through a normal port, with application-level meaning left to the endpoints.
  • Choose UDP when datagrams are part of the service contract and HTTP-style routing would be irrelevant or misleading.
  • Choose HTTP or HTTPS when host, URL path, headers, or HTTP identity should determine routing and the service is genuinely web-facing.
  • Choose tcpmux when several named services can share one public listener and their clients support HTTP CONNECT.
  • Choose STCP when the service should not have an ordinary public remote port and access should begin with a known visitor’s signed request.

No option wins on every criterion. TCP is protocol-neutral but exposes a blunt listener. HTTP offers richer routing but requires frp to interpret application metadata. tcpmux saves ports but makes clients depend on a particular connection method. STCP narrows the public surface but adds visitor configuration, shared-secret management, and a dependency on the frp client on the accessing side.

flowchart LR I["Service need"] --> Q1{"Need HTTP host/path routing?"} Q1 -->|Yes| H["HTTP / HTTPS\npublic virtual-host route"] Q1 -->|No| Q2{"Need datagrams?"} Q2 -->|Yes| U["UDP\npublic datagram port"] Q2 -->|No| Q3{"Can callers use HTTP CONNECT\nand should one port be shared?"} Q3 -->|Yes| M["TCPMUX\nshared listener + named host"] Q3 -->|No| Q4{"Should there be no\nordinary public remote port?"} Q4 -->|Yes| T["STCP\ninternal visitor listener\nsignature + allow-user gate"] Q4 -->|No| C["TCP\npublic byte-stream port"] H --> L["Registered local service"] U --> L M --> L T --> L C --> L

The key decision is not whether frp has a mode that can carry the bytes. Several modes may do that. The decision is which mode exposes the least surface while preserving the caller and service semantics that matter. Once that semantic contract is chosen, the next concern is whether the transport, connection pool, routing group, and health checks can carry it with acceptable latency and failure behavior.