生产环境禁用php -s,改用frankenphp worker或caddy+php-fpm;强制固化php.ini(open_basedir、error_log、opcache.restrict_api等);密钥须文件挂载而非环境变量;pdo需显式释放或启用持久连接;全程验证配置生效与内存稳定性。

生产环境用独立 PHP(非 PHP-FPM / Apache 模块)运行,比如 php -S 或 FrankenPHP Worker 模式,安全风险远高于传统部署——没有 Web 服务器层过滤、无进程隔离、错误配置直接暴露、资源复用易泄漏。必须放弃“能跑就行”思路,从启动方式、配置固化、连接管理三方面硬约束。
php -S 不该出现在生产环境
php -S 是开发用的内置服务器,无 TLS、无请求限速、无并发控制、无日志分级,且会把错误堆栈直接打到响应体里。哪怕加了反向代理,只要 php -S 进程监听在公网或 Docker bridge 网络,就等于把 PHP 解释器裸奔暴露。
- 真实错误场景:
PHP Warning: include(): open_basedir restriction in effect会完整返回给客户端,泄露绝对路径 - 若源码含
phpinfo()或调试入口,php -S会原样执行并输出全部配置 - 无法禁用
eval、assert等函数——它们在 CLI SAPI 下默认不受disable_functions限制 - 替代方案:改用 FrankenPHP(Worker 模式)或轻量 Caddy + PHP-FPM,二者都支持
disable_functions全局生效
FrankenPHP Worker 模式下 php.ini 必须强制覆盖
FrankenPHP 的 Worker 模式复用 PHP 进程,意味着 php.ini 加载只发生一次,但很多配置(如 open_basedir、error_log)仍可能被运行时函数修改。不能依赖默认值,必须显式固化。
-
open_basedir必须设为业务实际路径,例如/var/www/html:/tmp:/var/log;若留空或设为/,file_get_contents('/etc/passwd')就能成功 -
error_log要指向容器内可写路径(如/var/log/php_errors.log),并确保log_errors = On;display_errors = Off单独验证,因为 CLI 和 Web SAPI 的 ini 值可能不同 -
opcache.restrict_api = "/dev/null"必须启用——否则攻击者可通过opcache_get_status()读取所有已加载脚本路径与缓存键 - 验证是否生效:启动后访问
/frankenphp/info(或执行php -r "print_r(ini_get_all());"),逐项核对关键项
敏感配置必须走文件挂载,禁止环境变量传密钥
Worker 进程常驻内存,其环境变量会残留于 /proc/[pid]/environ,Docker inspect 也能直接读出。数据库密码、JWT 密钥、API Token 绝不能通过 -e DB_PASSWORD=xxx 注入。
- 宿主机创建
/etc/secrets/db.pwd,权限600,属主root:www-data - Docker 启动时挂载:
-v /etc/secrets/db.pwd:/run/secrets/db.pwd:ro - PHP 中读取:
file_get_contents('/run/secrets/db.pwd', LOCK_EX);加LOCK_EX防止并发读取错乱 - 切勿用
getenv('DB_PASSWORD')或$_ENV['DB_PASSWORD']——它们在 Worker 下可能被前一个请求污染
PDO 持久连接必须配对释放,否则连接数缓慢爬升
Worker 复用进程,new PDO() 创建的连接不会随请求结束自动 close。不显式管理,netstat -an | grep :3306 | wc -l 会持续上涨,最终打满 MySQL 连接池。
- 必须启用持久连接:
'options' => [PDO::ATTR_PERSISTENT => true],否则每次新建连接开销更大 - 禁用框架自动 close(如 Laravel 的
DB::disconnect()),改为在业务逻辑末尾显式调用$pdo->close()(如果驱动支持)或置 null - 更稳妥做法:用连接池中间件(如 Swoole\Coroutine\MySQL),或退回到 PHP-FPM 模式,靠进程重启自然回收
- 监控基线:
memory_get_peak_usage(true)每次请求后应基本稳定;若持续 +2MB/1000 请求,大概率有静态变量或 PDO 句柄未释放
独立 PHP 运行环境的安全,本质是把“进程生命周期可控”这个假设拿掉后的重新设计。每个配置项、每行代码都要回答一个问题:它在第 10001 次请求时,是否还和第一次一样干净?
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











