Skip to main content

PHP Security: Hardening Your Server Through php.ini Settings

PHP · 19.04.2026 · 4 min read
Illustration for “PHP Security: Hardening Your Server Through php.ini Settings”

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 responses

Disable 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_setpgid
Some applications (WP-CLI or Imagick, for instance) may need some of these functions. After applying the changes, check that everything still works correctly.

Restricting Filesystem Access

open_basedir = /var/www/mysite:/tmp:/usr/share/php
; Prevents PHP from reading files outside the listed directories

Disable Remote URLs

allow_url_fopen = Off
allow_url_include = Off
; Blocks file_get_contents("http://...") and includes from remote URLs

Secure 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 = 1440

File Upload Settings

file_uploads = On
upload_tmp_dir = /tmp
upload_max_filesize = 64M
; Restrict upload_tmp_dir to www-data permissions

Disable register_globals and magic_quotes (PHP < 5.4)

; Obsolete, but check them in legacy configs:
register_globals = Off
magic_quotes_gpc = Off

PHP Security Checklist

  • expose_php = Off — hide the PHP version
  • display_errors = Off — never show errors to visitors
  • log_errors = On — write errors to a log file
  • allow_url_include = Off — block remote includes
  • disable_functions — turn off dangerous functions
  • session.cookie_httponly = On — protect sessions
  • open_basedir — restrict the filesystem
# Apply the changes
sudo systemctl reload php8.2-fpm

# Verify
php -r "phpinfo();" 2>&1 | grep expose_php
Use the PHP Security Checker: install composer 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 22

This 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 8

After 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 5

This 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_functions with proc_open and popen breaks 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_basedir breaks symlinks to shared libraries outside the allowed paths — a common issue with a shared vendor directory across several projects.
  • session.cookie_secure = On without 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.

Was this article helpful?
← Back to Knowledge Base Ask Support