安全指南

在虚拟主机上避免 .env 被公开的目录布局与配置

在共享虚拟主机上部署 Laravel 等应用时,如何让 .env、.git、vendor 无法被外部读取:把应用主体放到公开根目录之外、只公开 public/,并说明 .htaccess 应急处理、结构改造和自查方法。

发布于 2026-06-07 更新于 2026-10-06 4 分钟阅读
本页目录

对象:在共享虚拟主机(使用 Apache/.htaccess 的环境)上放置 Laravel 等「以只公开 public/ 为前提」的应用的人。这是从真实事故(→ .env 全公开的故事)中提炼出来的、用于保护自己环境的步骤。

本站的看法:这不是“个人的疏忽”

虚拟主机早期的习惯是把所有东西都直接放在 public_html 下。以只暴露 public/ 为前提的 Laravel 进入了这种环境,结果危险的目录布局成了事实上的标准,连各家服务商自己的文档也把项目放在 public_html 之内。所以对策要靠机制而不是注意力:改变部署流程的默认做法。

用图掌握正确的目录布局

✗ 危险(主体放在公开根目录之下)

public_html/
├─ .env   ← 能被读到!
├─ .git/  vendor/
└─ public/

✓ 安全(主体在外,只公开 public)

app-laravel/   ← 公开根目录之外
├─ .env  .git/  ← 无法到达
└─ public/ → 从 public_html 处 require
主体放在 docroot 之外,对外公开的只有 public/。仅此一点就让 .env 无法到达。

按环境选择对策

「不让 .env 被公开」的具体做法,取决于服务器类型和你的权限。先找到符合自己情况的一行,再按下面的步骤操作。

环境长期对策应急处理注意点
共享虚拟主机(可用 SSH)把应用主体移出公开目录,公开目录里只留一个小的 index.php(Step 2)在 .htaccess 里拒绝访问把公开目录本身做成 symlink,在某些环境下会返回 500
共享虚拟主机(只有 FTP)用 FTP 客户端把应用主体重新上传到公开目录(public_html 等)的上一级在 .htaccess 里拒绝访问大多数虚拟主机允许写入公开目录的上一级;不允许时请联系客服
控制面板可以设置公开目录把公开目录直接指向 app-laravel/public不需要(但仍要检查)切换后,删除旧公开目录里残留的应用副本
VPS(Apache)把 VirtualHost 的 DocumentRoot 指向 public/在服务器配置里写拒绝规则AllowOverride None(Apache 2.4 的默认值)时不会读取 .htaccess
VPS(Nginx)把 root 指向 public/,并拒绝访问以点开头的文件在服务器配置里写拒绝规则Nginx 不读取 .htaccess

无论哪种环境,目标都一样:公开目录里没有 .env、.git、vendor/、storage/。Laravel 官方文档也提醒,如果把 index.php 移到项目根目录并从那里提供服务,会把许多敏感配置文件暴露到公网上。

Step 1:应急处理(立刻能做、可逆)

在公开根目录的 .htaccess 开头追加一段拒绝访问的规则(用于防御目的的配置示例)。它不影响正常页面。

# === SECURITY BLOCK ===
RedirectMatch 404 (?i)/\.(env|git)(\..*)?(/|$)
 
<FilesMatch "(?i)\.(sql|sql\.gz|bak|old|swp|save|orig|tgz)$|^credentials\.json$|\.bk[._]">
    Require all denied
</FilesMatch>
 
<FilesMatch "(?i)^(phpinfo|info)\.php$">
    Require all denied
</FilesMatch>
 
# if Laravel internals are visible at /<app>/storage/... etc.
RedirectMatch 403 (?i)^/[^/]+/(storage|bootstrap/cache|config|database|resources|routes|tests|vendor)(/|$)
# === END ===

应急处理不是终点

黑名单总会漏掉一些东西,比如 composer.lock,或者任何新出现的文件名。只把它当作在 Step 2 完成之前争取时间的手段。

Step 2:结构改造(长期对策)

把应用主体移到公开根目录之外,公开根目录里只留一个只负责加载主体的小入口。

# 1. Move the body outside the web root (same filesystem → mv is instant, atomic)
mv ~/example/public_html/app  ~/example/app-laravel
 
# 2. Put only a minimal entry in the web root
mkdir -p ~/example/public_html/sub
cat > ~/example/public_html/sub/index.php <<'PHP'
<?php require __DIR__ . '/../../app-laravel/public/index.php';
PHP
 
# 3. Copy Laravel public/.htaccess, symlink static assets
# 4. Clear config cache
rm -f ~/example/app-laravel/bootstrap/cache/{config,services,packages,routes-v7}.php

本站运维中的经验:bootstrap-redirect 比 symlink 更稳

把 public_html 本身做成指向 public/ 的 symlink,在某些环境里对已注册子域名的 docroot 不会跟随,会返回 500。让 docroot 保持为真实目录、由其中一个小小的 index.php 用绝对路径 require(即「bootstrap-redirect 型」),在权限受限的共享环境里更稳定。此外 opcache 可能会记住一次失败的加载并持续返回 500,最后的手段是更改入口文件名(如 entry.php)。

Step 3:更换密钥(按已被看到处理)

只要存在过被公开的时段,就把 .env 里的密钥按已被人看到来处理并替换掉。顺序是:外部 API、OAuth 密钥 → 加密密钥 → 邮件 → 数据库。也请参阅 .env 词条。重新签发 OAuth 密钥可能会使 refresh_token 失效,所以要先准备好重新授权的流程。

Step 4:自我检查(养成习惯)

最后,确认自己的站点上确实没有东西被公开(只针对自己拥有的域名)。

# A 200 with a body means it's exposed. 403/404 is okay for now.
curl -sI https://your-domain/.env | head -1
curl -sI https://your-domain/.git/config | head -1

每次部署都检查一遍,就能尽早发现目录布局上的失误。

更细致地检查自己的环境

除了 Step 4 的两行命令,下面这些检查能发现更多容易漏掉的问题。只对自己的服务器和自己的域名执行。

1

不只看状态码,还要看内容

有些应用对任何未知 URL 都返回首页和 200,所以只看状态码判断不了。用 curl -s https://your-domain/.env | head -c 120 之类的命令只输出开头部分,看看有没有 APP_KEY= 或 DB_PASSWORD= 这样的配置行。对于 .git/HEAD,如果出现以 ref: refs/heads/ 开头的行,就说明已被公开。

2

检查所有应用,而不只是一个

不只检查主域名,子域名和子目录下的每个应用都要重复检查。添加新应用时布局错误往往会被照搬,修好一个不代表其他的也修好了。

3

在服务器上搜索公开目录

能用 SSH 的话,直接查找危险文件,例如 find ~/public_html -maxdepth 4 \( -name ".env*" -o -name ".git" -o -name "*.sql" -o -name "*.zip" \)。没有任何输出才是想要的结果。只有 FTP 时,在 FTP 客户端里打开「显示隐藏文件」再查看文件列表。

4

确认 .htaccess 真的生效

在公开目录里放一个不含任何机密的无害测试文件(例如只写了 test 的 check.bak),然后通过 URL 打开。应该返回 403。如果能打开,说明 .htaccess 没有被读取,请确认服务器类型(例如是否为 Nginx)或相关设置。检查完后删除测试文件。

5

确认调试模式已关闭

在 .env 里,APP_ENV 应为 production,APP_DEBUG 应为 false。Laravel 文档指出,在生产环境中设置 APP_DEBUG=true 有可能把敏感的配置值暴露给用户。能用 SSH 的话,php artisan about --only=environment 也能看到同样的信息。

常见错误及修正方法

  • 删了 .env 却留下 .git:被公开的 .git 可能让人还原出过去的提交,其中包括旧的 .env 或密钥。把 .git 也移出公开目录,并重新签发所有曾经被提交过的机密。
  • 在公开目录里留下 .env.bak、.env.old 之类的副本:含有真实值的副本和 .env 一样敏感。把它移出去或删掉。.env.example 里只保留占位值。
  • 把整个项目打成 ZIP 上传,解压后没有删除:ZIP 里包含 .env。解压后立刻删除。
  • 生产环境以 APP_DEBUG=true 运行:错误页面可能显示配置值。生产环境一定要设为 false。
  • 以为写了 .htaccess 就安全了:Nginx 和 AllowOverride None 的环境根本不会读取它。用上面的测试文件方法确认。
  • 突然更换 APP_KEY:根据 Laravel 文档,更换密钥会让所有已登录用户退出登录,用旧密钥加密的数据也将无法解密。Laravel 11 及以后版本,在切换前把旧密钥放进 APP_PREVIOUS_KEYS。

真实案例:被公开的 .env 被大规模收集

2024 年 8 月,一家安全公司的研究团队报告了一起攻击活动:自动收集 Web 服务器上被公开的 .env 文件,利用其中的云服务凭据把数据复制出去,然后索要赎金。据报告,约 11 万个域名成为目标,被收集的环境变量超过 9 万个,其中约 7,000 个是云服务凭据,约 1,500 个是社交媒体账号凭据。

报告认为原因不在于云服务一侧的漏洞,而在于受害组织的配置错误:他们误把 .env 文件公开了。由此可以得出两点:

  • 被公开的 .env 不是碰巧被发现的,而是被程序大规模搜索出来的
  • 一个 .env 就可能把损害扩散到云存储、邮件等其他服务

不要以为小网站就不会被发现。同类布局错误的匿名案例,请看 .env 全公开的故事。

接着阅读

参考资料

  • Laravel 文档「Deployment」(请求应指向 public/index.php,不要从项目根目录提供服务,生产环境 APP_DEBUG=false):laravel.com
  • Laravel 文档「Configuration」(不要把 .env 提交到源代码管理;Debug Mode):laravel.com
  • Laravel 文档「Encryption」(更换 APP_KEY 与 APP_PREVIOUS_KEYS):laravel.com
  • Apache HTTP Server 2.4「core - AllowOverride」(设为 None 时不读取 .htaccess;默认值为 None):httpd.apache.org
  • Apache HTTP Server 2.4「.htaccess files」(何时使用及注意事项):httpd.apache.org
  • OWASP「WSTG-CONF-04 Review Old Backup and Unreferenced Files for Sensitive Information」:owasp.org
  • Unit 42(Palo Alto Networks)「Leaked Environment Variables Allow Large-Scale Extortion Operation in Cloud Environments」(2024 年 8 月 15 日):unit42.paloaltonetworks.com

FAQ

Q现在马上能做的对策是什么?
A

在公开根目录的 .htaccess 里追加一段拒绝访问 .env、.git、SQL 转储、credentials.json 等文件的规则。它可逆,不影响正常页面。但这只是应急处理,长期对策是结构改造。

Q为什么只靠 .htaccess 不够?
A

黑名单每出现一个新文件名就要加一条规则,总会有遗漏。把应用主体移到公开根目录之外,.env 就根本无法到达,也不需要维护规则。

Q用 symlink 把 public/ 暴露出去不行吗?
A

在某些环境下,把已注册子域名的 docroot 用 symlink 指向另一棵目录树时不会跟随,会返回 500。按本站的经验,让 docroot 保持为真实目录、由其中一个小小的 index.php 用绝对路径 require(即『bootstrap-redirect 型』)更稳定。

Q怎样确认自己站点的 .env 能不能从外部看到?
A

针对自己拥有的域名,用 curl 或浏览器打开 /.env 和 /.git/HEAD,同时看 HTTP 状态码和内容。403 或 404 暂时没问题;如果返回 200 并显示 'APP_KEY=' 之类的配置行,就说明已被公开。如果在子域名或子目录下运行多个应用,要逐个检查。能用 SSH 的话,也在服务器上搜索公开目录里有没有 .env 或 .git。

Q服务器是 Nginx,或者 .htaccess 不生效怎么办?
A

Nginx 不读取 .htaccess。Apache 在 AllowOverride 为 None 时也会忽略 .htaccess。这种情况下,要在服务器配置里把公开根目录(root 或 DocumentRoot)指向 Laravel 的 public/。Laravel 官方文档给出的 Nginx 配置示例里,也包含拒绝访问以点开头的文件(.well-known 除外)的规则。

Q.env 曾经被公开,把文件删掉就够了吗?
A

不够。如果在公开期间有人取走了文件,删掉文件后里面的密钥依然有效。要重新签发 API 密钥、OAuth 密钥、数据库和邮件密码以及 APP_KEY,并检查各服务的操作记录里有没有不认识的操作。如果 .git 也被公开过,要把过去任何一次提交里出现过的机密都当作已泄露处理。