Why Harden PHP
PHP ships with plenty of functions that become dangerous under a careless configuration. A proper php.ini setup closes entire classes of vulnerabilities: remote code execution (RCE), server information disclosure, file enumeration through open_basedir bypasses. This is the first step in securing any PHP server.
Hide PHP Information
expose_php = Off
; Removes the X-Powered-By: PHP/8.x header from HTTP responsesDisable Dangerous Functions
disable_functions = exec,passthru,shell_exec,system,proc_open,popen,\
curl_exec,curl_multi_exec,parse_ini_file,show_source,\
phpinfo,posix_kill,posix_mkfifo,posix_setpgidRestricting Filesystem Access
open_basedir = /var/www/mysite:/tmp:/usr/share/php
; Prevents PHP from reading files outside the listed directoriesDisable Remote URLs
allow_url_fopen = Off
allow_url_include = Off
; Blocks file_get_contents("http://...") and includes from remote URLsSecure Session Settings
session.cookie_httponly = On
session.cookie_secure = On
session.cookie_samesite = Strict
session.use_strict_mode = On
session.use_only_cookies = On
session.gc_maxlifetime = 1440File Upload Settings
file_uploads = On
upload_tmp_dir = /tmp
upload_max_filesize = 64M
; Restrict upload_tmp_dir to www-data permissionsDisable register_globals and magic_quotes (PHP < 5.4)
; Obsolete, but check them in legacy configs:
register_globals = Off
magic_quotes_gpc = OffPHP Security Checklist
expose_php = Off— hide the PHP versiondisplay_errors = Off— never show errors to visitorslog_errors = On— write errors to a log fileallow_url_include = Off— block remote includesdisable_functions— turn off dangerous functionssession.cookie_httponly = On— protect sessionsopen_basedir— restrict the filesystem
# Apply the changes
sudo systemctl reload php8.2-fpm
# Verify
php -r "phpinfo();" 2>&1 | grep expose_phpcomposer require enlightn/security-checker to automatically scan dependencies for known vulnerabilities.Real Errors After Hardening
A function disabled via disable_functions
Warning: shell_exec() has been disabled for security reasons in /var/www/html/deploy.php on line 22This is a warning, not a fatal error: the call silently returns NULL and script execution continues. Code that does not check the result of shell_exec() for null then fails later with a different, less obvious error — doubling the debugging effort.
allow_url_fopen blocked a request
Warning: file_get_contents(http://api.example.com/data): Failed to open stream: no suitable wrapper could be found in /var/www/html/app/Api.php on line 8After allow_url_fopen = Off, functions like file_get_contents() and fopen() stop opening network addresses — that is expected and correct, but code that reads external APIs through them needs to be rewritten to use cURL ahead of time, before hardening ships to production.
An open_basedir violation
Warning: file_get_contents(): open_basedir restriction in effect. File(/etc/passwd) is not within the allowed path(s): (/var/www/mysite:/tmp:/usr/share/php) in /var/www/mysite/app/Reader.php on line 5This is exactly the behavior the directive was enabled for — an attempt to escape the allowed paths is blocked before the system call even happens. If legitimate code hits this error on paths that should be reachable, extend the colon-separated list rather than disabling the directive entirely.
When Hardening Breaks Legitimate Tools
disable_functionswithproc_openandpopenbreaks WP-CLI, deploy scripts that run through Composer scripts, and any tool that spawns external processes — check the list of utilities in use before disabling anything.- A global
open_basedirbreaks symlinks to shared libraries outside the allowed paths — a common issue with a shared vendor directory across several projects. session.cookie_secure = Onwithout HTTPS will leave users with no working sessions at all — the cookie simply will not be sent by the browser.
Verifying the Result
# The PHP version is no longer leaked in response headers
curl -I https://example.com | grep -i x-powered-by
# (empty output means expose_php worked)
# List the functions that are actually disabled
php -i | grep disable_functions
# Check one specific dangerous function from code
php -r "var_dump(function_exists('exec'));"The absence of the X-Powered-By header in curl's response is a reliable sign that expose_php = Off took effect. To verify secure file uploads, additionally test a real file submission through a form, not just the php.ini settings.
Repeat this check after every PHP update: a new minor version sometimes resets a directive to its default if it was only set in a separate module ini file that the package update overwrote.