# How to Implement Load Balancing Across Multiple FRP Proxies Using Groups

> Implement load balancing across multiple frp proxies with identical group and groupKey configurations. Distribute client connections efficiently using server round-robin logic.

- Repository: [fatedier/frp](https://github.com/fatedier/frp)
- Tags: how-to-guide
- Published: 2026-02-26

---

**FRP (Fast Reverse Proxy) enables native load balancing across multiple client proxies by configuring them with identical `group` and `groupKey` values, allowing the server to distribute incoming connections via round-robin logic without external load balancers.**

The fatedier/frp repository provides a built-in group mechanism that allows several proxies to share a single public listener on the FRP server. When you implement load balancing across multiple FRP proxies using groups, the server creates one real listener and distributes traffic among group members using round-robin selection. This architecture eliminates the need for separate load balancing infrastructure while providing high availability for your services.

## Understanding the FRP Group Load Balancing Architecture

### Client-Side Configuration Structure

The group configuration originates in [`pkg/config/v1/proxy.go`](https://github.com/fatedier/frp/blob/main/pkg/config/v1/proxy.go), where the `LoadBalancer` field holds the `group` and `groupKey` parameters. When a client connects to the server, it transmits these values as part of the proxy registration message, signaling its intent to participate in a load-balanced group.

### Server-Side Group Controllers

On the server side, dedicated controllers manage group lifecycle and traffic distribution:

- **TCP Groups**: Implemented in [`server/group/tcp.go`](https://github.com/fatedier/frp/blob/main/server/group/tcp.go), the `TCPGroupCtl` handles TCP proxy registration and creates the real listening socket.
- **HTTP Groups**: Implemented in [`server/group/http.go`](https://github.com/fatedier/frp/blob/main/server/group/http.go), the HTTP group controller manages virtual host routing and load balancing for HTTP-based services.

These controllers validate that all group members share identical address, port, and authentication credentials before allowing registration.

### Round-Robin Distribution Logic

The actual load balancing uses atomic round-robin selection. In [`server/group/tcp.go`](https://github.com/fatedier/frp/blob/main/server/group/tcp.go), the code uses `atomic.AddUint64(&g.index, 1)` to cycle through registered proxies sequentially. When a new connection arrives on the shared listener, the controller selects the next proxy in the rotation and forwards the connection to that client's tunnel.

## How Group Registration Works in FRP

The group mechanism operates through a specific four-phase registration process:

1. **First Proxy Initialization**: When the first client proxy registers with a unique `group` name, the server creates a real listener on the requested address and port via the port manager (`ports.Manager`). This listening socket is stored within the group object and becomes the shared entry point for all subsequent traffic.

2. **Subsequent Proxy Validation**: For additional proxies joining the same `group`, the server performs strict validation:
   - **Address and Port Matching**: The `remotePort` and bind address must exactly match the first proxy's configuration. If they differ, the server returns `ErrGroupParamsInvalid`.
   - **Authentication Verification**: The `groupKey` must be identical across all members. A mismatch triggers `ErrGroupAuthFailed`.

3. **Connection Distribution**: Once registered, incoming connections hit the shared listener. The group's `Accept` method (implemented in `TCPGroupListener` for TCP or `createConn`/`chooseEndpoint` for HTTP) selects the next available proxy using round-robin logic and hands off the connection to that proxy's tunnel.

4. **Cleanup and Removal**: When a proxy disconnects or is stopped, the server removes it from the group. If the group becomes empty (no remaining members), the server closes the real listener and releases the port back to the port manager, ensuring efficient resource utilization.

## Implementing TCP Load Balancing with FRP Groups

To implement TCP load balancing, configure multiple client proxies with identical `group` and `groupKey` values, ensuring they request the same `remotePort`.

### Server Configuration

The server requires no special configuration to support groups. A standard [`frps.toml`](https://github.com/fatedier/frp/blob/main/frps.toml) suffices:

```toml

# frps.toml

bindPort = 7000
dashboardPort = 7500

```

### Client Configuration

Configure two or more clients to share the load:

```toml

# frpc.toml

[common]
serverAddr = "x.x.x.x"
serverPort = 7000

# First proxy member

[ssh_proxy_a]
type = "tcp"
localPort = 22
remotePort = 6000
loadBalancer.group = "mygroup"
loadBalancer.groupKey = "secret123"

# Second proxy member

[ssh_proxy_b]
type = "tcp"
localPort = 2222
remotePort = 6000
loadBalancer.group = "mygroup"
loadBalancer.groupKey = "secret123"

```

Both proxies request `remotePort = 6000`. The server creates a single listener on port 6000 and distributes incoming connections alternately between the SSH service on port 22 (first proxy) and port 2222 (second proxy).

## Implementing HTTP Load Balancing with FRP Groups

HTTP groups function similarly but operate through the virtual host router in [`server/group/http.go`](https://github.com/fatedier/frp/blob/main/server/group/http.go). This enables load balancing for web services behind the same domain.

### Client Configuration for HTTP Groups

```toml

# frpc.toml

[common]
serverAddr = "x.x.x.x"
serverPort = 7000

[web_a]
type = "http"
customDomains = ["example.com"]
location = "/app1"
loadBalancer.group = "http_grp"
loadBalancer.groupKey = "http_key"

[web_b]
type = "http"
customDomains = ["example.com"]
location = "/app1"
loadBalancer.group = "http_grp"
loadBalancer.groupKey = "http_key"

```

The `HTTPGroupController` registers the first proxy with the vhost router. Subsequent proxies join the same group, and the `chooseEndpoint` method distributes requests round-robin between `web_a` and `web_b` for traffic hitting `http://example.com/app1`.

## Validating Your FRP Group Configuration

The fatedier/frp repository includes end-to-end tests that verify group behavior. The test suite in [`test/e2e/v1/features/group.go`](https://github.com/fatedier/frp/blob/main/test/e2e/v1/features/group.go) confirms that:

- Connections distribute evenly across group members
- Mismatched `remotePort` or bind addresses trigger `ErrGroupParamsInvalid`
- Incorrect `groupKey` values result in `ErrGroupAuthFailed`

Run the specific group tests to validate your setup:

```bash

# Execute only the group-related e2e tests

make test-e2e TEST=TestGroup

```

## Summary

- **FRP groups** enable native load balancing by allowing multiple proxies to share a single public listener on the server.
- Configuration requires setting identical `loadBalancer.group` and `loadBalancer.groupKey` values across all participating client proxies.
- The server validates that all group members use the same `remotePort` and `groupKey`, rejecting mismatches with `ErrGroupParamsInvalid` or `ErrGroupAuthFailed`.
- Traffic distribution uses **round-robin** selection implemented in [`server/group/tcp.go`](https://github.com/fatedier/frp/blob/main/server/group/tcp.go) and [`server/group/http.go`](https://github.com/fatedier/frp/blob/main/server/group/http.go).
- No special server configuration is required; group handling activates automatically when clients present `loadBalancer` fields.

## Frequently Asked Questions

### What happens if two proxies in the same group specify different remote ports?

The FRP server rejects the second proxy's registration and returns `ErrGroupParamsInvalid`. According to the validation logic in [`server/group/tcp.go`](https://github.com/fatedier/frp/blob/main/server/group/tcp.go), all members of a group must bind to the identical address and port to ensure the server can maintain a single shared listener.

### Can I use different groupKey values for proxies in the same group?

No. The `groupKey` serves as an authentication credential for the group. If a proxy attempts to join with a `groupKey` that differs from the existing group's key, the server responds with `ErrGroupAuthFailed`. This security measure prevents unauthorized proxies from hijacking traffic intended for your load-balanced service.

### Does FRP support other load balancing algorithms besides round-robin?

The current implementation in fatedier/frp uses round-robin selection via `atomic.AddUint64` to cycle through proxy endpoints. The source code in [`server/group/tcp.go`](https://github.com/fatedier/frp/blob/main/server/group/tcp.go) and [`server/group/http.go`](https://github.com/fatedier/frp/blob/main/server/group/http.go) does not currently expose configuration for alternative algorithms like least-connections or IP hashing. For advanced load balancing requirements, you would need to implement a custom group controller or place an external load balancer in front of the FRP server.

### Is group load balancing available for all proxy types?

Group load balancing is implemented for TCP, HTTP, and HTTPS proxy types in the FRP server. The architecture uses specific controllers (`TCPGroupCtl`, `HTTPGroupCtl`) to handle the nuances of each protocol. UDP proxy groups follow a similar pattern but require state management for session tracking. Check the specific group controller files in `server/group/` to confirm support for your specific use case.