Context and Why Apache + PHP-FPM is Needed on CentOS Stream 9
When building a web server, we often face a difficult problem: how to balance high performance and stability? Apache HTTP Server, with its long development history and proven reliability, remains a top choice for many. However, for modern PHP applications, directly using Apache’s mod_php module is sometimes not the most optimal approach in terms of both performance and security.
This is where PHP-FPM (FastCGI Process Manager) comes into play. PHP-FPM operates as an independent process, managing separate PHP “workers”.
When Apache receives a request to process a PHP file, it forwards that request to PHP-FPM. This mechanism not only helps completely separate the web server and PHP processes, enhancing security (each website can run under its own PHP-FPM user), but also significantly improves performance, especially with “heavy” PHP applications or when traffic is high, for example, an e-commerce site with thousands of requests per minute.
Regarding CentOS Stream 9, I personally consider this an interesting move from Red Hat. I once “struggled” when migrating several CentOS 7 servers to AlmaLinux 9, so I understand the differences between versions very well.
Choosing CentOS Stream 9 for new systems, in my opinion, helps you access new features earlier while maintaining stability and compatibility with the RHEL ecosystem. This is an ideal choice for development and testing environments before official deployment. It helps us proactively familiarize ourselves with upcoming changes, avoiding a passive situation like when CentOS 8 suddenly reached its end of life.
Install Necessary Components
Before starting, update the system to ensure all software packages are up-to-date, avoiding conflict errors:
sudo dnf update -y
Install Apache HTTP Server
On CentOS Stream 9, Apache HTTP Server is called httpd. Installation is very simple:
sudo dnf install httpd -y
sudo systemctl enable --now httpd
sudo systemctl status httpd
After installation, check the service status. If you see active (running), it means everything is ready. Don’t forget to open ports 80 (HTTP) and 443 (HTTPS) on the firewall so the web server can operate:
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
Install PHP-FPM
CentOS Stream 9 provides PHP in its default package repositories, but it’s often not the latest version or lacks necessary extensions. To get the most up-to-date PHP-FPM and extensions, I usually use the EPEL and Remi repositories.
sudo dnf install epel-release -y
sudo dnf install https://rpms.remirepo.net/enterprise/remi-release-9.rpm -y
After adding the Remi repository, you can view the list of available PHP versions. Currently, PHP 8.2 and 8.3 are popular versions:
sudo dnf module list php
To install PHP 8.2 (for example), you need to enable the corresponding module and install PHP-FPM along with important extensions. I usually install the following modules: php-cli, php-mysqlnd, php-gd, php-xml, php-mbstring, php-fpm, php-opcache, php-json.
sudo dnf module enable php:remi-8.2 -y
sudo dnf install php-fpm php-cli php-mysqlnd php-gd php-xml php-mbstring php-opcache php-json -y
Finally, enable and check the status of PHP-FPM:
sudo systemctl enable --now php-fpm
sudo systemctl status php-fpm
Detailed Apache Configuration for Working with PHP-FPM
This is a crucial step where we will “connect” Apache and PHP-FPM.
Configure Apache Virtual Host
We need to create a Virtual Host to serve your website. I recommend creating a separate configuration file for each Virtual Host in the /etc/httpd/conf.d/ directory. For example, for a website with the domain example.com:
sudo vi /etc/httpd/conf.d/example.com.conf
The content of the example.com.conf file will be similar to this:
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com/public_html
<Directory /var/www/example.com/public_html>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
# Enable ProxyPassMatch to forward PHP requests to PHP-FPM
# PHP-FPM usually listens on port 9000 or via Unix socket.
# CentOS Stream 9 defaults to using Unix socket: /run/php-fpm/www.sock
<FilesMatch \.php$>
SetHandler "proxy:unix:/run/php-fpm/www.sock|fcgi://localhost/"
</FilesMatch>
ErrorLog /var/log/httpd/example.com_error.log
CustomLog /var/log/httpd/example.com_access.log combined
</VirtualHost>
To help you visualize, let’s explain the line SetHandler "proxy:unix:/run/php-fpm/www.sock|fcgi://localhost/":
proxy:unix:/run/php-fpm/www.sock: Apache will use themod_proxy_fcgimodule to connect to PHP-FPM via a Unix socket at the path/run/php-fpm/www.sock. This method is both more efficient and more secure than using traditional TCP ports.fcgi://localhost/: This is a crucial component that helps PHP-FPM correctly identify the document root of the request. When PHP-FPM receives a request, it will use this part to accurately set theSCRIPT_FILENAMEenvironment variable.
Next, create the DocumentRoot directory and set correct permissions:
sudo mkdir -p /var/www/example.com/public_html
sudo chown -R apache:apache /var/www/example.com
sudo chmod -R 755 /var/www/example.com
Check Apache configuration syntax and restart the service to apply changes:
sudo apachectl configtest
sudo systemctl restart httpd
Configure PHP-FPM Pool
PHP-FPM has a default pool named www. For simple setups, you can certainly use this pool. Its configuration file is located at /etc/php-fpm.d/www.conf.
Check the value of the listen line in the www.conf file:
grep "listen =" /etc/php-fpm.d/www.conf
If the result is listen = /run/php-fpm/www.sock, then our Apache configuration above is correct. If you see a TCP port (e.g., listen = 127.0.0.1:9000), you will need to adjust the SetHandler line in the Apache configuration to "proxy:fcgi://127.0.0.1:9000" accordingly.
Additionally, you should fine-tune the following parameters to optimize performance and prevent errors during operation:
userandgroup: Set both toapache. This helps PHP-FPM run under the same user as Apache, thoroughly resolving file access permission issues, especially when interacting with files created by Apache.- Process management parameters (
pm.*): pm = dynamic: This is the default mode and is suitable for most use cases.pm.max_children: This is the maximum number of PHP-FPM child processes allowed to run. You need to consider this carefully based on the server’s RAM capacity. A small rule of thumb is: divide the total RAM dedicated to PHP by the average RAM consumed by one PHP process (for example, if you dedicate 2GB RAM to PHP and each PHP process uses about 40MB, you can setpm.max_childrento around 50).pm.start_servers,pm.min_spare_servers,pm.max_spare_servers: These parameters help FPM automatically adjust the number of processes to “scale” according to the load, ensuring the server is not overloaded but still responds quickly.
Example adjustments in the /etc/php-fpm.d/www.conf file (only modify necessary lines):
listen = /run/php-fpm/www.sock
listen.owner = apache
listen.group = apache
listen.mode = 0660
user = apache
group = apache
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 35
After completing the changes, restart PHP-FPM for the new configurations to take effect:
sudo systemctl restart php-fpm
SELinux Configuration
CentOS Stream 9 enables SELinux by default. This is a powerful security layer, but it can sometimes hinder newcomers. For Apache and PHP-FPM to communicate via a Unix socket, we must ensure SELinux allows this connection.
If you encounter a “Permission denied” error in Apache’s logs when trying to connect to PHP-FPM, check the SELinux logs. Typically, you will need to create a custom policy to allow this interaction:
sudo ausearch -c 'httpd' --raw | sudo audit2allow -M my-httpd
sudo semodule -i my-httpd.pp
In case you need a quick troubleshooting fix (for testing environments only!), you can temporarily switch SELinux to Permissive mode:
sudo setenforce 0
To return to Enforcing mode after troubleshooting: sudo setenforce 1. Note that disabling SELinux is not recommended for production environments.
Testing and Monitoring
After installation and configuration are complete, verifying that everything is working smoothly is extremely important.
Test PHP Operation
Create a simple PHP file in the DocumentRoot directory of your Virtual Host:
sudo vi /var/www/example.com/public_html/info.php
Content of the info.php file:
<?php
phpinfo();
?>
Open your browser and access http://example.com/info.php. If you see the PHP information page appear, especially with “Server API” showing “FPM/FastCGI”, congratulations! Apache and PHP-FPM are successfully working together.
Check Logs
When encountering issues, log files are your best friends. Don’t forget to check the following log files:
- Apache Logs:
/var/log/httpd/error_logand/var/log/httpd/example.com_error.log(for a specific Virtual Host). - PHP-FPM Logs:
/var/log/php-fpm/www-error.log(or the log file corresponding to the pool you are using).
Use the tail -f command to view logs in real-time when you access the website; this helps you detect errors immediately:
sudo tail -f /var/log/httpd/example.com_error.log
sudo tail -f /var/log/php-fpm/www-error.log
Basic Monitoring
To maintain stable performance, you need to closely monitor system resources such as CPU, RAM, and I/O, especially when the website is under high load. Tools like htop, glances, or sar will provide useful overviews. One point to note is the number of running php-fpm processes, which helps you adjust the pm.* parameters to suit the server’s resources and actual traffic.
htop
If you see PHP-FPM processes being continuously killed and restarted, or the website shows 50x errors, this is a clear indication that you need to adjust the pm.max_children value or reconsider the memory usage of each PHP process.
Setting up Apache with PHP-FPM on CentOS Stream 9 might seem complex at first, but with meticulous attention to each configuration step, you will achieve superior stability and performance for your PHP web applications. Good luck with your project!

