logrotate killed by its own SIGUSR1 when a php-fpm pool is idle (misc-logrotate/php-fpm.j2)

apnscp version: 3.2.0 (build 46e264822)
OS: AlmaLinux 9.8
Reproducible: yes — steps below


What happens

logrotate.service is killed by SIGUSR1 and dies mid-run:

logrotate.service: Main process exited, code=killed, status=10/USR1

Everything that sorts after php-fpm in /etc/logrotate.d silently stops rotating for that run (on a stock box that’s the rsyslog set — messages, secure, maillog, cron), and logrotate.status isn’t written, so that run’s completed rotations repeat next time. It’s a signal kill rather than a PHP exception, so debug mode / a backtrace doesn’t apply here.

Why

The generated /etc/logrotate.d/php-fpm (from misc-logrotate/templates/php-fpm.j2, line 7) is:

/bin/kill -SIGUSR1 "$(systemctl show -pMainPID  php-fpm-{{ fpm_service_name }}  | cut -d= -f2)" 2> /dev/null || true

php-fpm pools are socket-activated, so an idle pool is inactive (dead) and its MainPID is 0:

# systemctl show -pMainPID php-fpm-MAIN
MainPID=0

cut -d= -f2 gives 0, so the line runs kill -SIGUSR1 0. Per POSIX, kill with PID 0 signals every process in the sender’s own process group — and logrotate runs postrotate scripts in its own process group (no setsid/setpgid), so the signal lands on logrotate itself. logrotate installs no USR1 handler, so it terminates. 2> /dev/null || true doesn’t save it: the kill succeeds, it just hits the wrong target.

Steps to reproduce

With the pool idle (systemctl show -pMainPID php-fpm-MAINMainPID=0), force the stanza:

logrotate -f /etc/logrotate.d/php-fpm

→ the logrotate process is killed by SIGUSR1.

In normal daily runs it needs two things at once: the pool’s log non-empty (so notifempty actually rotates it) and the pool not running — e.g. after a reboot that leaves the pool idle. It also self-arms: a successful USR1 makes php-fpm log error log file re-opened, so the log is non-empty the next night.

Fix

Use the systemd-native form apnscp already ships in /etc/logrotate.d/rspamd — it targets the unit’s own main process and is a safe no-op when the pool is down:

systemctl --signal=USR1 --kill-who=main kill php-fpm-{{ fpm_service_name }}.service 2>/dev/null || :

(--kill-who is --kill-whom on systemd ≥ 252; both work.) That’s also the semantically correct behaviour: a dead pool holds no open handle on the rotated file, and re-opens the new path when socket activation next starts it.

Note for existing installs

The task deploys with force: "{{ force | default(False) }}", so upcp won’t re-render an existing /etc/logrotate.d/php-fpm — a template fix alone won’t reach current boxes; they’d need a migration.


Environment: apnscp 3.2.0 (46e264822), AlmaLinux 9.8, logrotate 3.18.0, systemd 252, util-linux 2.37.4.

Thanks for the bug report. Fixed in edge :+1:

1 Like