← News
/PLAYBOOK · 2026-08-07

PHP Security Hardening: Web App Runtime Protection Config Guide

php.ini high-risk function disable, open_basedir jail, upload and session hardening, error hiding. Copy-paste config and verification commands.

PHP security hardening is baseline web-server defense. This guide covers php.ini knobs, dangerous-function disable, directory jail, upload and session security. Copy-paste the config to cut takeover risk.

Scope

Prerequisites

Rationale

Least privilege: disable abuse-prone functions in php.ini, jail script filesystem access, turn off unused dangerous features. Even with a WebShell or file-include bug, command exec, sensitive-file IO, and lateral movement get harder. In typical intrusion chains, `eval`, `system` and similar are the WebShell execution hinge.

Procedure

1. Locate php.ini and back it up

# Show loaded PHP config path
php --ini

# Show PHP-FPM loaded config
php -i | grep -i "Loaded Configuration"

# Backup
cp /etc/php/8.1/fpm/php.ini /etc/php/8.1/fpm/php.ini.bak

2. Disable dangerous functions

Edit php.ini, set `disable_functions`. High-risk functions typical web apps do not need, commonly abused:

disable_functions = passthru,exec,system,chroot,chgrp,chown,shell_exec,proc_open,popen,ini_alter,ini_restore,dl,openlog,syslog,readlink,symlink,popepassthru,stream_socket_server,pcntl_exec,putenv,mail,error_log,apache_setenv

Note: exec, system, shell_exec, proc_open are the usual command-exec WebShell calls—disable them. If the app truly needs them (cron, ffmpeg transcode), allow only in a separate PHP-CLI environment or via whitelist.

3. Jail directory access

; PHP may only access the site root and system temp
open_basedir = /www/wwwroot/example.com:/tmp

Once `open_basedir` is set, any file access outside the jail errors out. Blocks WebShell reads of `/etc/passwd`, database configs, and other sensitive files.

4. Disable dangerous features and error display

; Hide errors; do not leak paths or code layout
display_errors = Off
display_startup_errors = Off
log_errors = On
error_log = /var/log/php-fpm/php_errors.log

; Disable remote file include (blocks RFI)
allow_url_include = Off

; Uploads (adjust as needed)
file_uploads = On
upload_max_filesize = 20M
post_max_size = 25M

Production must set display_errors = Off. Pair with `log_errors = On` so errors go to the log: no leak, still debuggable.

5. Session and Cookie security

; Session cookie over HTTPS only
session.cookie_secure = 1
; Block JS access to the session cookie; mitigates XSS session theft
session.cookie_httponly = 1
; SameSite; mitigates CSRF
session.cookie_samesite = Lax
; Restrict session ID source; mitigates session fixation
session.use_strict_mode = 1

6. Deny PHP execution in upload dirs

php.ini alone is not enough. Block script execution in upload dirs at the web server. Nginx example:

location ~* ^/(upload|uploads|images|attach)/.*\.(php|php5|phtml)$ {
    deny all;
}

Even if upload checks are bypassed, PHP dropped in image dirs will not execute.

7. Restart PHP-FPM

# Syntax check
php-fpm8.1 -t

# Restart PHP-FPM
sudo systemctl restart php8.1-fpm

# Verify
php -i | grep -E "disable_functions|open_basedir|display_errors"

Verify

Drop a throwaway test script:

# Create test script
cat > /www/wwwroot/example.com/security_test.php <<'EOF'
<?php
echo "disable_functions test:\n";
var_dump(function_exists('system'));
echo "directory jail test:\n";
var_dump(@file_get_contents('/etc/passwd'));
echo "error display test:\n";
echo $undefined_var;
EOF

Hitting the script should show: `function_exists('system')` returns `false`; `/etc/passwd` read returns `false` or a warning; undefined variable prints no error detail. Delete the test script immediately after.

FAQ

FAQ 1: Site features break after disabling exec?

Find the caller: `grep -r "exec(" /www/wwwroot/example.com`. If core features depend on it (image processing, queues): (1) move those calls to a PHP-CLI cron script; (2) allow only needed functions such as `exec` plus `open_basedir`; (3) switch to pure PHP extensions (Imagick instead of ffmpeg CLI).

FAQ 2: White screen / 500 after open_basedir?

A required path is missing from the jail: Composer cache, temp, session dir, etc. Check the PHP error log for the blocked path. Append it to `open_basedir` (colon-separated), e.g. `open_basedir = /www/wwwroot/example.com:/tmp:/www/wwwroot/example.com/runtime`. Add paths incrementally and watch logs. Do not drop the restriction wholesale.

Summary

No extra appliances needed. Nail four php.ini basics—disable high-risk functions, jail dirs, hide errors, harden sessions—and you block most WebShell and injection paths. Add web-server deny-execute on upload dirs for app + middleware defense in depth. After hardening, recheck with WPScan, Nmap, and WebShell scanners so you did not break the app or leave gaps.