How FRP Hot Reload Works Without Dropping Active Connections

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, the command constructs a client instance and invokes the reload method with a strict configuration flag:

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. The SDK's Reload method sends an HTTP GET request to the /api/reload endpoint, appending the strictConfig query parameter to enforce validation rules:

// 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 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. 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, 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. 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:

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 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 and 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 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.

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 →