OneLinersCommand workbench
Guides
Services & Applications / Security

Connect Apache to a dedicated PHP-FPM pool

Replace embedded mod_php with a versioned FPM socket, isolate the application pool, configure Apache's FastCGI handler, and verify both execution and static-file behavior.

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

Run PHP through a least-privilege, resource-bounded FPM pool while Apache remains responsible for HTTP and static assets.

Supported environments
  • Ubuntu Server 24.04 LTS
  • Apache HTTP Server 2.4.x
  • PHP 8.3+
Prerequisites
  • Console and recovery access Keep an independent console session and a copy of the current Apache and PHP-FPM configuration before changing listeners, modules, or access rules.
  • DNS and network plan Resolve every production hostname to the intended public address and list the exact management and client networks that require access.getent ahosts {{domain}} && ip route show
  • Port ownership Confirm that TCP port 80 is free or already belongs to the intended Apache and PHP-FPM instance.sudo ss -lntp 'sport = :80'
  • Rollback artifact Snapshot the host or create an archive of the active configuration and web roots before the first material 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
  • An Apache virtual host that sends only PHP filenames to an application-specific PHP 8.3 FPM pool over a Unix-domain socket while Apache continues to serve static files directly.
  • A least-privilege process boundary with an explicit application identity, socket ownership, worker ceiling, request recycling, execution-extension restriction, environment clearing, slow-request capture, and dedicated errors.
  • A reversible migration away from embedded mod_php to event MPM and `mod_proxy_fcgi`, with complete inventory, parser gates, a temporary non-secret functional probe, logs, capacity evidence, and rollback.
Observable outcome
  • Apache loads event MPM and proxy_fcgi, the selected site contains one sentinel-marked handler, the versioned FPM service owns the expected socket, and no global PHP handler accidentally affects other tenants.
  • A temporary `.php` probe returns the expected version and `fpm-fcgi` SAPI through the named host, while static content remains served without PHP interpretation.
  • Operators can explain the concurrency relationship between Apache and FPM, identify saturation or slow requests, resize from measured memory, and restore the previous handler safely.

Architecture

How the parts fit together

Apache accepts HTTP/TLS and maps a request to the named virtual host and local filesystem. A site-scoped `FilesMatch` assigns only `.php` paths to `mod_proxy_fcgi`, which connects to `/run/php/{{appName}}.sock`. The versioned `php8.3-fpm` systemd service creates that socket and supervises a dedicated pool running as `{{appUser}}`. Each FPM child executes one request at a time; `pm.max_children` is therefore both a concurrency ceiling and a memory budget. Static assets bypass FPM, and application errors/slow requests are retained in pool-specific logs.

Apache event MPMHandles network connections efficiently with threads after embedded mod_php and other prefork-only dependencies are removed.
mod_proxy and mod_proxy_fcgiProvide FastCGI client transport; they do not start or supervise PHP-FPM.
Site-scoped FilesMatch handlerMaps `.php` files selected by one virtual host to one Unix socket without enabling a global PHP handler.
Dedicated PHP-FPM poolRuns application code under the deployment identity with bounded workers, request timeout/recycling, and separate diagnostic logs.
Unix-domain socketRestricts local FastCGI transport through filesystem ownership and mode instead of exposing a TCP listener.
systemd and parsersSupervise FPM and Apache and reject invalid pool or web-server configuration before activation.
  1. Apache selects the requested domain and maps the URI to a local file under that site's document root.
  2. Static files remain in Apache. A matching `.php` filename receives the site-specific FastCGI handler.
  3. mod_proxy_fcgi opens the pool's local socket and supplies the filesystem-derived script metadata to FPM.
  4. An available child in the named pool executes PHP as `{{appUser}}`, returns FastCGI output, and logs application/slow-request evidence to dedicated files.
  5. Apache converts the response to HTTP, applies site policy, and returns it while service and capacity monitoring observe both layers.

Assumptions

  • Ubuntu Server 24.04 supplies PHP 8.3 packages from approved repositories, and the selected application supports that exact PHP minor release and required extensions.
  • The Apache site file `/etc/apache2/sites-available/{{domain}}.conf` is explicit, enabled, backed up, and contains the intended virtual-host blocks for this one application.
  • The application deployment user `{{appUser}}` already exists, owns only the required code/runtime paths, and is not a login or shared tenant identity.
  • Every existing Apache virtual host and module has been inventoried. Switching from prefork/mod_php to event MPM will not strand another application or incompatible module.
  • The initial worker numbers are conservative commissioning values, not universal tuning. Capacity must be derived from measured child RSS, traffic, latency, and host reserve.

Key concepts

FPM pool
A named set of PHP worker processes with its own identity, listener, process-manager policy, limits, and selected PHP administrative settings.
FastCGI handler
Apache's mapping from selected local filenames to a separate application process using the FastCGI protocol.
Unix-domain socket
A local filesystem-addressed inter-process channel whose owner, group, and mode can restrict which service connects.
event MPM
Apache's threaded processing model that separates some keep-alive handling from request workers and is incompatible with embedded non-thread-safe mod_php.
pm.max_children
The maximum number of simultaneous requests a pool can execute and consequently a primary bound on its process memory consumption.
security.limit_extensions
An FPM restriction on script filename extensions accepted for execution; it complements, but does not replace, correct Apache path mapping.
Slow log
A diagnostic backtrace recorded when a request exceeds `request_slowlog_timeout`, helping distinguish application stalls from web-server transport failures.

Before you copy

Values used in this guide

{{domain}}

Existing Apache ServerName and document-root directory whose PHP handler is migrated.

Example: app.example.com
{{appName}}

Short lowercase pool identifier used in configuration, socket, sentinel, and log names.

Example: billing
{{appUser}}

Dedicated unprivileged Unix identity that reads application code and writes only approved runtime directories.

Example: billing-app
{{phpVersion}}

Versioned PHP configuration directory; this tutorial deliberately targets Ubuntu 24.04's PHP 8.3 packages.

Example: 8.3

Security and production boundaries

  • Do not run an Internet application pool as root, `www-data`, a human administrator, or a shared identity. Filesystem access should reveal only that application's code, uploads, cache, and secrets required at runtime.
  • Socket mode 0660 and group `www-data` permit Apache to connect without making FastCGI world-accessible. Never expose FPM on public TCP/9000.
  • A `.php` extension rule is necessary but insufficient. Keep uploads outside executable code paths, disable PHP execution in writable directories, and validate application routing.
  • `clear_env=yes` reduces accidental environment leakage, but applications still need a controlled secret-injection mechanism and must never expose `phpinfo()` in production.
  • Disabling mod_php and switching MPM affects the whole Apache process. Migrate all dependent tenants or perform a staged host replacement rather than silently breaking unrelated sites.
  • Worker and timeout limits protect capacity but can terminate legitimate work. Size from evidence, move long jobs to a queue, and alert on saturation and slow logs.

Stop before continuing if

  • Stop if any existing vhost still requires mod_php, an incompatible module requires prefork, or application/PHP extension compatibility is unproven.
  • Do not overwrite a pre-existing pool, socket, sentinel, application log, or site handler until its owner and rollback are documented.
  • Do not restart FPM or reload Apache after either parser fails, the site maps to another tenant, or socket ownership differs from the reviewed model.
  • Stop if the temporary probe displays source code, returns a different SAPI/version, reaches a different vhost, leaks configuration, or cannot be removed.
  • Do not accept commissioning without static-file checks, application transactions, error/slow logs, saturation metrics, and an application-owner validation.
01

verification

Identify the active PHP handler

read-only

Record PHP versions, loaded Apache modules, FPM services, socket paths, and current site handler before removing or disabling anything.

Why this step matters

The migration changes the process model for the entire Apache instance, so every PHP version, MPM, embedded module, FPM service, socket, handler, vhost, and dependent tenant must be known first.

What to understand

Record CLI PHP separately from web PHP; they can have different versions, extensions, ini files, and SAPI.

Inspect all enabled site/conf/module files for PHP handlers, not just the selected domain. A shared global handler or mod_php dependency makes this a multi-tenant migration.

Capture listener, service failure, memory, load, and representative application behavior so post-change differences are measurable.

System changes

  • No persistent change; reads package, process, Apache, PHP, socket, and service state.

Syntax explained

php -v || true
Records CLI PHP when present without hiding the rest of the inventory if it is absent.
apache2ctl -M
Shows the active MPM, embedded PHP, and proxy modules.
systemctl list-units 'php*-fpm.service'
Lists loaded versioned FPM services.
find /run/php -type s
Locates active Unix sockets and their inode metadata.
Command
php -v || true; sudo apache2ctl -M | grep -E 'php|proxy|mpm' || true; systemctl list-units 'php*-fpm.service'; find /run/php -maxdepth 1 -type s -ls 2>/dev/null || true
Example output / evidence
PHP 8.3.x
mpm_prefork_module (shared)
php8.3_module (shared)

Checkpoint: Checkpoint: inventory

Continue whenAll PHP consumers and handlers are mapped, the selected application supports PHP 8.3, and no unknown dependency blocks event MPM.

Stop whenAnother tenant, shared handler, version requirement, extension, MPM-only module, or undocumented socket is discovered.

If this step fails

The inventory shows both mod_php and a global FPM conf.

Likely causeA partial previous migration created overlapping handlers.

Safe checks
  • sudo apache2ctl -M | grep -E 'php|proxy_fcgi|mpm'
  • sudo grep -R -nE 'SetHandler.*php|proxy:unix' /etc/apache2

ResolutionMap which directive wins for each tenant and create a staged migration/rollback plan before changing modules.

Security notes

  • Inventory may reveal application paths and versions; protect the change record and never publish phpinfo output.

Alternatives

  • Build a replacement host and cut traffic over when shared-instance dependency risk is too high.

Stop conditions

  • Another tenant, shared handler, version requirement, extension, MPM-only module, or undocumented socket is discovered.
02

command

Preserve Apache and PHP configuration

caution

Archive enabled modules, sites, and PHP ini/FPM files before changing the request execution model.

Why this step matters

Module symlinks, MPM selection, site handlers, pool files, and ini state interact, so restoring only one edited file can leave Apache and FPM in incompatible states.

What to understand

Archive both `/etc/apache2` and `/etc/php`, list the archive, verify checksum, and record the prior active module/service states.

Back up application code and data through application-aware procedures; this configuration archive does not capture database transactions or uploads.

Store protected evidence outside served and deployment roots because ini/pool/site files may reveal secret paths or credentials.

System changes

  • Creates a root-owned compressed archive and checksum containing Apache and PHP configuration.

Syntax explained

tar -C /
Preserves canonical relative configuration paths for controlled restore.
-czf
Creates a gzip-compressed archive at the explicit protected path.
sha256sum
Records archive integrity for restore validation.
Command
sudo tar -C / -czf /root/apache-php-before-fpm.tgz etc/apache2 etc/php && sudo sha256sum /root/apache-php-before-fpm.tgz
Example output / evidence
8be1...  /root/apache-php-before-fpm.tgz

Checkpoint: Checkpoint: backup

Continue whenThe archive is non-empty, root-only, lists both trees, verifies by checksum, and has a documented restore sequence.

Stop whenEither configuration tree or application data needed for rollback is missing, changing during capture, or unreadable.

If this step fails

Archive succeeds but does not contain enabled module symlinks.

Likely causeThe wrong source path or dereference/exclusion behavior was used.

Safe checks
  • sudo tar -tzf /root/apache-php-before-fpm.tgz | grep 'etc/apache2/mods-enabled' | head

ResolutionCreate and inspect a corrected archive before touching handlers or MPMs.

Security notes

  • Configuration archives can reveal secret locations and database credentials; keep mode, retention, and transport restricted.

Alternatives

  • Use a tested immutable image/snapshot rollback that includes both service configurations and application release metadata.

Stop conditions

  • Either configuration tree or application data needed for rollback is missing, changing during capture, or unreadable.
03

command

Install FPM and required Apache proxy modules

caution

Install Ubuntu's versioned FPM and CLI packages, enable only the FastCGI proxy modules, and switch Apache to event MPM only after confirming every existing mod_php site is in migration scope. Do not enable Ubuntu's global PHP-FPM handler because this guide binds one named virtual host to one dedicated pool.

Why this step matters

Versioned Ubuntu packages and explicit module changes establish one supportable runtime while avoiding the distribution's global FPM handler that could alter unrelated sites.

What to understand

Review apt policy and transaction before installing PHP 8.3 FPM/CLI. Install extensions only after the application dependency list is approved.

Disable every enabled versioned mod_php module only after inventory proves all tenants are migrated. event MPM cannot coexist with the common non-thread-safe embedded module.

Enable `proxy_fcgi` and `setenvif`, but intentionally leave the generic `php8.3-fpm` Apache conf disabled; the selected site receives an explicit handler later.

System changes

  • Installs PHP 8.3 FPM/CLI, changes Apache module symlinks from prefork/mod_php to event/proxy_fcgi, and may start the default FPM service.

Syntax explained

php8.3-fpm
Installs the versioned daemon and default service/pool configuration.
a2dismod
Removes enabled-module symlinks; it does not uninstall packages.
a2enmod mpm_event
Selects Apache's threaded event processing module.
a2enmod proxy_fcgi setenvif
Enables FastCGI transport and environment handling required by the handler.
Command
sudo apt update && sudo apt install --yes php8.3-fpm php8.3-cli; for module in /etc/apache2/mods-enabled/php*.load; do [ -e "$module" ] && sudo a2dismod "$(basename "$module" .load)"; done; sudo a2dismod mpm_prefork; sudo a2enmod mpm_event proxy_fcgi setenvif
Example output / evidence
Considering dependency proxy for proxy_fcgi:
Enabling module proxy.
Enabling module proxy_fcgi.
Enabling module mpm_event.

Checkpoint: Checkpoint: install

Continue whenApproved packages are installed, exactly event MPM/proxy_fcgi are selected for the migration, and no generic PHP handler is enabled.

Stop whenAPT origin/version differs, module dependency output mentions an unreviewed consumer, another tenant remains on mod_php, or service startup fails.

If this step fails

a2enmod reports that mpm_event conflicts with mpm_prefork.

Likely causeAn embedded PHP or other module still requires prefork, or the disable step did not cover its enabled symlink.

Safe checks
  • sudo apache2ctl -M | grep -E 'php|mpm'
  • ls -l /etc/apache2/mods-enabled/*mpm* /etc/apache2/mods-enabled/php* 2>/dev/null

ResolutionDo not force the switch; identify and migrate or isolate every dependency, then select one MPM.

Security notes

  • A global handler can execute uploaded `.php` in unintended sites. Keep it disabled and scope execution per vhost.

Alternatives

  • Migrate on a replacement host or keep prefork temporarily when shared-instance compatibility cannot be resolved safely.

Stop conditions

  • APT origin/version differs, module dependency output mentions an unreviewed consumer, another tenant remains on mod_php, or service startup fails.
04

config

Create an application-specific FPM pool

caution

Run the pool as the deployment identity, use a private socket accessible to Apache, cap worker growth and recycling, restrict executable extensions, clear inherited environment variables, terminate overrunning requests, and write dedicated error and slow logs.

Why this step matters

A dedicated pool creates an application identity and capacity boundary; explicit socket, process-manager, extension, environment, timeout, recycling, and log settings prevent reliance on broad defaults.

What to understand

Verify `{{appUser}}` before writing the file. It should read immutable releases and write only application-specific cache/upload/session paths.

FPM creates the socket below `/run/php` when the service starts. Apache connects as `www-data` through owner/group/mode 0660; the application worker still runs as `{{appUser}}`.

Treat 20 children as an initial ceiling. Measure resident memory and request concurrency; `pm.max_requests` recycles workers but does not fix leaks or unsafe extensions.

`security.limit_extensions=.php` and site `FilesMatch` reinforce the execution boundary. `clear_env=yes` avoids inheriting service-manager environment by accident.

System changes

  • Creates `/etc/php/8.3/fpm/pool.d/{{appName}}.conf` and declares dedicated logs/socket that appear after activation.

Syntax explained

user/group
Select the Unix identity used by application worker processes.
listen.owner/group/mode
Permit Apache to connect to the local socket without world access.
pm=dynamic
Maintains a bounded number of idle workers and grows up to the ceiling.
pm.max_children=20
Caps simultaneous requests and primary pool process memory.
pm.max_requests=500
Recycles a child after a bounded number of requests.
request_slowlog_timeout=5s
Captures a backtrace after five seconds.
request_terminate_timeout=120s
Terminates a worker request that exceeds the hard ceiling.
clear_env=yes
Clears inherited environment variables before request processing.
File /etc/php/{{phpVersion}}/fpm/pool.d/{{appName}}.conf
Configuration
Fill variables0/2 ready

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

id {{appUser}} && printf '[{{appName}}]\nuser={{appUser}}\ngroup=www-data\nlisten=/run/php/{{appName}}.sock\nlisten.owner=www-data\nlisten.group=www-data\nlisten.mode=0660\npm=dynamic\npm.max_children=20\npm.start_servers=2\npm.min_spare_servers=2\npm.max_spare_servers=6\npm.max_requests=500\nsecurity.limit_extensions=.php\nclear_env=yes\nrequest_terminate_timeout=120s\nrequest_slowlog_timeout=5s\nslowlog=/var/log/php-fpm-{{appName}}-slow.log\nphp_admin_flag[log_errors]=on\nphp_admin_value[error_log]=/var/log/php-fpm-{{appName}}-error.log\ncatch_workers_output=yes\n' | sudo tee /etc/php/8.3/fpm/pool.d/{{appName}}.conf
Example output / evidence
[example]
listen=/run/php/example.sock
pm.max_children=20
request_terminate_timeout=120s

Checkpoint: Checkpoint: pool

Continue whenOne uniquely named pool parses, references the intended identity/socket/logs, and has a documented initial capacity calculation.

Stop whenThe user is absent/shared, pool name or socket already exists, paths are writable too broadly, or limits cannot support known workload safely.

If this step fails

FPM parser reports an unknown entry or duplicate listen address.

Likely causeA typo, wrong version semantics, or another pool already owns the name/socket.

Safe checks
  • sudo php-fpm8.3 -t
  • sudo grep -R -nE '^\[billing\]|^listen=/run/php/billing.sock' /etc/php/8.3/fpm/pool.d

ResolutionCorrect the versioned file and choose one unique pool/socket; never hide parser errors before restart.

Security notes

  • Pool isolation is only as strong as filesystem permissions, shared extensions, kernel boundary, and application secret handling.

Alternatives

  • Use a separate service/container/host when the application needs a stronger version, extension, or tenant boundary.

Stop conditions

  • The user is absent/shared, pool name or socket already exists, paths are writable too broadly, or limits cannot support known workload safely.
05

config

Bind one virtual host to the pool socket

caution

Limit the FastCGI handler to .php files in this host, use a sentinel so rerunning the step cannot duplicate the handler, and keep the site-specific socket out of global Apache configuration.

Why this step matters

The handler belongs inside the selected site so another vhost cannot send arbitrary PHP files to this application's identity; a sentinel makes the edit repeatable rather than duplicating directives.

What to understand

Inspect the site file before and after insertion. The supplied command adds the marked block before each closing VirtualHost only when the sentinel is absent.

If HTTP and HTTPS live in separate files, apply an independently reviewed handler to each intended active site rather than assuming one file covers both.

Apache's handler form allows normal URI-to-filename mapping before FastCGI. Keep uploaded writable paths outside PHP execution scope and verify missing scripts are not forwarded.

System changes

  • Adds one sentinel-marked FilesMatch/SetHandler block to the selected Apache site file.

Syntax explained

grep -q sentinel ||
Skips insertion on rerun when the managed handler already exists.
FilesMatch "\.php$"
Selects filenames ending in `.php`; it is not an authorization rule.
proxy:unix:/run/php/…|fcgi://localhost/
Connects to the exact local FPM socket using Apache's FastCGI handler syntax.
sed i before </VirtualHost>
Places the site-scoped handler inside each block in the reviewed file.
File /etc/apache2/sites-available/{{domain}}.conf
Configuration
Fill variables0/2 ready

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

sudo grep -q 'ONELINERS PHP-FPM {{appName}}' /etc/apache2/sites-available/{{domain}}.conf || sudo sed -i '\#</VirtualHost>#i\  # ONELINERS PHP-FPM {{appName}}\n  <FilesMatch "\\.php$">\n    SetHandler "proxy:unix:/run/php/{{appName}}.sock|fcgi://localhost/"\n  </FilesMatch>' /etc/apache2/sites-available/{{domain}}.conf
Example output / evidence
<FilesMatch "\.php$">
SetHandler "proxy:unix:/run/php/example.sock|fcgi://localhost/"

Checkpoint: Checkpoint: handler

Continue whenThe enabled intended site contains one marked handler for the exact socket and no global/unrelated site gains PHP execution.

Stop whenThe site file contains multiple tenants, separate active TLS file is omitted, sentinel exists with different content, or diff affects another directive.

If this step fails

Rerunning the command leaves duplicate or malformed handler blocks.

Likely causeThe sentinel was edited/removed or the site has a nonstandard generated structure.

Safe checks
  • sudo grep -R -n -A4 -B2 'ONELINERS PHP-FPM' /etc/apache2/sites-available
  • sudo apache2ctl configtest

ResolutionRestore the archived site file and make a deliberate manual/site-template edit with one stable marker.

Security notes

  • Never use a catch-all global PHP handler on hosts with user uploads or multiple trust domains.

Alternatives

  • Manage the full virtual-host file declaratively through configuration management instead of applying an inline edit.

Stop conditions

  • The site file contains multiple tenants, separate active TLS file is omitted, sentinel exists with different content, or diff affects another directive.
06

verification

Validate PHP-FPM and Apache before restart

read-only

Use the versioned FPM parser, Apache configtest, module list, and virtual-host map before applying the new execution path.

Why this step matters

FPM and Apache have independent parsers and dependency graphs; both must accept the exact files and module selection before either service is restarted or reloaded.

What to understand

Run the versioned FPM parser first, then Apache configtest and module inventory. Confirm event MPM and proxy_fcgi are present while mod_php is absent.

Dump the virtual-host map and inspect the handler sentinel. A syntax pass cannot prove that the requested domain selects the intended site.

Keep the old application serving until all static checks pass; validation is the safe gate between file edits and runtime change.

System changes

  • No persistent change; parses configuration and reads the selected module graph.

Syntax explained

php-fpm8.3 -t
Parses global FPM and every included pool file.
apache2ctl configtest
Parses Apache's complete enabled include graph.
apache2ctl -M
Confirms selected MPM and FastCGI module state.
Command
sudo php-fpm8.3 -t && sudo apache2ctl configtest && sudo apache2ctl -M | grep -E 'mpm_event|proxy_fcgi'
Example output / evidence
NOTICE: configuration file test is successful
Syntax OK
mpm_event_module (shared)
proxy_fcgi_module (shared)

Checkpoint: Checkpoint: validate

Continue whenBoth parsers succeed, event MPM and proxy_fcgi are selected, mod_php is absent, and the intended vhost contains one correct handler.

Stop whenEither parser warns/errors, module state differs, virtual-host map overlaps, or the handler targets another socket.

If this step fails

Both parsers pass but apache2ctl -M still shows prefork.

Likely causeThe active module symlink selection did not change or Apache has not restarted since MPM changes.

Safe checks
  • ls -l /etc/apache2/mods-enabled/*mpm*
  • sudo apache2ctl -M | grep mpm

ResolutionVerify enabled symlinks and perform the reviewed restart/reload sequence only after every dependency is cleared.

Security notes

  • Parser success does not authorize the change; review ownership, scope, diff, and execution paths separately.

Alternatives

  • Validate an identical replacement instance and cut traffic when shared-host activation risk is unacceptable.

Stop conditions

  • Either parser warns/errors, module state differs, virtual-host map overlaps, or the handler targets another socket.
07

warning

Restart FPM and reload Apache

danger

Restart the exact versioned FPM service to create the socket, verify its ownership, then reload Apache without dropping established connections.

Why this step matters

FPM must start first to create the reviewed socket; Apache should reload only after ownership is proven so client requests never point at a missing or world-accessible backend.

What to understand

Restart the exact versioned FPM service and inspect status, journal, socket type, owner, group, mode, and pool processes before touching Apache.

Because switching MPM can require a full Apache restart rather than a reload, check the actual service result and established-connection impact in the maintenance plan.

Watch both journals and the application health endpoint during activation. Roll back promptly if unrelated tenants or static content regress.

System changes

  • Restarts PHP 8.3 FPM, creating the pool/socket/processes, and activates Apache's new module/site configuration.

Syntax explained

systemctl restart php8.3-fpm
Recreates all versioned pools and sockets after successful parser validation.
stat /run/php/…
Shows socket type, owner, group, and mode before Apache connects.
systemctl reload apache2
Requests graceful configuration activation; an MPM transition may require a controlled restart.
Command
Fill variables0/1 ready

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

sudo systemctl restart php8.3-fpm && sudo stat /run/php/{{appName}}.sock && sudo systemctl reload apache2
Example output / evidence
Socket: /run/php/example.sock
Access: (0660/srw-rw----) Uid: (www-data) Gid: (www-data)

Checkpoint: Checkpoint: activate

Continue whenFPM is active with the named pool and 0660 www-data socket, Apache is healthy on event MPM, and existing site checks remain green.

Stop whenService, journal, socket, process identity, MPM, listener, or any existing tenant differs from pre-change expectation.

If this step fails

Apache reload reports that a restart is required or leaves the old MPM active.

Likely causeMPM modules are process-lifetime choices and cannot always change through graceful reload.

Safe checks
  • systemctl status apache2 --no-pager
  • sudo apache2ctl -M | grep mpm
  • sudo journalctl -u apache2 -n 100 --no-pager

ResolutionUse the approved maintenance window for a controlled Apache restart with continuous external probes and immediate rollback capability.

Security notes

  • Verify socket permissions before the first request; never solve connectivity by chmod 0777.

Alternatives

  • Cut over to a prevalidated replacement host for applications requiring zero observable restart impact.

Stop conditions

  • Service, journal, socket, process identity, MPM, listener, or any existing tenant differs from pre-change expectation.
08

verification

Prove PHP execution without publishing phpinfo

read-only

Create a temporary deterministic script, request it through the named host, confirm the FPM SAPI, and remove it immediately.

Why this step matters

A short deterministic script proves routing, PHP version, and FPM SAPI without exposing the extensive environment, modules, paths, and secrets revealed by `phpinfo()`.

What to understand

Write only fixed code under the intended document root, request it with an explicit Host mapping to loopback, and remove it immediately even when the request fails.

Confirm static files still bypass PHP and an absent `.php` path returns the application's expected 404 rather than reaching an arbitrary script.

Run representative read-only application checks and one approved transaction, then inspect Apache, FPM, and application logs for warnings.

System changes

  • Creates a temporary public PHP probe, executes it through the local named vhost, removes it, and writes normal request/error log entries.

Syntax explained

PHP_VERSION
Returns the web runtime version selected by the FPM service.
PHP_SAPI
Returns `fpm-fcgi`, proving the request did not execute through CLI or embedded mod_php.
curl --resolve
Tests the intended Host and local listener without waiting for public DNS.
rm after request
Removes the diagnostic endpoint immediately to avoid permanent information disclosure.
Command
Fill variables0/1 ready

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

printf '<?php echo PHP_VERSION," ",PHP_SAPI,"\\n";' | sudo tee /srv/www/{{domain}}/public/.fpm-check.php >/dev/null && curl --fail --resolve '{{domain}}:80:127.0.0.1' http://{{domain}}/.fpm-check.php && sudo rm /srv/www/{{domain}}/public/.fpm-check.php && sudo tail -n 20 /var/log/apache2/{{domain}}-error.log
Example output / evidence
8.3.x fpm-fcgi

Checkpoint: Checkpoint: functional test

Continue whenThe named host returns the approved PHP 8.3 version and `fpm-fcgi`, static content remains correct, and the temporary file is absent afterward.

Stop whenSource is displayed, version/SAPI/tenant differs, the probe remains public, static content regresses, or logs show permission/saturation errors.

If this step fails

The probe returns 404 although it exists.

Likely causeDocumentRoot differs, the request selected another vhost, an application front controller intercepts it, or file permissions deny traversal.

Safe checks
  • sudo apache2ctl -S
  • namei -l /srv/www/app.example.com/public/.fpm-check.php
  • sudo tail -n 50 /var/log/apache2/app.example.com-error.log

ResolutionIdentify the actual active document root and routing policy; never broaden permissions or proxy arbitrary paths just to make the probe pass.

Security notes

  • Never publish `phpinfo()` or environment dumps; remove even the minimal probe immediately and verify its URL returns 404 afterward.

Alternatives

  • Use an existing authenticated application health endpoint that reports only a non-secret runtime marker.

Stop conditions

  • Source is displayed, version/SAPI/tenant differs, the probe remains public, static content regresses, or logs show permission/saturation errors.

Finish line

Verification checklist

Both parserssudo php-fpm8.3 -t && sudo apache2ctl configtestBoth report successful syntax.
Pool socketsudo stat -c '%A %U %G %n' /run/php/{{appName}}.sockSocket is 0660 and accessible to Apache without being world-writable.

Recovery guidance

Common problems and safe checks

Apache returns `503 Service Unavailable` and the error log reports failure to connect to the Unix socket.

Likely causeThe FPM service failed, the pool parser rejected configuration, the socket path differs, or Apache lacks traversal/write permission on the socket.

Safe checks
  • sudo php-fpm8.3 -t
  • systemctl status php8.3-fpm --no-pager
  • sudo stat -c '%A %U %G %n' /run/php/billing.sock
  • sudo journalctl -u php8.3-fpm -n 100 --no-pager

ResolutionCorrect the exact pool or handler path and socket permissions, restart only FPM, verify the socket, parse Apache, then reload.

The browser downloads PHP source or displays it as plain text.

Likely causeThe vhost has no matching handler, a different site serves the path, proxy_fcgi is disabled, or the sentinel edit missed the active TLS vhost.

Safe checks
  • sudo apache2ctl -S
  • sudo apache2ctl -M | grep proxy_fcgi
  • sudo grep -R -n 'ONELINERS PHP-FPM' /etc/apache2/sites-enabled

ResolutionRemove the public probe immediately, correct the handler in every intended active block, parse, reload, and retest with a non-secret script.

Apache will not enable event MPM.

Likely causemod_php or another prefork-dependent module remains enabled, or a current tenant still requires the old execution model.

Safe checks
  • sudo apache2ctl -M | grep -E 'php|mpm|ruid|itk'
  • ls -l /etc/apache2/mods-enabled
  • sudo grep -R -nE 'SetHandler.*php|application/x-httpd-php' /etc/apache2

ResolutionInventory and migrate every dependency; do not force the MPM switch on a shared production host.

Requests queue or time out while CPU remains moderate.

Likely causeAll FPM children are busy, external dependencies are slow, or persistent FastCGI connection behavior consumes pool capacity.

Safe checks
  • ps -o pid,rss,etime,cmd -C php-fpm8.3
  • sudo tail -n 100 /var/log/php-fpm-billing-slow.log
  • sudo journalctl -u php8.3-fpm -n 100 --no-pager

ResolutionFix the slow dependency or application path first; resize `pm.max_children` only from measured RSS and host memory reserve.

The application receives permission denied for cache, upload, session, or log paths.

Likely causeThe new dedicated identity previously relied on broad `www-data` ownership or an undocumented writable directory.

Safe checks
  • sudo -u billing-app test -r /srv/www/app.example.com/public/index.php
  • sudo -u billing-app find /srv/www/app.example.com -maxdepth 3 -not -user billing-app -ls
  • namei -l /srv/www/app.example.com/var

ResolutionGrant the application identity the smallest explicit runtime directories; do not recursively chown the whole host or make paths world-writable.

The FPM service starts but the named pool is absent.

Likely causeThe pool file has invalid syntax, duplicate name/listener, wrong version directory, or was ignored by the include pattern.

Safe checks
  • sudo php-fpm8.3 -t
  • sudo grep -n '^include' /etc/php/8.3/fpm/php-fpm.conf
  • sudo grep -R -n '^\[billing\]\|^listen=' /etc/php/8.3/fpm/pool.d

ResolutionPlace one uniquely named file in the versioned pool directory, remove conflicts under change control, and pass the parser before restart.

After the procedure

Alternatives and next steps

Consider these alternatives

  • Use one distribution default FPM pool for a single low-risk host, accepting weaker tenant isolation but still keeping the socket private.
  • Run separate versioned FPM services, containers, or hosts when applications require conflicting PHP versions, extensions, identities, or maintenance windows.
  • Use a framework-specific application server or container architecture when deployments already provide a supported process and health model.
  • Keep prefork/mod_php temporarily only when a documented compatibility blocker exists; plan migration because it prevents the intended event-MPM separation.

Operate it safely

  • Install only application-required PHP extensions from approved repositories, record them, and rerun application tests after every version or extension change.
  • Measure per-child RSS under representative load and reserve memory for Apache, kernel, cache, monitoring, and incident response before changing `pm.max_children`.
  • Expose a protected FPM status/ping endpoint only to local monitoring, then alert on active/idle processes, listen queue, max-children reached, failures, and slow logs.
  • Separate writable uploads/cache from executable source and explicitly prevent PHP handling in user-controlled directories.
  • Add a zero-downtime application deployment process with immutable releases, health checks, opcache strategy, database compatibility, rollback, and external probes.

Reference

Frequently asked questions

Why not enable Ubuntu's global `php8.3-fpm` Apache conf?

It can attach a generic handler beyond the intended tenant. This guide makes the socket relationship explicit inside the selected site.

Is `pm.max_children=20` a recommended production value?

It is only a conservative starting ceiling. Measure child RSS and workload latency, retain host reserve, and tune with saturation evidence.

Why use a Unix socket instead of TCP/9000?

A local socket avoids an unnecessary network listener and provides an explicit filesystem owner/group/mode boundary between Apache and one pool.

Recovery

Rollback

Restore the prior Apache handler and PHP configuration, then restart the previous execution model.

  1. Restore /etc/apache2 and /etc/php from /root/apache-php-before-fpm.tgz.
  2. Run both PHP-FPM and Apache syntax tests before restarting either service.
  3. Re-enable the prior MPM and PHP module only if the archived configuration used mod_php.
  4. Remove the dedicated pool only after confirming no virtual host references its socket.

Evidence

Sources and review

Verified 2026-07-25Review due 2026-10-23
PHP-FPM configuration referenceofficialApache mod_proxyofficialApache HTTP Server 2.4 documentationofficialUbuntu: install Apache2officialUbuntu: configure Apache2official