This guide details how to decouple the PHP runtime from your host operating system. By running an up-to-date PHP version inside a Docker container while retaining your native Apache web server for SSL and routing, you can address critical PHP vulnerabilities (like PHP2Shell) without upgrading or replacing your host OS.
This approach is particularly useful when running an outdated host that cannot easily be upgraded or replaced. For example, Debian 11 only provides PHP 7.4, which has not received a patch for this issue. Obviously, the ideal solution would be to migrate away from an EOL OS entirely. However, this may not be immediately feasible, and continuing to run a vulnerable version of PHP would not be a wise decision.
Step 1: Prepare the Host System
First, you must enable the necessary communication modules within your native host Apache installation so it can forward PHP requests to the FastCGI backend.
# Enable the required Apache proxy modules
sudo a2enmod proxy
sudo a2enmod proxy_fcgi
# (Optional) Disable your native old PHP module to free system memory
# Replace "7.4" or "8.0" with your specific native system version
sudo a2dismod php7.4
# Restart Apache to load the changes
sudo systemctl restart apache2
Step 2: Create the Docker Compose Deployment
Make sure you have Docker installed.
We will use Docker Compose to define the runtime environment. This keeps your setup reproducible and easy to patch.
Create a docker-compose.yaml file in your administrative directory (e.g., /home/docker/php-fpm/):
services:
php-fpm:
image: hermsi/alpine-fpm-php:8.5
container_name: php-fpm
restart: unless-stopped
user: "1000:33"
network_mode: "host"
volumes:
- ./zz-listen.conf:/usr/local/etc/php-fpm.d/zz-listen.conf:ro
- ./zz-security.ini:/usr/local/etc/php/conf.d/zz-security.ini:ro
- /var/www:/var/www
Note: Run id -u [username] and getent group www-data on your host system to verify your exact numeric IDs for the user: field.
Modify your volumes: directive to point to your Apache DocumentRoot. Add any additional volumes you may need for additional virtual hosts.
Create zz-listen.conf in the same directory as your compose file with the following content:
[www]
listen = 127.0.0.1:9000
listen.allowed_clients = 127.0.0.1
This will bind the FastCGI service to the loopback interface. Without this explicit listener restriction, FastCGI may be exposed on the server's public interfaces. The configuration above will allow local connections from Apache while preventing direct external access to the FastCGI listener.
Also create zz-security.ini:
; do not advertise php version
expose_php = Off
; do not output errors in response
display_errors = Off
display_startup_errors = Off
; send errors to docker log
log_errors = On
error_log = /proc/self/fd/2
; mitigate CVE-2026-115560
session.upload_progress.enabled = 0
These settings represent sensible production defaults for a public-facing PHP application. Use this pattern to modify any other settings you want to change (like upload_max_filesize for example).
Step 3: Start The FastCGI Service
Launch the container in the background:
sudo docker-compose up -d
Note: I have used the deprecated docker-compose command. If you are following this guide, chances are you might have an outdated Docker installation too so this will be necessary. If you are using Docker v23 or newer the command should be docker compose with a space.
Step 4: Configure Host Apache to Forward PHP Requests to PHP-FPM
Update your native Apache VirtualHost configuration file (/etc/apache2/sites-available/your-site.conf) to bridge your front-end domains directly to the containerized socket handler.
By mapping your DocumentRoot volumes in the compose file, Apache and your container share the exact same root directory layouts and the execution paths line up seamlessly:
<VirtualHost *:443>
ServerName yourdomain.com
DocumentRoot /var/www
# ... Your existing SSL configuration rules ...
# The Core Mechanism: Intercept all PHP requests and forward them
# directly to the container listening locally on port 9000
<FilesMatch "\.php$">
SetHandler "proxy:fcgi://127.0.0.1:9000"
</FilesMatch>
<Directory /var/www>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
Reload Apache to enforce the new routing configuration:
sudo systemctl reload apache2
Key Architectural Gotchas & Solutions
The
localhostSocket Trap (Error 2002):
When a PHP application configuration (like Nextcloud or WordPress) uses the stringlocalhostfor its database target, the PHP engine forces a connection via a physical filesystem socket file (mysqld.sock). Since that file doesn't exist inside the container, connections will fail. Solution: Change your application database configuration files fromlocalhostto the explicit network loopback IP127.0.0.1.Database Permissions Matrix:
Ensure your native database server (MariaDB/MySQL) is configured to accept network connections from the loopback interface, and that your database user account has been explicitly granted privileges to authenticate from127.0.0.1via standard TCP networking.The Log Silencing Effect:
By default, containerized PHP applications log their primary system warnings directly to standard output streams (stdout/stderr). If you are searching for execution errors, rundocker compose logs php-fpmordocker logs php-fpmto view your application backtraces in real-time.
Future Upgrades
When a future security patch drops or a new major/minor PHP release launches, updating your entire environment is simple. The php version tag used in docker-compose.yaml is set to :8.5. Pulling the image from dockerhub will automatically fetch the latest patch release (currently 8.5.12). If you want to use a newer version just change this tag to :8.6 or eventually :9.0 when they become available. Be sure to check for any compatibility issues with your hosted applications first.
sudo docker-compose pull && \
sudo docker-compose up -d
Комментарии (0)
Пока нет комментариев — будьте первым.