# How FRP Hot Reload Works Without Dropping Active Connections

> Learn how FRP hot reload maintains active connections during zero-downtime configuration updates by diffing changes and incrementally updating proxies.

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

---

**FRP performs zero-downtime configuration updates by diffing new proxy definitions against the current runtime state, gracefully stopping only removed or modified proxies while preserving existing work connections through an incremental update pipeline.**

The **frp hot reload** capability in the fatedier/frp repository allows clients to update tunnel configurations on-the-fly without terminating established sessions. Unlike a full process restart, this mechanism leverages a fine-grained diffing algorithm implemented across the Control, Service, and ProxyManager components to isolate changes and maintain network continuity.

## Triggering Reloads via the Admin API and SDK

### CLI Administration Commands

The reload process begins at the command line. When you execute `frpc admin reload`, the CLI parses the client configuration and initializes an SDK client targeting the internal web admin server. In [`cmd/frpc/sub/admin.go`](https://github.com/fatedier/frp/blob/main/cmd/frpc/sub/admin.go), the command constructs a client instance and invokes the reload method with a strict configuration flag:

```bash
frpc admin reload -c frpc.toml

```

This binary acts as a thin wrapper that authenticates against the admin interface and forwards your request to the running frpc daemon.

### SDK Client Implementation

Underneath the CLI, the actual network call originates in [`pkg/sdk/client/client.go`](https://github.com/fatedier/frp/blob/main/pkg/sdk/client/client.go). The SDK's `Reload` method sends an HTTP **GET** request to the `/api/reload` endpoint, appending the `strictConfig` query parameter to enforce validation rules:

```go
// From pkg/sdk/client/client.go
func (c *Client) Reload(ctx context.Context, strict bool) error {
    // Constructs request to /api/reload?strictConfig={strict}
    // Returns error if admin server rejects configuration
}

```

This design decouples the reload trigger from the core logic, enabling both manual operator intervention and automated configuration management systems to update the client dynamically.

## Configuration Validation and Diffing

Upon receiving the HTTP request, the handler in [`client/api/controller.go`](https://github.com/fatedier/frp/blob/main/client/api/controller.go) executes the reload sequence. The `Controller.Reload` function first loads the new configuration file using `config.LoadClientConfig`, then validates it through `validation.ValidateAllClientConfig`. Only after passing validation does it invoke the injected `UpdateConfig` function, passing the sanitized proxy and visitor definitions downstream.

This validation layer ensures that malformed configurations never reach the runtime, preventing partial updates that could corrupt the proxy state.

## The Hot-Reload Pipeline: Service to ProxyManager

### Propagating Updates Through the Control Layer

Once validated, the configuration enters the service layer through [`client/service.go`](https://github.com/fatedier/frp/blob/main/client/service.go). The `UpdateAllConfigurer` method stores the new proxy and visitor slices, then forwards them to the active `Control` instance via `ctl.UpdateAllConfigurer`. 

In [`client/control.go`](https://github.com/fatedier/frp/blob/main/client/control.go), the `UpdateAllConfigurer` method acts as a dispatcher, invoking:
- `ctl.vm.UpdateAll(visitorCfgs)` for visitor management
- `ctl.pm.UpdateAll(proxyCfgs)` for proxy synchronization

This separation ensures that visitor tunnels (used for accessing services behind the client) and proxy tunnels (exposing local services) update independently without cross-interference.

### ProxyManager's Incremental Synchronization

The core hot-reload logic resides in [`client/proxy/proxy_manager.go`](https://github.com/fatedier/frp/blob/main/client/proxy/proxy_manager.go). The `UpdateAll` method performs a three-phase atomic update:

1. **Mapping**: Builds a hash map of the incoming proxy configurations keyed by name.
2. **Diffing**: Iterates through existing proxies, calling `reflect.DeepEqual` to compare current and new configurations. Proxies missing from the new map or with differing configs are marked for removal.
3. **Synchronization**: 
   - **Removes** stale proxies by calling `pxy.Stop()`, which gracefully closes the listener but preserves established work connections until they naturally terminate.
   - **Adds** new proxies by creating wrapper objects and invoking `Start()` to bind new listeners.
   - **Preserves** unchanged proxies entirely, leaving their active connections undisturbed.

This diff-based approach ensures that a configuration change affecting only one proxy does not disrupt traffic flowing through unrelated tunnels.

## Connection Persistence Mechanisms

### Graceful Proxy Stopping

When the ProxyManager stops a proxy during reload, it executes the `Stop()` method on the proxy wrapper. This operation specifically targets the **listener** socket, preventing new incoming connections while allowing existing work connections to complete their data exchange. Because FRP's architecture separates listener management from active session handling, ongoing transfers survive the configuration transition.

### Isolated Component Updates

The reload process never destroys the root `Control` object or its underlying network session with the FRP server. By updating the `ProxyManager` and `VisitorManager` maps in-place, the system maintains the TCP control connection and authentication state throughout the reload. This architectural isolation confines changes to the specific proxy definitions that differ between the old and new configurations.

## Implementing Hot Reload Programmatically

You can trigger hot reloads directly from Go applications using the official SDK without shelling out to the CLI. This example demonstrates loading a configuration, creating an authenticated client, and executing a reload with context-based timeout:

```go
package main

import (
	"context"
	"time"

	"github.com/fatedier/frp/pkg/sdk/client"
	"github.com/fatedier/frp/pkg/config/v1"
)

func main() {
	// Load configuration to obtain admin server credentials
	cfg, _, _, _, _ := v1.LoadClientConfig("frpc.toml", false)

	// Initialize SDK client pointing at the admin web server
	sdk := client.New(cfg.WebServer.Addr, cfg.WebServer.Port)
	sdk.SetAuth(cfg.WebServer.User, cfg.WebServer.Password)

	// Execute hot reload with 30-second timeout
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()
	if err := sdk.Reload(ctx, false); err != nil {
		panic(err)
	}
	// Existing connections remain active; new configuration is now live
}

```

This approach is ideal for configuration management agents that monitor file changes and apply updates automatically.

## Summary

- **FRP hot reload** uses an HTTP admin API endpoint (`/api/reload`) to accept new configurations without process restarts.
- The `ProxyManager` in [`client/proxy/proxy_manager.go`](https://github.com/fatedier/frp/blob/main/client/proxy/proxy_manager.go) implements diff-based updates using `reflect.DeepEqual`, affecting only changed or removed proxies.
- Graceful stopping closes listeners while preserving active work connections, ensuring zero-downtime transitions.
- The `Control` and `Service` layers in [`client/control.go`](https://github.com/fatedier/frp/blob/main/client/control.go) and [`client/service.go`](https://github.com/fatedier/frp/blob/main/client/service.go) propagate updates without tearing down the server connection.
- Both CLI (`frpc admin reload`) and programmatic SDK interfaces support hot reloading with strict configuration validation.

## Frequently Asked Questions

### Does frp hot reload require restarting the frpc process?

No. The hot reload mechanism updates the in-memory configuration and proxy maps while the main process continues running. The `Control` object and its network session with the FRP server remain intact throughout the operation, eliminating the need for a full restart.

### What happens to active tunnels during an frp hot reload?

Unchanged proxies continue operating normally with their existing connections fully preserved. Only proxies that were removed from the configuration or whose parameters changed are stopped, and even then, their active work connections remain alive until they close naturally. New proxies defined in the updated configuration are started immediately.

### How does the ProxyManager determine which proxies to restart?

The `UpdateAll` method in [`client/proxy/proxy_manager.go`](https://github.com/fatedier/frp/blob/main/client/proxy/proxy_manager.go) compares the current proxy configuration against the incoming one using `reflect.DeepEqual`. If a proxy's name no longer exists in the new configuration, or if its settings differ from the running instance, the manager stops the old proxy and starts a new one with the updated definition. Identical configurations are left untouched in the manager's internal map.

### Can I trigger a hot reload from my own application?

Yes. Import `github.com/fatedier/frp/pkg/sdk/client` to create a client instance targeting the frpc admin server, then call `client.Reload(ctx, strict)`. This sends an authenticated HTTP request to `/api/reload`, executing the same diff-and-update logic as the CLI command while allowing you to handle errors programmatically.