跳到正文
>_ITDITDWeb 安全平台

安全指南

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

把 Laravel 等应用放到共享虚拟主机时,让 .env、.git、vendor 无法被外部读取的实战指南。核心做法是『把应用主体放在公开根目录之外,只对外暴露 public/』结构。本文图解目录布局的正误,给出立刻能做的 .htaccess 止血、长期对策的步骤、本站在实战中踩过的坑(bootstrap-redirect 比 symlink 更稳),以及自我检查的方法。

发布于 2026-06-07 更新于 2026-06-07 2 分钟阅读

对象:在共享虚拟主机(可使用 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 无法到达。

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现在马上能做的对策是什么?
A

在公开根目录的 .htaccess 里追加一段『拒绝访问 .env、.git、SQL 转储、credentials.json 等』的规则来止血。它可逆,不影响正常显示。但这只是应急处理,核心仍是结构改造。

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

黑名单是每加一个新名字的文件就多一个漏洞的『打地鼠』。只要把应用主体移到公开根目录之外,.env 从根本上就无法到达,不依赖规则也能安全。

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

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