OneLinersCommand workbench
Guides
Services & Applications / Security

Configure a dedicated PHP-FPM pool for one application

Separate Unix identity, socket, resource limits, logs, environment, and health checks for an application instead of sharing the default pool.

45 min8 stepsChanges system stateRevision 2
Save or explore
Save to collectionCreate a collection in the sidebar first.
0 of 8 steps completed
Goal

Limit cross-application impact and make PHP worker ownership, capacity, and failures observable.

Supported environments
  • PHP-FPM 8.2, 8.3, 8.4, 8.5
Prerequisites
  • Supported host and recovery access Use a supported distribution, keep an independent root-capable session, and record the current package repositories before changing the PHP runtime.cat /etc/os-release && uname -m
  • Application compatibility Confirm the application, framework, extensions, and deployment tooling explicitly support the target PHP branch.
  • Configuration backup Archive the active PHP, web-server, and application configuration before packages, pools, or handlers change.
Operating boundary

OneLiners never runs these steps or stores secrets. Review placeholders, versions, current state, and change-control requirements before using a command.

Full guide

What you will build

System
  • A least-privilege PHP-FPM pool for one application with its own Unix identity, root-owned configuration, private runtime directories and a 0660 Unix socket shared only with the selected web-server group.
  • A capacity-based ondemand worker policy, worker recycling, clean environment, PHP-only execution, pool-scoped error/session/upload paths, and protected slow/status/ping observability.
  • A validation and canary sequence proving configuration syntax, service health, socket/worker identity, FastCGI reachability, write-boundary isolation and rollback to the prior site handler.
Observable outcome
  • The web worker can connect to the socket but cannot write application source; the application worker can write only approved runtime directories and never runs as root or the web identity.
  • Peak worker memory and host headroom justify `pm.max_children`; overload queues/fails predictably instead of exhausting the host, and workers recycle after a bounded request count.
  • Status, ping, slow log and errors are observable only through local/authenticated administration paths, while final HTTP and filesystem tests prove the intended application boundary.

Architecture

How the parts fit together

The web server parses HTTP and forwards only reviewed PHP routes to `/run/php/php{{phpVersion}}-{{app}}.sock`. FPM's privileged master creates the socket, then workers execute as `{{appUser}}:{{appGroup}}`. Socket group `{{webGroup}}` grants connect permission without application ownership. The pool uses ondemand workers capped at eight as a documented starting value; operators must replace it from measured RSS and concurrency. Application source is read-only to workers, while `/srv/{{app}}/var`, `tmp`, sessions and logs are the explicit write set. Status/ping are FPM endpoints, not public application pages, and must be exposed only on an internal or authenticated location.

Web serverTerminates HTTP and sends only intended scripts to FastCGI.
Unix socketLimits which local process group can submit FastCGI requests.
FPM master and poolCreates bounded workers and applies pool-specific policy.
Application identityOwns runtime data without owning the web server or privileged configuration.
Private runtime directoriesContain sessions, uploads and logs outside the public document root.
Protected observabilityExposes health/capacity signals without publishing sensitive status data.
  1. nginx or Apache selects an allowed PHP script and connects through the group-readable socket.
  2. An ondemand FPM worker starts under the application identity, loads pool/runtime policy and executes the script.
  3. The worker reads source, writes only approved runtime paths and returns FastCGI output.
  4. Slow/error/status signals go to private paths or a restricted management location; operators validate identity and capacity.

Assumptions

  • The exact PHP branch/service/binary are known, FPM currently validates, and a previous site handler is archived for rollback.
  • The application has a managed non-login identity, read-only release model and explicit writable directories; shared-host multi-tenant risk has been reviewed.
  • `{{webGroup}}` is the actual nginx/Apache worker group, and AppArmor/SELinux plus parent directory traversal are intentionally configured.
  • Representative traffic or staging data exists to estimate worker RSS and concurrency; the example ceiling of eight is not copied blindly.

Key concepts

FPM pool
A named group of PHP workers with shared process, identity, socket, logging and runtime policy.
pm.max_children
The hard number of simultaneous workers/requests; memory and latency determine a safe value.
ondemand
A process-manager mode that starts workers when needed and removes them after an idle timeout.
socket mode
Filesystem owner/group/permissions controlling which local processes can submit FastCGI requests.
clear_env
A policy that clears arbitrary inherited environment variables before worker execution unless explicitly allowed.
slow log
A backtrace written when a request exceeds `request_slowlog_timeout`, used as sensitive diagnostic evidence.

Before you copy

Values used in this guide

{{app}}

Short unique pool and path identifier.

Example: billing
{{appUser}}

Dedicated non-login worker user.

Example: billing
{{appGroup}}

Private application group.

Example: billing
{{webGroup}}

Existing nginx/Apache worker group allowed to connect.

Example: www-data
{{phpVersion}}

Installed versioned PHP-FPM unit/binary suffix.

Example: 8.3
{{domain}}

Canary hostname whose reviewed handler targets this pool.

Example: billing.example.com

Security and production boundaries

  • A separate Unix user is a useful boundary but not a container or VM. Kernel, shared extensions and readable host paths remain common.
  • The web server must connect to the socket but must not write application source; the FPM worker must not own privileged configuration.
  • Status, ping, slow logs and PHP error logs expose paths, queries and runtime data. Keep them local or strongly authenticated and redact exports.
  • Do not solve permission failures with chmod 777, shared root/web identities, `clear_env=no`, disabled MAC policy or a TCP listener on all interfaces.

Stop before continuing if

  • Stop if current memory/concurrency cannot be measured, the host has insufficient headroom, or the example child ceiling would threaten other services.
  • Do not reload when FPM syntax fails, identity/path/socket collides, source becomes worker-writable, or status is publicly reachable.
  • Do not cut traffic until direct socket, HTTP health, worker identity, write-boundary and rollback tests all pass.
01

instruction

Measure memory and concurrency

read-only

Estimate a worker budget from representative process RSS and available memory; do not copy arbitrary pm.max_children values.

Why this step matters

Each child can execute one request and consumes application-dependent memory; a copied ceiling can either starve traffic or trigger host-wide OOM.

What to understand

Measure RSS after representative warm requests, not an idle generic process, and reserve memory for kernel, cache, web/database/agents and failure spikes.

Estimate concurrency and queue tolerance, then load-test. `MemoryCurrent` includes more than individual RSS and is useful corroboration.

System changes

  • No persistent change; reads process RSS, host memory and current service memory.

Syntax explained

ps -o rss
Shows resident KiB for each existing FPM process.
free -m
Shows host memory and available headroom.
systemctl show ... MemoryCurrent
Reports cgroup memory accounted to the FPM unit.
Command
ps --no-headers -o rss,cmd -C php-fpm | sort -n; free -m; systemctl show php-fpm -p MemoryCurrent
Example output / evidence
Worker RSS and host memory headroom are recorded.

Checkpoint: Checkpoint: capacity

Continue whenA documented worker RSS range, reserved host budget, target concurrency and justified initial `pm.max_children` exist.

Stop whenRepresentative RSS/concurrency is unavailable, swapping/OOM already occurs, or no memory is reserved for other services.

If this step fails

No current FPM workers exist to measure.

Likely causeThe application is new or ondemand workers are idle.

Safe checks
  • systemctl is-active php8.3-fpm
  • free -m

ResolutionWarm a staging/canary pool with representative requests and measure it before production sizing.

Security notes

  • Resource exhaustion is an availability risk; never leave child count unlimited.

Alternatives

  • Start with a smaller explicit ceiling and a canary load test when production measurements do not yet exist.

Stop conditions

  • Representative RSS/concurrency is unavailable, swapping/OOM already occurs, or no memory is reserved for other services.
02

command

Create a non-login application account

caution

Use a dedicated Unix identity and group, then grant the web server traverse access only where required.

Why this step matters

A dedicated non-login identity separates application execution and writable runtime data from the web server and other pools.

What to understand

Create group/user only when absent and stop if an existing name has unexpected UID/GID/home/shell.

Public source should be deployed read-only separately; `var`, `tmp`, logs and sessions are the deliberate write set.

System changes

  • Creates the system group/user and `/srv/{{app}}` public/runtime directories with explicit ownership and mode.

Syntax explained

getent group ... || groupadd
Creates the private system group idempotently.
id -u ... || useradd
Creates a non-login system user only when absent.
install -d -m 0750
Creates explicit private directories without permissive defaults.
Command
Fill variables0/3 ready

Values stay on this page and are never sent or saved.

getent group {{appGroup}} >/dev/null || sudo groupadd --system {{appGroup}}; id -u {{appUser}} >/dev/null 2>&1 || sudo useradd --system --gid {{appGroup}} --home-dir /srv/{{app}} --shell /usr/sbin/nologin {{appUser}}; sudo install -d -o {{appUser}} -g {{appGroup}} -m 0750 /srv/{{app}}/{public,var,tmp,var/log,var/sessions}
Example output / evidence
The managed non-login identity exists and owns its private runtime directories.

Checkpoint: Checkpoint: account

Continue whenThe identity is non-login with intended UID/GID/home, runtime paths are private, and web user cannot write the application tree.

Stop whenNames already belong to another service, source/runtime data would be overwritten, or parent traversal/access policy is unknown.

If this step fails

The user exists but has a login shell or another primary group.

Likely causeThe name was reused or provisioned manually.

Safe checks
  • getent passwd billing
  • id billing
  • namei -l /srv/billing

ResolutionChoose a unique managed identity or reconcile it through approved account management before assigning files.

Security notes

  • Never reuse the nginx/Apache identity or grant the application sudo/login access.

Alternatives

  • Provision the same identity and paths declaratively through system management.

Stop conditions

  • Names already belong to another service, source/runtime data would be overwritten, or parent traversal/access policy is unknown.
03

config

Create the pool configuration

caution

Define a complete unique pool and Unix socket instead of editing the packaged www pool.

Why this step matters

One complete root-owned pool file makes identity, socket, capacity, runtime and observability policy reviewable as a unit.

What to understand

Replace all placeholders before save and compare against current versioned FPM directive support.

The file is 0640 root-owned; workers consume it but cannot modify their own execution policy.

The sample uses ondemand and eight children only as a bounded starting point from the previous calculation.

System changes

  • Creates `/etc/php/{{phpVersion}}/fpm/pool.d/{{app}}.conf` with the displayed structured configuration.

Syntax explained

listen
Names the unique local FastCGI socket.
pm = ondemand
Starts workers only on demand.
clear_env = yes
Clears arbitrary inherited variables.
security.limit_extensions = .php
Restricts accepted script suffixes.
php_admin_*
Applies pool settings that scripts cannot override.
File /etc/php/{{phpVersion}}/fpm/pool.d/{{app}}.conf
Configuration
Fill variables0/5 ready

Values stay on this page and are never sent or saved.

[{{app}}]
user = {{appUser}}
group = {{appGroup}}
listen = /run/php/php{{phpVersion}}-{{app}}.sock
listen.owner = {{appUser}}
listen.group = {{webGroup}}
listen.mode = 0660
pm = ondemand
pm.max_children = 8
pm.process_idle_timeout = 10s
pm.max_requests = 500
pm.status_path = /fpm-{{app}}-status
ping.path = /fpm-{{app}}-ping
ping.response = pong
request_slowlog_timeout = 5s
slowlog = /srv/{{app}}/var/log/php-fpm-slow.log
catch_workers_output = yes
clear_env = yes
security.limit_extensions = .php
php_admin_flag[log_errors] = on
php_admin_flag[display_errors] = off
php_admin_value[error_log] = /srv/{{app}}/var/log/php-error.log
php_admin_value[upload_tmp_dir] = /srv/{{app}}/tmp
php_admin_value[session.save_path] = /srv/{{app}}/var/sessions
Command
Fill variables0/2 ready

Values stay on this page and are never sent or saved.

sudo install -o root -g root -m 0640 /dev/null /etc/php/{{phpVersion}}/fpm/pool.d/{{app}}.conf; sudoedit /etc/php/{{phpVersion}}/fpm/pool.d/{{app}}.conf
Example output / evidence
[{{app}}], identity, socket, workers, runtime limits, status, slow log, and environment policy are explicit.

Checkpoint: Checkpoint: pool

Continue whenThe complete file is root-owned 0640, has no placeholders/duplicates and passes `php-fpm{{phpVersion}} -tt`.

Stop whenAny path/identity/socket is unverified, directive conflicts with global policy, or configuration test reports a warning/error.

If this step fails

The configuration test reports an unknown or duplicate directive.

Likely causePHP version differs, the file is included twice, or an option was copied incorrectly.

Safe checks
  • sudo php-fpm8.3 -tt
  • grep -R -n '^\[billing\]' /etc/php/8.3/fpm/pool.d

ResolutionUse official docs for the installed branch, remove only the unintended duplicate and retest.

Security notes

  • Pool configuration controls executable identity and writable paths; workers must not own it.

Alternatives

  • Render the same file from a reviewed configuration-management template.

Stop conditions

  • Any path/identity/socket is unverified, directive conflicts with global policy, or configuration test reports a warning/error.
04

config

Constrain socket access

caution

Set listen.owner, listen.group, and mode 0660 so only the intended web-server group can submit FastCGI requests.

Why this step matters

The socket is the local authorization point between web server and application runtime; owner/group/mode must be explicit and verified after service creation.

What to understand

Owner `{{appUser}}`, group `{{webGroup}}` and 0660 let the web worker connect without world access.

Check the actual web service group and MAC policy rather than assuming `www-data` on every distribution.

System changes

  • No new file beyond the pool policy; verifies socket directives before service reload.

Syntax explained

listen.owner
Sets socket owner when FPM creates it.
listen.group
Grants the reviewed web group connect access.
listen.mode = 0660
Denies all access to other users.
Configuration
Fill variables0/2 ready

Values stay on this page and are never sent or saved.

sudo grep -E '^(listen|listen\.(owner|group|mode))' /etc/php/{{phpVersion}}/fpm/pool.d/{{app}}.conf
Example output / evidence
Socket path and 0660 ownership are explicit.

Checkpoint: Checkpoint: socket

Continue whenExactly one unique socket path and intended owner/group/mode are configured, with no TCP wildcard listener.

Stop whenWeb group is wrong/shared too broadly, socket collides, parent runtime directory is unsafe, or MAC policy is unresolved.

If this step fails

Web server cannot connect despite 0660.

Likely causeIts actual group differs, supplementary group is stale, parent/MAC policy blocks, or it targets another path.

Safe checks
  • ps -o user,group,cmd -C nginx -C apache2
  • namei -l /run/php/php8.3-billing.sock
  • sudo ausearch -m AVC -ts recent

ResolutionCorrect the exact handler/group/path/policy mismatch; never make the socket world-writable.

Security notes

  • Socket connect permission is equivalent to submitting PHP execution requests to this pool.

Alternatives

  • Use an ACL or loopback namespaced listener when group-based Unix socket access cannot express the boundary.

Stop conditions

  • Web group is wrong/shared too broadly, socket collides, parent runtime directory is unsafe, or MAC policy is unresolved.
05

config

Set an evidence-based process policy

caution

Choose dynamic or ondemand mode, bound children and idle time, and recycle workers with pm.max_requests.

Why this step matters

Process policy converts measured memory/concurrency into predictable capacity and recycles long-lived workers without hiding application leaks.

What to understand

Ondemand saves idle memory but adds cold-start latency; dynamic mode may suit steady workloads.

A hard child ceiling causes queuing rather than unbounded memory growth; monitor saturation and adjust from evidence.

System changes

  • No separate mutation; verifies the process directives stored in the pool file.

Syntax explained

pm.max_children
Hard concurrent worker/request limit.
pm.process_idle_timeout
Removes idle ondemand workers after the interval.
pm.max_requests
Recycles each worker after a bounded request count.
Configuration
Fill variables0/2 ready

Values stay on this page and are never sent or saved.

sudo grep -E '^(pm|pm\.)' /etc/php/{{phpVersion}}/fpm/pool.d/{{app}}.conf
Example output / evidence
pm mode, max_children, spare limits or idle timeout, and max_requests are set.

Checkpoint: Checkpoint: workers

Continue whenMode, ceiling, idle timeout and recycle count match the documented capacity calculation and load test.

Stop whenThe ceiling is copied without memory math, load test exceeds latency/SLO, or workers trigger swap/OOM.

If this step fails

`server reached pm.max_children` appears.

Likely causeConcurrency exceeds the ceiling or requests/dependencies are too slow.

Safe checks
  • journalctl -u php8.3-fpm --since '-30 min' | grep -F max_children
  • ps -o rss,pid,etime,cmd -C php-fpm8.3

ResolutionFind blocked/slow work first, then recalculate and load-test any bounded increase.

Security notes

  • Capacity bounds protect host availability from traffic bursts and expensive requests.

Alternatives

  • Use dynamic mode with explicit min/max spare servers after measuring stable traffic.

Stop conditions

  • The ceiling is copied without memory math, load test exceeds latency/SLO, or workers trigger swap/OOM.
06

config

Apply pool-scoped runtime limits

caution

Set upload, execution, error logging, session, and temporary paths inside the pool without exposing secrets through clear_env=no.

Why this step matters

Pool-scoped administrative values keep errors, sessions and temporary uploads private and prevent application code from weakening critical settings.

What to understand

Use paths outside the public root and owned only by the application identity.

Keep `display_errors` off and `log_errors` on; logs can contain secrets and require retention/access controls.

System changes

  • No separate file; verifies pool-scoped PHP administrative values in the root-owned pool configuration.

Syntax explained

php_admin_flag
Sets a boolean that scripts cannot override.
php_admin_value
Sets a pool value that scripts cannot override.
upload_tmp_dir/session.save_path
Places writable transient data outside the document root.
Configuration
Fill variables0/2 ready

Values stay on this page and are never sent or saved.

sudo grep -E '^(php_admin_|php_value|env\[)' /etc/php/{{phpVersion}}/fpm/pool.d/{{app}}.conf
Example output / evidence
Pool overrides point to application-owned private paths.

Checkpoint: Checkpoint: runtime

Continue whenPrivate paths exist/writable only as intended, errors are logged not displayed, and no secrets are imported via environment.

Stop whenRuntime path is public/shared/symlinked unexpectedly, worker cannot write it, or log/session privacy policy is absent.

If this step fails

Uploads or sessions fail after canary cutover.

Likely causePrivate path ownership, mode, capacity, confinement or cleanup policy is wrong.

Safe checks
  • namei -l /srv/billing/tmp /srv/billing/var/sessions
  • df -h /srv/billing
  • sudo -u billing test -w /srv/billing/tmp

ResolutionFix the narrow path/capacity/policy and retest; do not fall back to `/tmp` or document-root writes blindly.

Security notes

  • Sessions, uploads and logs are sensitive attacker-influenced data; isolate and never execute them.

Alternatives

  • Use an application-supported external session store while keeping upload/error paths private.

Stop conditions

  • Runtime path is public/shared/symlinked unexpectedly, worker cannot write it, or log/session privacy policy is absent.
07

config

Enable a restricted status endpoint and slow log

caution

Verify pm.status_path, ping.path, request_slowlog_timeout, slowlog, and catch_workers_output; expose status only through a localhost or authenticated management location.

Why this step matters

Pool-specific status, ping and slow logs distinguish capacity from application/dependency failures, but their contents are too sensitive for a public route.

What to understand

Expose status/ping only through a localhost, mTLS or strongly authenticated management location that forwards to this exact pool.

Slow logs capture stack/path details after five seconds; set retention and generate a controlled fixture to prove the path.

System changes

  • Verifies observability directives and application-owned log directory; web-server exposure is a separate restricted configuration.

Syntax explained

pm.status_path
Defines the internal FastCGI status URI.
ping.path/ping.response
Defines a lightweight pool liveness response.
request_slowlog_timeout
Starts a worker backtrace after the threshold.
catch_workers_output
Redirects worker stdout/stderr into FPM logging.
Configuration
Fill variables0/2 ready

Values stay on this page and are never sent or saved.

sudo grep -E '^(pm.status_path|ping.path|request_slowlog_timeout|slowlog|catch_workers_output)' /etc/php/{{phpVersion}}/fpm/pool.d/{{app}}.conf; sudo namei -l /srv/{{app}}/var/log
Example output / evidence
Status, ping, slowlog, worker output, and protected application-owned log paths are present.

Checkpoint: Checkpoint: observability

Continue whenPaths are configured, logs are writable/protected, local status/ping works and public unauthenticated access is rejected.

Stop whenStatus is public, log path is public/unprotected, backtraces violate policy, or test traffic cannot be isolated.

If this step fails

Status returns 404 while application PHP works.

Likely causeWeb server did not route the status URI to this pool or passed the wrong SCRIPT_NAME.

Safe checks
  • grep -R -n 'fpm-billing-status' /etc/nginx /etc/apache2
  • sudo php-fpm8.3 -tt

ResolutionCreate a local-only exact FastCGI location and verify both positive local and negative public tests.

Security notes

  • Status and slow logs reveal capacity, paths and code flow; restrict, redact and retain them deliberately.

Alternatives

  • Collect equivalent pool metrics through a local exporter without exposing an HTTP route.

Stop conditions

  • Status is public, log path is public/unprotected, backtraces violate policy, or test traffic cannot be isolated.
08

verification

Validate, reload, and inspect the pool

caution

Run the versioned FPM configuration test before reload, then confirm the socket and child identity.

Why this step matters

The final gate must prove parser, graceful reload, socket creation and worker identity before a user-facing canary can be trusted.

What to understand

Run the versioned FPM test before reload; a failed left side must prevent mutation.

Inspect actual socket metadata and trigger a canary request so an ondemand worker exists for identity verification.

System changes

  • Gracefully reloads the versioned FPM service and activates the new pool/socket.

Syntax explained

php-fpm{{phpVersion}} -tt
Tests and dumps the effective versioned configuration.
systemctl reload
Requests a graceful configuration reload.
ss -lxnp
Proves the configured socket is listening.
ps -eo user,group
Proves worker Unix identity after a canary request.
Command
Fill variables0/2 ready

Values stay on this page and are never sent or saved.

sudo php-fpm{{phpVersion}} -tt; sudo systemctl reload php{{phpVersion}}-fpm; sudo ss -lxnp | grep '{{app}}'; ps -eo user,group,pid,cmd | grep '[p]hp-fpm: pool {{app}}'
Example output / evidence
Syntax succeeds, the socket listens, and workers use the application identity.

Checkpoint: Checkpoint: validate

Continue whenSyntax is clean, service stays active, socket metadata matches policy, worker runs as app identity, and final HTTP/isolation checks pass.

Stop whenAny parser/service/socket/identity/health/write-boundary check fails or the prior handler cannot be restored.

If this step fails

Reload reports success but no worker appears.

Likely causeOndemand mode has not received a request, the handler points elsewhere, or the pool was not included.

Safe checks
  • sudo php-fpm8.3 -tt
  • curl -vk https://billing.example.com/health.php
  • ps -eo user,group,pid,cmd | grep '[p]hp-fpm: pool billing'

ResolutionGenerate the controlled canary through the exact handler, confirm inclusion and stop if it selects another pool.

Security notes

  • Verify negative write/public-status tests before accepting the boundary.

Alternatives

  • Use direct `cgi-fcgi` socket testing before changing the public handler.

Stop conditions

  • Any parser/service/socket/identity/health/write-boundary check fails or the prior handler cannot be restored.

Finish line

Verification checklist

FastCGI healthcurl --fail --silent https://{{domain}}/health.phpThe application endpoint returns its expected health response.
Isolation proofsudo -u www-data test ! -w /srv/{{app}} && sudo -u {{appUser}} test -w /srv/{{app}}/varThe web-server account cannot write the application tree, while the pool account can write only runtime storage.

Recovery guidance

Common problems and safe checks

The web server returns 502/503 while FPM is active.

Likely causeHandler socket differs, web group lacks connect permission, no worker can start, or MAC/path policy denies access.

Safe checks
  • sudo ss -lxnp | grep php8.3-billing
  • namei -l /run/php/php8.3-billing.sock /srv/billing/public
  • sudo journalctl -u php8.3-fpm -n 150 --no-pager
  • sudo ausearch -m AVC -ts recent

ResolutionCorrect the exact socket/group/path/policy mismatch and retest; never broaden all filesystem permissions.

Requests queue or time out under load.

Likely causeAll children are busy, workers are too slow, backend dependencies block, or memory pressure causes reclaim/OOM.

Safe checks
  • systemctl show php8.3-fpm -p MemoryCurrent
  • ps -o rss,pid,etime,cmd -C php-fpm8.3
  • journalctl -k --since '-30 min' | grep -i oom
  • curl --silent http://127.0.0.1/fpm-billing-status?full

ResolutionFix blocking work first, then recalculate children from measured RSS/headroom and load-test a bounded increase.

FPM starts but status or slow logs remain empty.

Likely causeThe web server does not route the internal status path, the threshold was not crossed, or worker identity cannot write the log path.

Safe checks
  • grep -E '^(pm.status_path|request_slowlog_timeout|slowlog)' /etc/php/8.3/fpm/pool.d/billing.conf
  • namei -l /srv/billing/var/log

ResolutionAdd a local-only FastCGI status location and fix the narrow application log path; use a controlled slow fixture.

After the procedure

Alternatives and next steps

Consider these alternatives

  • Use `dynamic` mode for steady traffic where measured warm-worker latency justifies reserved memory.
  • Use a container/VM per application when Unix identity and path controls do not meet tenant isolation requirements.
  • Use a loopback TCP socket only when Unix sockets are unavailable and local firewall/namespace boundaries are equally explicit.

Operate it safely

  • Load-test representative traffic and tune worker ceiling, idle timeout and recycling from latency, RSS, queue and dependency evidence.
  • Add pool-specific CPU/memory controls with systemd/cgroups only after verifying shared-unit effects and application write requirements.
  • Integrate status/error/slow-log metrics with bounded cardinality and alerts for queue saturation, failed workers and restarts.
  • Move deployments to immutable releases, keep runtime data separate, and automate parser plus canary plus rollback checks.

Reference

Frequently asked questions

Why not run FPM as `www-data`?

That collapses the web parser and application execution identities, allowing one compromise to gain broader write/read reach.

Is eight children a recommendation?

No. It is a visible bounded example. Replace it using measured per-worker RSS, reserved host memory and concurrency targets.

Should the socket be owned by the web server?

The FPM master creates it. This guide uses the app user as owner and web group for connect access, keeping application ownership separate.

Recovery

Rollback

Restore the prior handler and remove the dedicated pool only after traffic is drained.

  1. Point the virtual host back to the recorded prior socket, validate, and reload the web server.
  2. Disable the pool by renaming its .conf file, validate FPM, and reload.
  3. Preserve application logs and do not delete the Unix account until ownership dependencies are audited.

Evidence

Sources and review

Verified 2026-07-25Review due 2026-10-23
PHP-FPM installation and configurationofficialPHP core configuration directivesofficialUbuntu Server: install PHPofficial