skip to content

Pools & Web Server Wiring

Each pool file defines a worker group with its own user, listen socket and PHP settings, wired to Nginx fastcgi_pass or Apache proxy_fcgi. Interviewers probe socket permissions and SCRIPT_FILENAME.

on this pageshow

explore

questions

6

When wiring a PHP-FPM pool to a web server, when should it listen on a Unix socket versus TCP, and how is each secured?

level: middleimportance: must knowfreq 55%

answer

  1. same host vs across hosts or containers
  2. listen.owner, listen.group, listen.mode 0660
  3. permission denied becomes 502
  4. listen.allowed_clients only for TCP
  5. PHP_VALUE lets a client rewrite ini

basics

~20 s

Use a Unix socket when the web server and PHP-FPM share a host, secured by listen.owner, listen.group and listen.mode; use TCP across hosts or containers, restricted with listen.allowed_clients and a firewall, because an exposed FastCGI port lets anyone run PHP.

solid answer

~50 s

The pool's `listen` directive takes either a path, giving a Unix domain socket, or an address and port for TCP. A **Unix socket** works only on the same host, skips the TCP stack, and is protected by file permissions: `listen.owner`, `listen.group` and `listen.mode` (default `0660`, owner defaulting to the user running the FPM master). If the web server's user cannot read and write it, every PHP request fails with a permission error and a 502. **TCP** is needed when the web server runs on another host or container; restrict it with `listen.allowed_clients` (TCP only) and bind to a private address, never a public one. An exposed FastCGI port is remote code execution: a client chooses `SCRIPT_FILENAME` and can pass `PHP_VALUE`/`PHP_ADMIN_VALUE` parameters that rewrite ini settings. Performance differences are small; the choice is about topology and security.

code

ini · 11 lines
ini
; same host as nginx: Unix socket the web server can open
[app]
listen = /run/php/$pool.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

; separate web tier: private TCP address, allow-listed client
[api]
listen = 10.0.1.5:9000
listen.allowed_clients = 10.0.1.10

go deeper

for a junior

Recall that listen takes a socket path or an address and port, and that the web server must be able to connect to whichever you choose.

for a middle

Explain listen.owner, listen.group and listen.mode for sockets, listen.allowed_clients for TCP, and why a wrong socket owner shows up as a 502.

for a senior

Treat an exposed FastCGI port as remote code execution: explain SCRIPT_FILENAME and PHP_VALUE, bind privately, allow-list clients and firewall the port.

for a principal

Choose the topology — co-located sockets, a shared-volume sidecar or a private network — from how the web and PHP tiers are deployed and scaled.

## What listen accepts Each PHP-FPM **pool** accepts FastCGI requests on the address given by its mandatory `listen` directive. The sample `www.conf` accepts four forms: | Form | Example | Meaning | |---|---|---| | `ip:port` | `listen = 127.0.0.1:9000` | TCP on one IPv4 address | | `[ipv6]:port` | `listen = [::1]:9000` | TCP on one IPv6 address | | `port` | `listen = 9000` | TCP on **all** addresses | | path | `listen = /run/php/app.sock` | Unix domain socket | The variable `$pool` expands to the pool name, so `listen = /run/php/$pool.sock` gives each pool its own socket. `listen.backlog` sets the queue of connections waiting for a free worker for either kind. ## Unix domain sockets A Unix socket is a file-system object. It only works between processes on the **same host** (or containers sharing a mounted directory), and it avoids the TCP/IP stack, ports and connection-state tables. Access is controlled by the socket file's ownership and mode: - `listen.owner` and `listen.group` — default to the user running the FPM master (usually root) and that user's group. - `listen.mode` — default `0660`: read and write for owner and group only. - `listen.acl_users` / `listen.acl_groups` — POSIX ACLs; when set, `listen.owner` and `listen.group` are ignored. On Linux the connecting process needs read and write permission, so the web server's user (for example `www-data` or `nginx`) must be the owner, in the group, or listed in the ACL. The classic failure: a hand-written pool with no `listen.owner`, so the socket is `root:root 0660`, the web server gets *permission denied* on connect, and every PHP page returns **502 Bad Gateway**. Set `listen.owner` and `listen.group` explicitly in every pool file you write. ## TCP sockets TCP is required when the web server and FPM are on **different hosts** or in separate containers without a shared volume. It costs a little more per request and, under very high rates, can run into ephemeral-port and TIME_WAIT limits on the web-server side, but for most sites the difference is not measurable. What matters is exposure: 1. **Bind narrowly.** `listen = 9000` binds every interface. Prefer a private address such as `10.0.1.5:9000`, or `127.0.0.1:9000` on one host. 2. **`listen.allowed_clients`** — a comma-separated list of client IPs allowed to connect. It only makes sense with TCP; left blank, **any** address is accepted. 3. **Firewall** the port as well; FPM's own list is a second line, not the only one. ## Why an exposed FastCGI port is critical FastCGI has no authentication. Whoever can connect sends the request parameters, and PHP-FPM trusts them: - `SCRIPT_FILENAME` decides **which file** PHP executes — any PHP file on the server's disk that the pool's user can read and that passes `security.limit_extensions`. - `PHP_VALUE` and `PHP_ADMIN_VALUE` parameters are parsed as ini settings for that request — the second at system level — so an attacker can change settings such as `auto_prepend_file` and turn a request into arbitrary code execution. So a reachable FPM port is equivalent to handing out a shell as the pool user. That is why the sample file binds to `127.0.0.1` and why internet-facing FPM ports are a well-known audit finding. ## Choosing - **Same host, one web server:** Unix socket, with `listen.owner`/`listen.group` set to the web server's user and group. - **Several pools on one host:** one socket per pool, `/run/php/$pool.sock`. - **Web tier and PHP tier on separate machines or containers:** TCP on a private network, `listen.allowed_clients`, firewall rules. - **Container sidecars sharing a volume:** a Unix socket in the shared volume works and removes the network exposure. ## Diagnosing a broken connection Every one of these shows up to the user as **502 Bad Gateway**; the web server's error log tells them apart: - **Connection refused** — nothing listens on that TCP address and port: FPM is down, or the pool listens elsewhere. - **No such file or directory** — the socket path in the web server differs from the pool's `listen`, or FPM is not running (it creates the socket when it starts). - **Permission denied** — the socket exists but its owner, group or mode excludes the web server's user. After changing `listen`, reload FPM and update the web server's FastCGI target to match.

  • After adding a new pool, nginx logs a permission-denied error on the socket and returns 502. What do you check?
    The socket file's owner, group and mode. Without `listen.owner`/`listen.group`, FPM creates it owned by the master's user (usually root) with mode `0660`, so the web server's user cannot connect. Set `listen.owner` and `listen.group` to the web server's user and group, or use `listen.acl_users`, then reload PHP-FPM.
  • Why is `listen = 9000` riskier than `listen = 127.0.0.1:9000`?
    A bare port binds on all addresses, so any host that can route to the machine can speak FastCGI to the pool unless a firewall or `listen.allowed_clients` stops it. A FastCGI client picks `SCRIPT_FILENAME` and can send `PHP_VALUE`/`PHP_ADMIN_VALUE` ini overrides, which amounts to remote code execution.

saying these in an interview costs you the question

  • A Unix socket can be used by a web server on another machine
  • listen.allowed_clients also restricts access to a Unix socket
  • FastCGI authenticates the web server, so an open port is harmless
  • TCP is always much slower, so it should never be used
  • The default socket permissions let any local user connect
open as a page

Every PHP request through Nginx to PHP-FPM returns 'File not found.'; what does SCRIPT_FILENAME do, and what usually went wrong?

level: juniorimportance: should knowfreq 45%

basics

~20 s

SCRIPT_FILENAME is the FastCGI parameter telling PHP-FPM which file to execute. 'File not found.' means FPM could not resolve or open that path — usually the parameter is missing or points to a path that does not exist where FPM runs.

open as a page

Why does getenv('DATABASE_URL') return false in PHP under PHP-FPM when the variable is set in the service's environment?

level: middleimportance: should knowfreq 40%

basics

~20 s

PHP-FPM's clear_env defaults to yes, so workers start with an empty environment plus only the pool's env[] entries. Add env[DATABASE_URL] = $DATABASE_URL to copy it from the master's environment, or set clear_env = no, then reload FPM.

open as a page

In a PHP-FPM pool file, what is the difference between php_value and php_admin_value, and when do you use each?

level: middleimportance: should knowfreq 42%

basics

~20 s

Both set php.ini directives for one PHP-FPM pool. php_value is a default that ini_set() or .user.ini may still change where the directive allows; php_admin_value locks it. Use admin for security and resource limits, plain for defaults.

open as a page

How would you run two PHP applications under different Unix users behind one Nginx, using PHP-FPM pools?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Define one PHP-FPM pool per application, each with its own user and group, its own listen socket that the Nginx user can open, and locked per-app settings via php_admin_value; each Nginx server block passes PHP requests to its app's socket.

open as a page

Under PHP-FPM, what does fastcgi_finish_request() do, and what are its pitfalls when used for background work?

level: seniorimportance: nice to knowfreq 25%

basics

~20 s

fastcgi_finish_request() sends all buffered output to the client and ends the HTTP response, while the PHP script keeps running. The worker stays busy, locks such as the session remain held, later output is lost, and the function exists only under PHP-FPM.

open as a page