FL Server over Secure RPCΒΆ

We demonstrate how to launch a gRPC server as a federated learning server with authentication. Consider only one client so that we can launch a server and a client (from another notebook) together.

[1]:
num_clients = 1

Load server configurationsΒΆ

In this example, we use the FedAvg server aggregation algorithm (while there is only one client for easy demo, the aggregation algorithm does not matter a lot though) and the MNIST dataset by loading the server configurations from examples/resources/configs/mnist/server_fedavg.yaml.

[2]:
from omegaconf import OmegaConf

server_config_file = "../../examples/resources/configs/mnist/server_fedavg.yaml"
server_config = OmegaConf.load(server_config_file)
print(OmegaConf.to_yaml(server_config))
client_configs:
  train_configs:
    trainer: VanillaTrainer
    mode: step
    num_local_steps: 100
    optim: Adam
    optim_args:
      lr: 0.001
    loss_fn_path: ./resources/loss/celoss.py
    loss_fn_name: CELoss
    do_validation: true
    do_pre_validation: true
    metric_path: ./resources/metric/acc.py
    metric_name: accuracy
    use_dp: false
    epsilon: 1
    clip_grad: false
    clip_value: 1
    clip_norm: 1
    train_batch_size: 64
    val_batch_size: 64
    train_data_shuffle: true
    val_data_shuffle: false
  model_configs:
    model_path: ./resources/model/cnn.py
    model_name: CNN
    model_kwargs:
      num_channel: 1
      num_classes: 10
      num_pixel: 28
  comm_configs:
    compressor_configs:
      enable_compression: false
      lossy_compressor: SZ2Compressor
      lossless_compressor: blosc
      error_bounding_mode: REL
      error_bound: 0.001
      param_cutoff: 1024
server_configs:
  num_clients: 2
  scheduler: SyncScheduler
  scheduler_kwargs:
    same_init_model: true
  aggregator: FedAvgAggregator
  aggregator_kwargs:
    client_weights_mode: equal
  device: cpu
  num_global_epochs: 10
  logging_output_dirname: ./output
  logging_output_filename: result
  comm_configs:
    grpc_configs:
      server_uri: localhost:50051
      max_message_size: 1048576
      use_ssl: false

πŸ’‘ It should be noted that configuration fields such as loss_fn_path, metric_path, and model_path are the paths to the corresponding files, so we need to change their relative paths now to make sure the paths point to the right files.

⚠️ We also need change num_clients in server_configs to 1.

[3]:
server_config.client_configs.train_configs.loss_fn_path = (
    "../../examples/resources/loss/celoss.py"
)
server_config.client_configs.train_configs.metric_path = (
    "../../examples/resources/metric/acc.py"
)
server_config.client_configs.model_configs.model_path = (
    "../../examples/resources/model/cnn.py"
)
server_config.server_configs.num_clients = num_clients

Create secure SSL server and authenticatorΒΆ

Secure SSL server requires both a public certificate and a private key. Generate a fresh local CA and a server certificate signed by it with the bundled console script:

appfl-setup-ssl

By default this writes ca.key, ca.crt, server.key, and server.crt to ~/.appfl/ssl/. Keep ca.key and server.key private; ship ca.crt to every client that should trust this federation.

πŸ’‘ Please check this tutorial for more details on generating SSL certificates for production deployments (institutional PKI, Let’s Encrypt, etc.).

Then point the server config at the generated paths:

[4]:
import os

ssl_dir = os.path.expanduser("~/.appfl/ssl")
server_config.server_configs.comm_configs.grpc_configs.use_ssl = True
server_config.server_configs.comm_configs.grpc_configs.server_certificate_key = (
    f"{ssl_dir}/server.key"
)
server_config.server_configs.comm_configs.grpc_configs.server_certificate = (
    f"{ssl_dir}/server.crt"
)

Setup an authenticatorΒΆ

Now we use a naive authenticator, where the server sets a special token and uses token-match to authenticate the client.

πŸ’‘ It should be noted that the naive authenticator is only for easy demonstration and is not really safe in practice to protect your FL experiment. We also provide Globus authenticator, and you can also define your own ones.

[5]:
server_config.server_configs.comm_configs.grpc_configs.use_authenticator = True
server_config.server_configs.comm_configs.grpc_configs.authenticator = (
    "NaiveAuthenticator"
)
server_config.server_configs.comm_configs.grpc_configs.authenticator_args = {
    "auth_token": "A_SECRET_DEMO_TOKEN"
}

Start serverΒΆ

Now, we are ready to create the server agent using the server_config defined and modified above and start the grpc server.

After launching πŸš€ the server, let’s go to the notebook to launch the client to talk to the server!

πŸ’‘ After finishing the FL experiment, you need to manually stop the server.

[6]:
from appfl.agent import ServerAgent
from appfl.comm.grpc import GRPCServerCommunicator, serve

server_agent = ServerAgent(server_agent_config=server_config)

communicator = GRPCServerCommunicator(
    server_agent,
    logger=server_agent.logger,
    **server_config.server_configs.comm_configs.grpc_configs,
)

serve(
    communicator,
    **server_config.server_configs.comm_configs.grpc_configs,
)
appfl: βœ…[2026-05-31 21:49:12,776 server]: Logging to ./output/result_Server_2026-05-31-21-49-12.txt
/Users/zilinghan/Documents/projects/appfl/appfl-release/src/appfl/misc/utils.py:126: UserWarning: NaiveAuthenticator is a shared-secret scheme suitable only for demos and trusted-network testing. Use GlobusAuthenticator (or another identity-bound authenticator) in production.
  authenticator = AuthenticatorClass(**authenticator_args)
appfl: βœ…[2026-05-31 21:49:26,598 server]: Received [1/1] GetConfiguration request from client Client1
appfl: βœ…[2026-05-31 21:49:26,616 server]: Received GetGlobalModel request from client Client1
appfl: βœ…[2026-05-31 21:49:27,336 server]: Received InvokeCustomAction set_sample_size request from client Client1
appfl: βœ…[2026-05-31 21:49:32,046 server]: Received UpdateGlobalModel request from client Client1
appfl: βœ…[2026-05-31 21:49:32,145 server]: Received metadata from Client1:
{'current_local_steps': 100,
 'pre_val_accuracy': 15.93,
 'pre_val_loss': 2.30059186820012,
 'round': 1,
 'val_accuracy': 94.22,
 'val_loss': 0.19807521227426875}
appfl: βœ…[2026-05-31 21:49:38,616 server]: Received UpdateGlobalModel request from client Client1
appfl: βœ…[2026-05-31 21:49:38,718 server]: Received metadata from Client1:
{'current_local_steps': 100,
 'pre_val_accuracy': 94.22,
 'pre_val_loss': 0.1980752093616612,
 'round': 2,
 'val_accuracy': 96.7,
 'val_loss': 0.10678713586015307}
appfl: βœ…[2026-05-31 21:49:45,165 server]: Received UpdateGlobalModel request from client Client1
appfl: βœ…[2026-05-31 21:49:45,265 server]: Received metadata from Client1:
{'current_local_steps': 100,
 'pre_val_accuracy': 96.7,
 'pre_val_loss': 0.10678713719188503,
 'round': 3,
 'val_accuracy': 97.29,
 'val_loss': 0.0915415328956998}
appfl: βœ…[2026-05-31 21:49:51,653 server]: Received UpdateGlobalModel request from client Client1
appfl: βœ…[2026-05-31 21:49:51,753 server]: Received metadata from Client1:
{'current_local_steps': 100,
 'pre_val_accuracy': 97.29,
 'pre_val_loss': 0.0915415316573967,
 'round': 4,
 'val_accuracy': 98.23,
 'val_loss': 0.057862771333159674}
appfl: βœ…[2026-05-31 21:49:58,134 server]: Received UpdateGlobalModel request from client Client1
appfl: βœ…[2026-05-31 21:49:58,238 server]: Received metadata from Client1:
{'current_local_steps': 100,
 'pre_val_accuracy': 98.23,
 'pre_val_loss': 0.05786277159120128,
 'round': 5,
 'val_accuracy': 98.41,
 'val_loss': 0.05265393350219612}
appfl: βœ…[2026-05-31 21:50:04,656 server]: Received UpdateGlobalModel request from client Client1
appfl: βœ…[2026-05-31 21:50:04,756 server]: Received metadata from Client1:
{'current_local_steps': 100,
 'pre_val_accuracy': 98.41,
 'pre_val_loss': 0.05265393443796769,
 'round': 6,
 'val_accuracy': 98.49,
 'val_loss': 0.04675506340746399}
appfl: βœ…[2026-05-31 21:50:11,199 server]: Received UpdateGlobalModel request from client Client1
appfl: βœ…[2026-05-31 21:50:11,303 server]: Received metadata from Client1:
{'current_local_steps': 100,
 'pre_val_accuracy': 98.49,
 'pre_val_loss': 0.046755062995561376,
 'round': 7,
 'val_accuracy': 98.56,
 'val_loss': 0.04519760218074666}
appfl: βœ…[2026-05-31 21:50:17,691 server]: Received UpdateGlobalModel request from client Client1
appfl: βœ…[2026-05-31 21:50:17,792 server]: Received metadata from Client1:
{'current_local_steps': 100,
 'pre_val_accuracy': 98.56,
 'pre_val_loss': 0.04519760190565057,
 'round': 8,
 'val_accuracy': 98.73,
 'val_loss': 0.03950823215095943}
appfl: βœ…[2026-05-31 21:50:24,185 server]: Received UpdateGlobalModel request from client Client1
appfl: βœ…[2026-05-31 21:50:24,286 server]: Received metadata from Client1:
{'current_local_steps': 100,
 'pre_val_accuracy': 98.73,
 'pre_val_loss': 0.039508231548491604,
 'round': 9,
 'val_accuracy': 98.65,
 'val_loss': 0.04178397888937783}
appfl: βœ…[2026-05-31 21:50:30,742 server]: Received UpdateGlobalModel request from client Client1
appfl: βœ…[2026-05-31 21:50:30,843 server]: Received metadata from Client1:
{'current_local_steps': 100,
 'pre_val_accuracy': 98.65,
 'pre_val_loss': 0.041783977746728075,
 'round': 10,
 'val_accuracy': 98.56,
 'val_loss': 0.039475969079361746}
appfl: βœ…[2026-05-31 21:50:32,462 server]: Received InvokeCustomAction close_connection request from client Client1
appfl: βœ…[2026-05-31 21:50:32,858 server]: Terminating the server ...