对象:在共享虚拟主机(可使用 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
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>
# /<app>/storage 等内部目录被暴露出来时
RedirectMatch 403 (?i)^/[^/]+/(storage|bootstrap/cache|config|database|resources|routes|tests|vendor)(/|$)
# === END ===止血不是终点
黑名单一定会出现 composer.lock、.gitignore 之类『忘了堵』的口子,是一场打地鼠。只能把它当作撑到 Step 2 之前的争取时间手段。
Step 2:结构改造(长期对策)
把应用主体移到公开根目录之外,公开根目录里只放一个「只负责加载的小入口」。
# 1. 把主体移到公开根目录之外(同一文件系统时 mv 是瞬间完成且 atomic)
mv ~/example/public_html/app ~/example/app-laravel
# 2. 在公开根目录里只建一个最小入口
mkdir -p ~/example/public_html/sub
cat > ~/example/public_html/sub/index.php <<'PHP'
<?php require __DIR__ . '/../../app-laravel/public/index.php';
PHP
# 3. 复制 Laravel public/.htaccess,把静态资源用 symlink 链接
# 4. 清除配置缓存
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 密钥 → 加密密钥 → 邮件 → DB」。也请参阅 .env 词条。重新签发 OAuth 密钥有时会使 refresh_token 失效,所以要先准备好重新授权流程再执行。
Step 4:自我检查(养成习惯)
最后,确认自己的站点上是否真的发生了泄露(只针对自己拥有的域名)。
# 返回 200 且带正文就说明被公开了。返回 403/404 暂时算 OK
curl -sI https://你的域名/.env | head -1
curl -sI https://你的域名/.git/config | head -1养成每次发布都检查一遍的习惯,就能尽早发现目录布局上的失误。
接着阅读
FAQ
Q现在马上能做的对策是什么?
在公开根目录的 .htaccess 里追加一段『拒绝访问 .env、.git、SQL 转储、credentials.json 等』的规则来止血。它可逆,不影响正常显示。但这只是应急处理,核心仍是结构改造。
Q为什么只靠 .htaccess 不够?
黑名单是每加一个新名字的文件就多一个漏洞的『打地鼠』。只要把应用主体移到公开根目录之外,.env 从根本上就无法到达,不依赖规则也能安全。
Q用 symlink 把 public/ 暴露出去不行吗?
在某些环境下,把已注册子域名的 docroot 事后用 symlink 指向另一棵目录树时不会跟随,会返回 500。按本站的经验,让 docroot 保持为真实目录、由其中一个小小的 index.php 用绝对路径 require 的『bootstrap-redirect 型』更稳定。