Nginx上传目录禁止执行PHP:location匹配与fastcgi拦截写法

    发布时间:2026-09-18 12:34 更新时间:2026-09-18 12:34 阅读量:0

    不少站长都遇到过这样的情况:网站根目录下有个 uploads 或者 attachments 目录,本来是给用户传图片、PDF、压缩包用的,某天做文件排查时却发现里面多了几个 .php 文件。这些文件往往不是你自己放的,而是通过表单上传、编辑器接口或者某个老插件传进去的。文件既然落到了服务器上,只要有人知道路径并访问它,PHP-FPM 就会老老实实执行它。这时候,单靠 open_basedir 限制目录范围并不够,因为上传目录本身就在网站允许访问的范围之内。

    本文要解决的,就是在 Nginx 这一层再补一道闸:让上传目录只能被当静态文件读取,任何 .php 请求都直接拒绝。做法不复杂,但前提是得先弄清楚 location 的匹配顺序,否则规则写了不生效,白忙一场。

    先搞懂 location 的匹配顺序,规则才不会写废

    Nginx 处理请求时,会按一套固定优先级挑选 location,大致顺序是:先看等号精确匹配 = /path,再看带 ^~ 的前缀匹配,然后是普通前缀匹配,接着才是正则匹配 ~ 和 ~*,最后是兜底的前缀匹配。这里有两个容易踩的点。

    第一,普通前缀匹配和正则匹配之间,正则一旦命中就会立刻生效,不会再去比较前缀谁更长。所以如果你写了一个 ^~ 前缀,它就会压过所有正则,用来做"这个目录整体交给某段逻辑处理"最省心。第二,正则的书写顺序就是匹配顺序,写在前面且能命中的那条先赢。

    很多现成的建站环境里,PHP 的执行是这么配的:一条正则匹配所有 .php 结尾的请求,交给 fastcgi 处理。这条规则通常是全局生效的。于是问题来了——上传目录里的 .php 也会被它抓走。要让上传目录的规则赢,就得让我们的规则排在它前面,或者用 ^~ 让前缀匹配优先。

    两种拦截写法:拒绝访问与掐断 fastcgi

    先说最直接的一种:让上传目录里的脚本请求直接返回 403。写法是在 server 块里加一段针对上传目录的正则,位置要放在处理 .php 的 location 之前。

    location ~* ^/uploads/.*\.(php|php5|phtml|pht|phar)$ {
        deny all;
        return 403;
    }
    

    这段规则的意思是:只要请求路径以 /uploads/ 开头、且以这些脚本后缀结尾,一律拒绝。扩展名里带上 phtml、pht、phar 是有必要的,因为部分环境会把这些后缀也交给 PHP 解析,具体解析哪些后缀以你的 PHP-FPM 配置为准。

    第二种写法更进一步,不是简单拒绝,而是把这个目录整体标成静态,让请求根本走不到 fastcgi 那一环。它适合你希望上传目录只出静态文件、连正常的 PHP 入口都不需要的情况:

    location ^~ /uploads/ {
        location ~* \.(php|php5|phtml|pht|phar)$ {
            return 403;
        }
        try_files $uri $uri/ =404;
    }
    

    外层用 ^~ 前缀,保证这个目录优先于全局的 .php 正则;内层再补一条正则专门拦脚本后缀。这样即使有人传了个 shell.php 进去,访问时也只会拿到 403。

    如果你用的是宝塔面板,可以在「网站 → 设置 → 配置文件」里,把上面的片段加到 server 块内、放在 include enable-php-xx.conf 那一行之前。宝塔生成的 PHP 处理规则通常是通过 include 引入的,顺序放对了才有效。改完记得先执行 nginx -t 检查语法再重载:

    nginx -t
    nginx -s reload
    

    面板环境下也可以用 bt 命令重载,或者直接在面板里点「重载配置」。如果 nginx -t 报错,多半是括号没配对或者漏了分号,按提示的行号回去看即可。

    验证与几个容易忽略的细节

    规则加完不能只看配置文件,要实际发请求验证。用 curl 模拟访问一个上传目录里的 PHP 路径:

    curl -I https://www.example.com/uploads/test.php
    curl -I https://www.example.com/uploads/photo.jpg
    

    第一条应当返回 403(或你设定的状态码),第二条应当正常返回 200,说明静态文件没被误伤。这里要提醒的是,测试时别拿真实存在的马文件去试,随便测一个不存在的路径看状态码即可,重点验证的是"后缀命中拦截"这个行为。

    几个实际运维中常被忽略的点:一是目录名要写全,很多站点除了 uploads,还有 attachments、data、temp、cache 等可写目录,这些都应该纳入拦截范围,可以写成一条正则覆盖多个目录名。二是大小写,Linux 下路径区分大小写,正则加 i 标志(即 ~*)能避免 Uploads 这种漏网。三是伪静态规则的位置,如果站点本身有 WordPress 或 ThinkPHP 的 rewrite,注意别让上传目录的规则被前面的规则截走,必要时把拦截段整体上移。

    还有一点值得说清楚:这层防护解决的是"上传目录里的脚本被直接访问执行",它不能替代文件上传接口本身的校验。真正的源头还是要在上传环节限制后缀、校验 MIME、重命名文件,Nginx 这层属于兜底。两者配合,才算完整。

    小结与下一步

    给上传目录加一道 Nginx 拦截,成本很低,收益却很实在。核心就三句话:用 ^~ 或前置正则让规则优先于全局 PHP 处理;拦掉 php、phtml、phar 等脚本后缀;改完 nginx -t 验证再重载。做完这一步,建议顺手检查一下站点里还有哪些目录是可写的,把它们统一纳入拦截名单,同时回头看看上传接口是否做了后缀白名单。安全加固从来不是一条规则的事,而是把几个薄弱环节逐个补上。

    继续阅读

    📑 📅
    宝塔面板数据库连不上排查:socket与3306端口、bind-address区别 2026-09-18
    301跳转链太长拖慢首屏:curl与浏览器面板揪出重定向链 2026-09-18
    WordPress定时发布失效排查:wp-cron不触发与服务器计划任务替代方案 2026-09-18
    WordPress后台打开极慢前台正常:插件钩子与admin-ajax排查 2026-09-17
    网站图片被外站盗链怎么办:Nginx防盗链配置与误伤排查 2026-09-17
    宝塔面板网站目录权限怎么给:www用户与755/644取舍 2026-09-19
    Nginx日志按天切割实操:logrotate配置与不生效排查 2026-09-19
    Cloudflare 后真实访客 IP 丢失:CF-Connecting-IP 与 Nginx real_ip 配置 2026-09-19
    WordPress改域名后打不开:siteurl与home改错的救援步骤 2026-09-19
    宝塔面板SSL证书部署后HTTP/2没生效:协议开启、ALPN检查与Nginx版本差异 2026-09-19