Linux PHP Hardening and Security Best Practices
PHP powers approximately 78% of all websites with known server-side programming languages, making it a prime target for attackers. Securing your PHP environment is crucial for protecting your web applications from vulnerabilities and exploits. This comprehensive guide covers essential PHP hardening techniques specifically for Linux environments, providing practical steps to enhance your PHP security posture.
Introduction
PHP applications face numerous security challenges, from injection attacks and cross-site scripting to remote code execution. By properly hardening your PHP configuration, you can mitigate these risks and create a more secure environment for your web applications. This guide focuses on server-level PHP hardening rather than application code security, though both are essential for a complete security strategy.
1. Keep PHP Updated
Running the latest stable version of PHP is your first line of defense against security vulnerabilities.
Checking Your PHP Version
php -v
Updating PHP
On Debian/Ubuntu:
sudo apt update
sudo apt upgrade php
# For specific versions (e.g., PHP 8.1)
sudo apt update
sudo apt upgrade php8.1 php8.1-common php8.1-cli
On CentOS/RHEL:
# If using standard repositories
sudo yum update php
# If using Remi repository
sudo yum update php-*
Automating Security Updates
Configure automatic security updates for PHP:
# On Debian/Ubuntu
sudo apt install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
Edit the configuration file:
sudo nano /etc/apt/apt.conf.d/50unattended-upgrades
Ensure PHP packages are included in the automatic updates:
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}";
"${distro_id}:${distro_codename}-security";
"${distro_id}ESMApps:${distro_codename}-apps-security";
"${distro_id}ESM:${distro_codename}-infra-security";
};
2. Secure php.ini Configuration
The PHP configuration file (php.ini) contains many settings that directly impact security.
Locating php.ini
# Find the loaded php.ini file
php -i | grep "Loaded Configuration File"
# Or check via phpinfo()
echo "" > /var/www/html/phpinfo.php
# Access phpinfo.php in browser, then delete it immediately after use
rm /var/www/html/phpinfo.php
Essential Security Settings
Edit your php.ini file:
sudo nano /etc/php/8.1/apache2/php.ini # Adjust path as needed
Apply these security-focused settings:
# Disable dangerous functions
disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source,phpinfo
# Disable remote file inclusion
allow_url_fopen = Off
allow_url_include = Off
# Limit file uploads
file_uploads = On
upload_max_filesize = 2M
max_file_uploads = 2
# Control maximum execution time and memory
max_execution_time = 30
max_input_time = 60
memory_limit = 128M
# Disable error display on production
display_errors = Off
display_startup_errors = Off
log_errors = On
error_log = /var/log/php_errors.log
# Enable SQL safe mode
sql.safe_mode = On
# Cookie security
session.cookie_httponly = 1
session.cookie_secure = 1
session.use_only_cookies = 1
# Disable register globals (for older PHP versions)
register_globals = Off
# Protect session data
session.save_path = "/tmp"
session.gc_maxlifetime = 1440
# Disable XML external entity processing
libxml.disable_entity_loader = On
# Limit POST size to prevent DoS attacks
post_max_size = 8M
# Open basedir restriction
open_basedir = /var/www/html/:/tmp/
Restart Web Server
After making changes to php.ini:
# For Apache
sudo systemctl restart apache2 # Debian/Ubuntu
sudo systemctl restart httpd # CentOS/RHEL
# For Nginx with PHP-FPM
sudo systemctl restart php8.1-fpm # Adjust version as needed
sudo systemctl restart nginx
3. Implement PHP-FPM with Restricted Permissions
PHP-FPM (FastCGI Process Manager) offers better security and performance than mod_php.
Installing PHP-FPM
# Debian/Ubuntu
sudo apt install php8.1-fpm
# CentOS/RHEL
sudo yum install php-fpm
Configure PHP-FPM with Restricted User
Create a dedicated user for PHP:
sudo useradd -r -s /sbin/nologin -d /var/www/html phpuser
sudo usermod -a -G www-data phpuser # On Debian/Ubuntu
sudo usermod -a -G apache phpuser # On CentOS/RHEL
Edit the PHP-FPM pool configuration:
# Debian/Ubuntu
sudo nano /etc/php/8.1/fpm/pool.d/www.conf
# CentOS/RHEL
sudo nano /etc/php-fpm.d/www.conf
Update the user and group settings:
user = phpuser
group = www-data # or 'apache' on CentOS/RHEL
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
# Run PHP as different users for different sites (optional)
; Example of a per-site pool
;[example.com]
;user = site1user
;group = www-data
;listen = /var/run/php-fpm-example.sock
;listen.owner = www-data
;listen.group = www-data
;pm = dynamic
;pm.max_children = 5
;pm.start_servers = 2
;pm.min_spare_servers = 1
;pm.max_spare_servers = 3
Configure PHP-FPM Process Limits
# In the same www.conf file
pm = dynamic
pm.max_children = 10
pm.start_servers = 3
pm.min_spare_servers = 2
pm.max_spare_servers = 5
pm.max_requests = 500
Restart PHP-FPM
# Debian/Ubuntu
sudo systemctl restart php8.1-fpm
# CentOS/RHEL
sudo systemctl restart php-fpm
4. Implement Web Server Configurations
Apache with PHP-FPM Configuration
# Debian/Ubuntu
sudo apt install libapache2-mod-fcgid
sudo a2enmod proxy_fcgi setenvif
sudo a2enconf php8.1-fpm # Adjust version as needed
sudo systemctl restart apache2
Secure your Apache virtual host configuration:
sudo nano /etc/apache2/sites-available/000-default.conf
Add these security directives:
<VirtualHost *:80>
# [Other virtual host settings]
# PHP handling with PHP-FPM
<FilesMatch \.php$>
SetHandler "proxy:unix:/var/run/php/php8.1-fpm.sock|fcgi://localhost"
</FilesMatch>
# Deny access to files without filename (e.g., /index.php/)
RedirectMatch 403 /$
# Deny access to hidden files and directories
<DirectoryMatch "^\.|\/\.">
Require all denied
</DirectoryMatch>
# Disable PHP execution in uploads directory
<Directory /var/www/html/uploads>
<FilesMatch "\.php$">
Require all denied
</FilesMatch>
</Directory>
# Set security headers
Header set X-Content-Type-Options "nosniff"
Header set X-XSS-Protection "1; mode=block"
Header set X-Frame-Options "SAMEORIGIN"
Header set Referrer-Policy "strict-origin-when-cross-origin"
Header set Content-Security-Policy "default-src 'self';"
</VirtualHost>
Nginx with PHP-FPM Configuration
Secure your Nginx server configuration:
sudo nano /etc/nginx/sites-available/default
Apply these security settings:
server {
listen 80;
server_name example.com;
root /var/www/html;
index index.php index.html;
# PHP handling with PHP-FPM
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
# Deny access to hidden files
location ~ /\. {
deny all;
}
# Disable PHP execution in uploads directory
location ~ ^/uploads/.*\.php$ {
deny all;
}
# Security headers
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self';" always;
# Deny access to sensitive files
location ~ \.(engine|inc|info|install|make|module|profile|test|po|sh|.*sql|theme|tpl(\.php)?|xtmpl)(~|\.sw[op]|\.bak|\.orig|\.save)?$|^(\.(?!well-known).*|Entries.*|Repository|Root|Tag|Template|composer\.(json|lock)|web\.config)$|^#.*#$|\.php(~|\.sw[op]|\.bak|\.orig\.save)$ {
deny all;
}
}
5. Implement suPHP or PHPSuExec
These modules allow PHP to run with the permissions of the file owner rather than the web server user.
Installing suPHP
# Debian/Ubuntu
sudo apt install libapache2-mod-suphp
# CentOS/RHEL
sudo yum install mod_suphp
Configure suPHP
Edit the suPHP configuration file:
sudo nano /etc/suphp/suphp.conf
Modify these settings:
[global]
;Path to logfile
logfile=/var/log/suphp/suphp.log
;Loglevel
loglevel=info
;User Apache runs as
webserver_user=www-data
;Path all scripts have to be in
docroot=/var/www:${HOME}/public_html
;Handler for php-scripts
application/x-httpd-php=php
;Handler for CGI-scripts
x-suphp-cgi=execute
[handlers]
;Handler for php-scripts
x-httpd-php=php
;Handler for CGI-scripts
x-suphp-cgi=execute
[php]
;Path to php executable
phprc_paths=/etc/php/8.1/suphp
php_binary=/usr/bin/php-cgi
[client]
;Minimum UID
min_uid=100
;Minimum GID
min_gid=100
Update Apache Configuration
sudo nano /etc/apache2/mods-available/suphp.conf
Add:
<IfModule mod_suphp.c>
AddType application/x-httpd-php .php
suPHP_AddHandler application/x-httpd-php
suPHP_Engine on
suPHP_ConfigPath /etc/php/8.1/cgi
# Document root must be owned by site owner, not web server user
# chmod 755 for directories
# chmod 644 for files
</IfModule>
6. Implement PHP OPcache Properly
OPcache improves PHP performance but needs to be secured properly.
Configure OPcache Securely
Edit your php.ini file or create a separate OPcache configuration file:
sudo nano /etc/php/8.1/mods-available/opcache.ini
Add secure settings:
[opcache]
opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.revalidate_freq=60
opcache.fast_shutdown=1
opcache.enable_cli=0
opcache.save_comments=1
; Security settings
opcache.validate_timestamps=1
opcache.restrict_api=/var/www/allowed_opcache_path
opcache.validate_permission=1 ; PHP 7.0.14+ or 7.1+
opcache.validate_root=1 ; PHP 7.0.14+ or 7.1+
7. Implement File and Directory Permissions
Proper file permissions are critical for PHP security.
Set Secure File Permissions
# For web content directories
sudo find /var/www/html -type d -exec chmod 755 {} \;
sudo find /var/www/html -type f -exec chmod 644 {} \;
# For writable directories (adjust paths as needed)
sudo find /var/www/html/uploads -type d -exec chmod 775 {} \;
sudo find /var/www/html/cache -type d -exec chmod 775 {} \;
sudo chown -R phpuser:www-data /var/www/html/uploads
sudo chown -R phpuser:www-data /var/www/html/cache
Secure php.ini Files
sudo chmod 0644 /etc/php/8.1/apache2/php.ini
sudo chmod 0644 /etc/php/8.1/cli/php.ini
sudo chmod 0644 /etc/php/8.1/fpm/php.ini
8. Implement Error Handling and Logging
Proper error handling prevents information disclosure while maintaining visibility for administrators.
Configure Error Logging
Edit php.ini:
# For production servers
display_errors = Off
display_startup_errors = Off
log_errors = On
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
error_log = /var/log/php_errors.log
# For development servers (never use in production)
; display_errors = On
; display_startup_errors = On
; log_errors = On
; error_reporting = E_ALL
; error_log = /var/log/php_errors.log
Set Up Log Rotation
sudo nano /etc/logrotate.d/php
Add:
/var/log/php_errors.log {
weekly
rotate 12
compress
delaycompress
missingok
notifempty
create 0640 www-data adm
}
/var/log/suphp/suphp.log {
weekly
rotate 12
compress
delaycompress
missingok
notifempty
create 0640 www-data adm
}
9. Install PHP Security Extensions
Suhosin Extension
Suhosin provides additional protection layers for PHP:
# Debian/Ubuntu
sudo apt install php-suhosin
# Or manually compile if not available in repos
cd /usr/src
wget https://github.com/sektioneins/suhosin/archive/master.zip
unzip master.zip
cd suhosin-master
phpize
./configure
make
sudo make install
Edit php.ini or create a separate configuration file:
sudo nano /etc/php/8.1/mods-available/suhosin.ini
Add:
[suhosin]
extension=suhosin.so
; Request Variables
suhosin.request.max_vars = 200
suhosin.request.max_value_length = 1000000
suhosin.request.max_array_depth = 100
; Cookie Security
suhosin.cookie.encrypt = On
suhosin.cookie.cryptkey = 'random_string_here'
; Session Security
suhosin.session.encrypt = On
suhosin.session.cryptkey = 'another_random_string_here'
; Execution Rules
suhosin.executor.include.max_traversal = 4
suhosin.executor.disable_eval = On
suhosin.executor.disable_emodifier = On
; Log settings
suhosin.log.syslog = 0
suhosin.log.sapi = 0
suhosin.log.stdout = 0
suhosin.log.phpscript = 0
suhosin.log.file = 511
suhosin.log.file.name = /var/log/suhosin.log
ModSecurity with PHP Rules
ModSecurity can protect PHP applications from various attacks:
# Install ModSecurity
# Debian/Ubuntu
sudo apt install libapache2-mod-security2
# CentOS/RHEL
sudo yum install mod_security
Configure ModSecurity for PHP protection:
sudo cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf
sudo nano /etc/modsecurity/modsecurity.conf
Set to detection mode first, then production mode once tested:
SecRuleEngine On
Install OWASP Core Rule Set (CRS):
# Debian/Ubuntu
sudo apt install modsecurity-crs
sudo ln -s /etc/modsecurity/modsecurity.conf /etc/apache2/mods-enabled/security2.conf
# CentOS/RHEL
sudo yum install mod_security_crs
Create PHP-specific security rules:
sudo nano /etc/modsecurity/rules/php-security.conf
Add PHP protection rules:
# Block PHP code injection attempts
SecRule REQUEST_COOKIES|REQUEST_COOKIES_NAMES|REQUEST_FILENAME|REQUEST_HEADERS|REQUEST_HEADERS_NAMES|REQUEST_BODY|REQUEST_LINE|ARGS|ARGS_NAMES "(?i)(<\?(?:php)?|<\% )" \
"id:1000,phase:2,t:none,t:urlDecodeUni,block,msg:'PHP Code Injection Attempt',logdata:'%{MATCHED_VAR}',severity:2"
# Block common PHP exploits like file traversal
SecRule REQUEST_URI|REQUEST_HEADERS|ARGS|ARGS_NAMES|REQUEST_BODY "(?i)(\.\.\/|\.\.\\\\|%2e%2e%2f|%252e%252e%252f)" \
"id:1001,phase:2,t:none,t:urlDecodeUni,block,msg:'Directory Traversal Attempt',logdata:'%{MATCHED_VAR}',severity:2"
# Block PHP file uploads
SecRule FILES ".*\.(?:php|phtml|php3|php4|php5|php7|phps)$" \
"id:1002,phase:2,t:none,t:lowercase,block,msg:'PHP File Upload Attempt',logdata:'%{MATCHED_VAR}',severity:2"
10. Implement Real-time Monitoring
Set Up PHP File Change Monitoring
# Install AIDE (Advanced Intrusion Detection Environment)
sudo apt install aide
# Configure AIDE to watch PHP files
sudo nano /etc/aide/aide.conf
Add or modify rules for PHP files:
# Monitor all PHP files with strong rules
PHPRule = p+i+n+u+g+s+b+m+c+sha1+sha256+rmd160
/var/www/html/.*\.php$ PHPRule
/var/www/html/.*\.phtml$ PHPRule
# Initialize AIDE database
sudo aideinit
# Move the new database to the active location
sudo cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db
Schedule regular checks:
sudo nano /etc/cron.daily/aide-check
Add this script:
#!/bin/bash
aide --check | mail -s "AIDE PHP Files Check Report $(date)" root@localhost
Make it executable:
sudo chmod +x /etc/cron.daily/aide-check
Implement Log Monitoring
Set up log analysis for PHP issues:
# Install Logwatch
sudo apt install logwatch
# Configure custom PHP log checking
sudo nano /etc/logwatch/conf/logfiles/php-errors.conf
Add:
LogFile = /var/log/php_errors.log
Archive = /var/log/php_errors.log.*.gz
LogFile = /var/log/suphp/suphp.log
Archive = /var/log/suphp/suphp.log.*.gz
Create a service file:
sudo nano /etc/logwatch/conf/services/php-errors.conf
Add:
Title = "PHP Errors"
LogFile = php-errors
Troubleshooting Section
Common Issues and Solutions
1. PHP Scripts Not Executing After Security Changes
Problem: PHP files return 403 Forbidden or download instead of executing.
Solution: Check file permissions and handler configurations:
# Check file ownership
ls -la /var/www/html/*.php
# Check handler configuration
sudo apache2ctl -M | grep php
# or
sudo ls -la /etc/apache2/mods-enabled/ | grep php
# For PHP-FPM, verify the socket
sudo systemctl status php8.1-fpm
2. Too Restrictive open_basedir
Problem: Applications fail due to restrictive open_basedir.
Solution: Adjust the open_basedir setting:
sudo nano /etc/php/8.1/apache2/php.ini
Modify the path to include necessary directories:
open_basedir = /var/www/html/:/tmp/:/usr/share/php/:/var/lib/php/sessions/
3. Legitimate Functions Blocked by disable_functions
Problem: Applications break because needed functions are disabled.
Solution: Refine the disable_functions list:
# Check which functions are causing issues via error logs
sudo tail -f /var/log/php_errors.log
# Modify the disable_functions directive
sudo nano /etc/php/8.1/apache2/php.ini
Remove only the specific functions needed by your application.
4. ModSecurity Blocking Legitimate Requests
Problem: False positives from ModSecurity rules.
Solution: Review ModSecurity logs and create exceptions:
# Check the ModSecurity logs
sudo tail -f /var/log/apache2/modsec_audit.log
# Create exceptions for specific rules
sudo nano /etc/modsecurity/rules/exceptions.conf
Add rule exceptions:
SecRuleRemoveById 1000 1001 1002 # Replace with actual IDs causing issues
# Or more targeted exclusions for specific URLs
SecRule REQUEST_URI "@beginsWith /legitimate-path" "id:10000,phase:1,pass,nolog,ctl:ruleRemoveById=1000"
Best Practices & Optimization Tips
Security Enhancement
- Regular Security Audits: Perform security scans with tools like Nikto, OWASP ZAP
- Principle of Least Privilege: Run PHP with minimal permissions needed
- Defense in Depth: Implement multiple security layers (server, PHP, application)
- Keep Informed: Subscribe to PHP security mailing lists for vulnerability alerts
- Use WAF: Implement a Web Application Firewall in front of PHP applications
Performance Optimization
- Optimize OPcache: Fine-tune OPcache settings for your specific application
- Implement PHP-FPM Pools: Create separate PHP-FPM pools for different applications
- Use APCu: Implement APCu for application caching alongside security measures
- HTTP/2: Enable HTTP/2 on your web server for performance benefits
- Content Compression: Enable Gzip/Brotli compression for PHP output
Conclusion
Hardening PHP on Linux systems is a multi-layered approach that encompasses configuration changes, permission management, and monitoring. By implementing the security measures outlined in this guide, you can significantly reduce the risk of compromises through PHP vulnerabilities.
Remember that security is an ongoing process, not a one-time task. Regularly update your PHP installation, review security configurations, monitor logs for suspicious activities, and stay informed about emerging threats and best practices.
Key takeaways include:
- Keep PHP and related components updated
- Implement secure php.ini settings
- Use PHP-FPM with restricted user permissions
- Configure proper file system permissions
- Implement real-time monitoring and intrusion detection
- Consider security extensions like Suhosin and ModSecurity
- Follow the principle of least privilege throughout your environment
By following these best practices, you can create a secure PHP environment that protects your web applications and your users' data from potential threats.