Skip to content

Reloading a master safely

Reloading a master safely

Changing a pool's settings means writing configuration and telling the master to re-read it. The order is not a preference — it is the difference between a graceful reload and a dead host.

Validate, then reload

A configuration php-fpm rejects does not degrade politely. The master refuses to come back, and every pool it served goes with it. So a caller changing pm.* must prove php-fpm accepts the configuration before anything is signalled:

// 1. Ask php-fpm whether it accepts the configuration, without applying it.
if err := phpfpm.Validate(ctx, master.Binary, master.ConfigPath); err != nil {
	return fmt.Errorf("php-fpm rejected the configuration: %w", err)
}

// 2. Only now reload.
newPID, err := phpfpm.ReloadAndWait(ctx, phpfpm.ReloadTarget{
	PID:        master.PID,
	ConfigPath: master.ConfigPath,
}, 2*time.Second, log)
if err != nil {
	return fmt.Errorf("the master did not survive the reload: %w", err)
}

Validate runs php-fpm -t against the configuration and returns the rejection verbatim if there is one. Verified against a real master: a drop-in naming a pool that no longer exists makes php-fpm -t exit 78, and reloading with that file present kills the master permanently.

Confirm the master, not the pid

ReloadAndWait sends SIGUSR2 and then watches the master actually survive the settle window you give it. Two things make this more than "is the pid still alive":

  • A daemonized master re-execs into a new pid. In the foreground the pid survives; daemonized — php-fpm's own default — the master replaces itself and the original exits. ReloadAndWait follows the master across the re-exec and returns the pid it came back as. A caller watching the pid it signalled would report a textbook reload as a dead master.
  • A master that fails late initialisation dies a moment after the signal, not instantly. So the settle window is watched to its end rather than accepted at the first glimpse of a live process — a master that exists for a moment and then exits is caught, not mistaken for success.

If the master genuinely dies with nothing taking its place, ReloadAndWait returns an error. The change did not take, and the caller's job is to put the previous configuration back.

Validation is not the same as survival

Validate forks a separate process with no sockets to bind and no pools to start. A live master can still fail to initialise on a configuration that validated — a socket it cannot bind, a permission it does not have. That is exactly why the reload is watched rather than trusted: validation is necessary, and it is not sufficient.

For the reload semantics in full, see Reload, not restart.