PHP and PHP-FPM¶
PHP powers a large share of the dynamic web (WordPress, Laravel, Drupal, and more). The modern way to run it is PHP-FPM (FastCGI Process Manager) — a standalone pool of PHP workers that your web server talks to over FastCGI. This page covers installing PHP/PHP-FPM, wiring it into nginx and Apache, tuning php.ini, and testing.
Tested on
AlmaLinux 9 / RHEL 9 with PHP 8.x from the AppStream module. Debian/Ubuntu notes are inline.
LAMP vs LEMP¶
These are the two classic PHP stacks:
| Stack | Components | Web server |
|---|---|---|
| LAMP | Linux + Apache + MySQL/MariaDB + PHP | Apache |
| LEMP | Linux + Enginx (the "E" is the nginx sound) + MySQL/MariaDB + PHP | nginx |
The difference is just the web server. In both modern variants, PHP runs as PHP-FPM rather than embedded in the web server process.
Install PHP and PHP-FPM¶
RHEL 9 packages PHP as an AppStream module. See which versions (streams) are available:
You will see streams like 8.1 and 8.2. Optionally pin one, then install PHP, the FPM service, and the most common extensions:
# Optional: select a specific stream (e.g. 8.2)
sudo dnf module enable -y php:8.2
# Core PHP, FPM, and frequently-needed extensions
sudo dnf install -y php php-fpm php-mysqlnd php-gd php-mbstring php-xml php-curl
# Enable and start the FPM service
sudo systemctl enable --now php-fpm
php -v
| Extension | Why you usually need it |
|---|---|
php-mysqlnd |
MySQL/MariaDB database driver |
php-gd |
Image manipulation (thumbnails, captcha) |
php-mbstring |
Multibyte/UTF-8 string handling |
php-xml |
XML / many framework dependencies |
php-curl |
Outbound HTTP from PHP |
Where does PHP-FPM listen?¶
On RHEL/AlmaLinux the default pool (/etc/php-fpm.d/www.conf) listens on a Unix socket:
On Debian it is typically /run/php/php8.2-fpm.sock. A Unix socket is faster and more secure than a TCP port for same-host communication. Note the path — you reference it from the web server below.
Wire PHP into nginx (LEMP)¶
Nginx has no PHP module; it must proxy PHP requests to PHP-FPM over FastCGI. Add a location that matches .php files to your server {} block:
server {
listen 80;
server_name example.com;
root /var/www/example.com;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# Hand .php requests to PHP-FPM
location ~ \.php$ {
# Pull in the standard FastCGI parameter mappings
include fastcgi_params;
# Pass to the PHP-FPM Unix socket (RHEL path; Debian differs)
fastcgi_pass unix:/run/php-fpm/www.sock;
# The single most important parameter: the actual script to run
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_index index.php;
}
# Optional but recommended: never serve hidden files like .htaccess, .git
location ~ /\.(?!well-known) {
deny all;
}
}
SCRIPT_FILENAME is mandatory
Without SCRIPT_FILENAME, PHP-FPM does not know which file to execute and you get a blank page or "No input file specified". The line above derives it from the request — keep it.
SELinux for the socket and outbound calls
On RHEL/AlmaLinux, nginx talking to the FPM Unix socket works under the default policy. But if your PHP app connects out (e.g. to a database on another host, or an external API), allow it persistently: sudo setsebool -P httpd_can_network_connect 1. And remember custom roots still need semanage fcontext -t httpd_sys_content_t + restorecon — see SELinux.
Then:
Wire PHP into Apache¶
Apache has two options:
Option A — PHP-FPM via mod_proxy_fcgi (recommended)¶
Works with any MPM (including the default event MPM on RHEL 9) and keeps PHP out of Apache's process. On RHEL the php package installs a drop-in (/etc/httpd/conf.d/php.conf) that already wires this up. The core directive looks like:
<FilesMatch \.php$>
# Proxy PHP scripts to the FPM Unix socket
SetHandler "proxy:unix:/run/php-fpm/www.sock|fcgi://localhost"
</FilesMatch>
Enable mod_proxy_fcgi (on Debian: sudo a2enmod proxy_fcgi setenvif && sudo a2enconf php8.2-fpm).
Option B — mod_php (legacy)¶
Embeds PHP directly in Apache. It requires the prefork MPM (it is not thread-safe), uses more memory, and is no longer the default on RHEL 9. Prefer Option A unless you have a specific reason.
After either option:
php.ini essentials¶
The active php.ini for FPM is /etc/php.ini on RHEL (Debian: /etc/php/8.2/fpm/php.ini). Confirm which file is loaded with php-fpm -i | grep 'Loaded Configuration File' (or check a phpinfo() page).
Settings you will almost always review:
; Maximum memory a single script may use
memory_limit = 256M
; Largest file a user can upload
upload_max_filesize = 64M
; Total POST body size — must be >= upload_max_filesize
post_max_size = 64M
; Maximum seconds a script may run
max_execution_time = 120
; ALWAYS set this — avoids per-call timezone warnings
date.timezone = "Asia/Kolkata"
; Production: do NOT show errors to visitors; log them instead
display_errors = Off
log_errors = On
After editing the FPM php.ini, restart the FPM service (a reload of the web server is not enough — PHP runs in the FPM process):
Test with a phpinfo() page¶
Create a temporary info page in your document root:
If you created a new file under a custom root on RHEL/AlmaLinux, re-apply the SELinux label:
Visit http://example.com/info.php. You should see the PHP version table (not the raw source code — if you see source, PHP is not wired up). Check it with curl:
Delete the phpinfo page immediately after testing
phpinfo() exposes your full PHP configuration, loaded modules, paths, and environment — a goldmine for attackers. Remove it as soon as you have confirmed PHP works:
Verify your work¶
# 1. PHP-FPM is enabled and running
systemctl is-enabled php-fpm && systemctl is-active php-fpm
# 2. The FPM socket exists
ls -l /run/php-fpm/www.sock # Debian: /run/php/php8.2-fpm.sock
# 3. Required extensions are loaded
php -m | grep -E 'mysqlnd|gd|mbstring'
# 4. A .php page renders (returns HTML, not raw PHP source)
curl -s -H 'Host: example.com' http://localhost/info.php | head -1
# 5. Your timezone is set (no warnings)
php -i | grep 'date.timezone'
Summary¶
- LAMP = Apache + PHP; LEMP = nginx + PHP. Both run PHP as PHP-FPM today.
- Install with
dnf install php php-fpm php-mysqlnd php-gd php-mbstring ...(usednf module list phpto pick a version), thensystemctl enable --now php-fpm. - nginx: a
location ~ \.php$block withinclude fastcgi_params;,fastcgi_passto the FPM socket, andSCRIPT_FILENAME. - Apache: prefer
mod_proxy_fcgito PHP-FPM (works with the event MPM) over legacymod_php. - Tune
php.ini(memory_limit,upload_max_filesize/post_max_size,date.timezone) and restart php-fpm to apply. - Test with a
phpinfo()page, then delete it. Watch SELinux on custom roots and for outbound connections.
Next: host multiple PHP sites in Virtual Hosts or secure them with HTTPS with Let's Encrypt.