韩国服务器网站出现403怎么办:检查Nginx服务身份与目录权限

韩国服务器上的网站返回 403 时,不要先把它归因于负载过高、访问速度或带宽不足。403 是 Web 服务已经收到请求、但拒绝继续提供资源的结果,最常见的原因是 Nginx 工作进程无法穿过目录、无法读取目标文件,或者配置规则明确拒绝了请求。
先用一个确定存在的静态文件做基准测试,比只访问首页更容易定位问题。将域名、路径和协议替换成实际值:
curl -sS -D /tmp/headers.txt -o /tmp/body.html \
-H 'Host: www.example.com' \
http://127.0.0.1/assets/index.css
sed -n '1,10p' /tmp/headers.txt
如果本机请求已经返回 403,应优先检查 Nginx 日志、工作进程身份和文件路径权限;如果本机返回 200,而通过实际域名访问仍是 403,则 Nginx 本机目录权限不是首要嫌疑,应继续核对前置访问控制或实际请求是否落到了同一个 server 和 location。
先确认 403 是谁产生的
同一个状态码可能来自 Nginx、自定义错误页、上游应用或访问控制规则。修复权限前,先确定请求的处理链路。
查看响应和 Nginx 日志
在请求发生的同一时间窗口内查看 Nginx 服务日志:
sudo journalctl -u nginx --since "10 minutes ago" --no-pager
如果发行版没有把错误日志写入 systemd 日志,则先从配置中找出 error_log:
sudo nginx -T 2>/dev/null | grep -E '^[[:space:]]*error_log'
常见日志信息及其含义如下:
| 日志特征 | 通常说明 | 首要检查项 |
|---|---|---|
Permission denied 或 error 13 | Nginx 进程访问文件系统时被拒绝 | 工作进程用户、目录穿越权限、文件读取权限 |
directory index ... is forbidden | 请求指向目录,但没有可用的 index 文件,且未允许目录列表 | index 配置、目录访问意图 |
access forbidden by rule | 配置中的 deny、allow 或其他访问规则拒绝 | location 匹配顺序和访问控制 |
| 没有对应的 Nginx 文件错误,但出现上游 403 | 403 可能由应用或 FastCGI、代理上游返回 | 上游日志、应用授权逻辑 |
| 本机正常、外部异常 | 请求路径、Host、协议或前置访问控制可能不同 | 实际生效的 server、域名请求链路 |
directory index ... is forbidden 不等于目录权限一定错误。对于不希望暴露文件列表的网站,目录列表被禁止通常是合理的安全状态。只有当该 URL 本来就应该返回目录中的默认页面时,才需要检查 index index.html index.php; 或实际文件是否存在。
对比 Nginx 本机请求和实际域名请求
本机测试应尽量带上真实域名的 Host,否则可能命中默认虚拟主机:
curl -sS -D - -o /dev/null \
-H 'Host: www.example.com' \
http://127.0.0.1/assets/index.css
如果网站使用 HTTPS,应使用实际配置的 HTTPS 方式测试。证书尚未配置完成时可以临时使用 -k 进行故障定位,但这只影响证书校验,不应作为长期访问方式:
curl -k -sS -D - -o /dev/null \
-H 'Host: www.example.com' \
https://127.0.0.1/assets/index.css
本机和外部请求必须使用同一个路径、Host、协议以及相同的查询条件,否则两次测试不具备可比性。
确认真正提供文件的 Nginx 身份
Nginx 通常由主进程读取配置,再由工作进程处理请求。主进程可能以较高权限启动,但静态文件读取通常由工作进程完成,因此不能只看到 nginx 服务处于运行状态,就认为它可以访问网站目录。
查看 systemd 和工作进程身份
在使用 systemd 的 Linux 系统上,可以先查看服务属性:
sudo systemctl show nginx \
-p User -p Group -p SupplementaryGroups -p ExecStart
再直接查看正在运行的 Nginx 进程:
ps -eo user,group,pid,args | grep '[n]ginx'
重点观察类似下面的工作进程行:
nginx nginx 1234 nginx: worker process
其中的 user 和 group 才是检查静态文件读取权限时的重点。常见身份可能是 nginx、www-data 或部署时自定义的服务账号,不能只凭发行版猜测。
同时查看 Nginx 配置中的 user 指令:
sudo nginx -T 2>/dev/null | grep -E '^[[:space:]]*user[[:space:]]+'
配置中的 user 影响工作进程身份,但最终仍应以运行中的工作进程为准。若 systemd 使用了 User=、Group= 或 SupplementaryGroups=,还要把这些限制纳入判断。
确认账号及其所属组:
WEBUSER=nginx
id "$WEBUSER"
getent passwd "$WEBUSER"
如果实际进程显示的是 www-data,就应将示例中的 WEBUSER=nginx 改为:
WEBUSER=www-data
不要为了绕过 403,把 Nginx 工作进程改成 root。这会扩大 Web 请求进程的文件访问范围,无法解决配置误配、上游拒绝或 MAC 强制访问控制导致的问题,还会增加网站被利用后的影响范围。
按完整路径检查目录和文件权限
假设 Nginx 配置最终把请求映射到:
/srv/www/example/public/assets/index.css
需要检查的不只是 index.css,还包括从根目录到目标文件的每一级目录。Linux 权限中:
- 目录的
x表示允许穿过目录; - 目录的
r表示可以读取目录项列表; - 文件的
r表示可以读取文件内容; - 访问一个已知文件时,父目录通常至少需要具备穿越权限,目标文件需要具备读取权限;
- ACL、SELinux 或 AppArmor 可能在传统用户、用户组和模式位允许访问后继续拒绝请求。
使用 namei 查看每一级目录
TARGET=/srv/www/example/public/assets/index.css
namei -l "$TARGET"
stat -c '%A %U %G %n' "$TARGET"
readlink -f "$TARGET"
namei -l 可以把路径拆开显示。例如目标文件本身是 0644,但 /srv/www/example 对 Nginx 工作进程没有 x 权限,Nginx 仍然无法访问目标文件。
如果系统没有安装 namei,可以逐级检查:
ls -ld /srv
ls -ld /srv/www
ls -ld /srv/www/example
ls -ld /srv/www/example/public
ls -ld /srv/www/example/public/assets
ls -l /srv/www/example/public/assets/index.css
直接模拟工作进程读取
将 WEBUSER 和 TARGET 替换成实际值:
WEBUSER=nginx
TARGET=/srv/www/example/public/assets/index.css
sudo -u "$WEBUSER" -- id
sudo -u "$WEBUSER" -- test -r "$TARGET" \
&& echo "file-readable" \
|| echo "file-not-readable"
同时测试关键目录是否可以穿过:
for path in \
/srv \
/srv/www \
/srv/www/example \
/srv/www/example/public \
/srv/www/example/public/assets
do
if sudo -u "$WEBUSER" -- test -x "$path"; then
echo "traversable: $path"
else
echo "blocked: $path"
fi
done
这组测试能验证传统 Unix 权限,但不一定完整模拟 systemd 的安全限制、SELinux 上下文或 AppArmor 配置。如果测试显示可读、Nginx 错误日志仍出现拒绝,就继续检查强制访问控制。
注意符号链接和 root、alias 差异
如果网站目录中使用了符号链接,readlink -f 得到的真实目标路径也必须逐级可访问。只修改链接所在目录,而没有检查目标目录,通常无法解决问题。
还要从生效配置中确认 URL 到文件系统路径的映射:
sudo nginx -T 2>/dev/null | grep -E \
'^[[:space:]]*(root|alias|index|try_files|location|allow|deny|auth_basic)'
root 和 alias 的路径拼接方式不同,不能只根据 URL 目录名称猜测真实文件位置。若访问 /assets/index.css 实际被映射到了另一个目录,针对错误路径修改权限不会产生效果。
按最小权限原则修复
修复前先记录现状,避免后续无法判断修改了什么。权限信息可能包含敏感的目录结构,应限制保存文件的访问权限:
SITE=/srv/www/example
TS=$(date +%Y%m%d-%H%M%S)
sudo getfacl -R --absolute-names "$SITE" \
> "/root/site-acl.before.$TS.txt"
sudo chmod 600 "/root/site-acl.before.$TS.txt"
如果还要修改 Nginx 配置,应先备份实际配置目录:
sudo cp -a /etc/nginx "/root/nginx.before.$TS"
优先使用正确的用户组
对于由部署用户维护、由 Nginx 只读的网站,常见做法是让网站文件归部署账号所有,Nginx 通过专用用户组获得读取权限,而不是把所有文件的所有者改成 Nginx。
下面只是静态网站的示例,执行前必须确认网站没有依赖其他用户的写入权限:
SITE=/srv/www/example
WEBUSER=nginx
WEBGROUP=webcontent
sudo getent group "$WEBGROUP" \
|| sudo groupadd --system "$WEBGROUP"
sudo usermod -aG "$WEBGROUP" "$WEBUSER"
sudo chgrp -R "$WEBGROUP" "$SITE"
sudo find "$SITE" -type d -exec chmod 0750 {} +
sudo find "$SITE" -type f -exec chmod 0640 {} +
这组命令会递归改变网站目录的组和模式,可能影响部署程序、日志写入、上传程序以及其他本地服务,不能在不确认业务依赖的情况下直接执行。若网站包含上传目录或缓存目录,应把这些目录与只读静态内容分开处理,不要为了让 Nginx 访问而给整个网站增加写权限。
加入新用户组后,已有 Nginx 工作进程未必立即取得新的组信息。确认配置无误后再重启服务:
sudo nginx -t
sudo systemctl restart nginx
restart 会影响正在处理的连接,适合在可接受的维护窗口执行。若只是修改了已存在文件的 ACL,通常不需要为了权限本身重启,但仍应通过实际请求验证。
使用 ACL 做范围更小的授权
如果不希望改动整个网站的所有者和模式位,可以在系统安装了 setfacl 的前提下,为实际工作进程账号增加只读 ACL。示例仍以静态目录为目标:
SITE=/srv/www/example
WEBUSER=nginx
sudo getfacl -R --absolute-names "$SITE" \
> "/root/site-acl.before-change.txt"
sudo find "$SITE" -type d \
-exec setfacl -m "u:${WEBUSER}:r-x" {} +
sudo find "$SITE" -type f \
-exec setfacl -m "u:${WEBUSER}:r--" {} +
如果目标文件位于多个上级目录中,还必须为这些上级目录增加最基本的穿越权限。例如网站位于 /srv/www/example,而 Nginx 用户无法穿过 /srv 或 /srv/www,则可以只增加 x 权限:
sudo setfacl -m "u:${WEBUSER}:--x" /srv /srv/www
ACL 只解决当前文件的访问,不一定会自动覆盖后续发布的新文件。部署流程需要明确新文件的组、模式或默认 ACL,否则复测通过后下一次发布仍可能再次出现 403。
如果需要回滚已增加的 ACL,可以使用变更前保存的文件:
sudo setfacl --restore=/root/site-acl.before-change.txt
回滚前应确认备份文件对应的是同一个目录和同一次变更,避免把其他时间的权限状态覆盖回来。
排查 SELinux 或 AppArmor 拒绝
如果传统权限检查正常,但 Nginx 错误日志仍显示拒绝,在启用 SELinux 的系统上查看状态和上下文:
getenforce
ls -Zd /srv/www/example /srv/www/example/public/assets/index.css
sudo ausearch -m avc -ts recent | tail -n 30
当 SELinux 处于强制模式且审计日志明确指向网站路径时,应按照系统现有策略修复上下文,而不是反复扩大 Unix 权限。静态内容常使用 httpd_sys_content_t 类型,但具体类型应以发行版策略和现有网站设计为准:
sudo semanage fcontext -l | grep '/srv/www'
sudo restorecon -Rv /srv/www/example
如果该路径从未纳入持久化文件上下文规则,增加规则前要先记录当前配置,并确认动态写入目录不应套用静态只读类型。不要使用 chmod 777 或仅依赖 chcon 作为长期方案。
AppArmor 拒绝通常可以在内核日志或系统日志中找到:
sudo journalctl -k --since "10 minutes ago" | grep -i denied
日志明确显示 AppArmor 拦截时,应调整对应配置并重新加载策略;单纯修改目录属主不会改变 AppArmor 的访问判断。
用指标区分权限故障和其他问题
403 数量、请求耗时和错误日志需要结合起来看,单个指标不能证明根因。
403 比例只能判断影响范围
在同一时间窗口内,可以按路径统计:
403 比例 = 403 响应数 ÷ 该窗口内的总请求数
如果所有静态资源都同时出现 403,优先怀疑工作进程身份、父目录权限或发布后的统一权限变化。如果只有某个目录或某个 location 出现 403,则更应检查路径映射、目录默认页或规则配置。
这个比例能说明故障影响范围,但不能证明韩国服务器负载、网络质量或磁盘性能存在问题。一个几乎立即返回的 403,往往只是拒绝动作很快,并不表示网站已经恢复正常。
请求耗时需要与状态码一起解释
如果访问日志包含 $request_time、$status 或 $upstream_status,应把相同 URL 的成功请求和 403 请求放在同一时间段比较:
- 403 且没有上游状态码,通常更像是 Nginx 在本地规则或文件访问阶段拒绝;
- 403 同时带有上游 403,优先查看应用或上游服务的授权日志;
- 403 请求很快返回,不能作为“性能正常”的证明;
- 200 或 304 只说明该请求在当时获得了期望响应,不能证明所有目录和所有工作进程都具备相同权限。
如果日志格式没有记录上游状态码,不要仅凭响应头猜测来源,应结合 Nginx 错误日志和上游应用日志判断。
验收时同时检查成功和拒绝边界
修复的目标不是让所有 URL 都返回 200,而是让应该公开的资源可读、应该受保护的资源继续被拒绝。
| 验收项目 | 正常表现 | 异常表现与判断 | 建议留证 |
|---|---|---|---|
| 公开静态文件 | 返回预期的 200,条件请求可能返回 304 | 403 且错误日志有权限拒绝 | 请求时间、URL、响应头、错误日志 |
| 受保护路径 | 按设计返回 403 | 被错误返回 200,说明授权边界被放宽 | 保护规则、负向测试结果 |
| Nginx 工作进程 | 实际工作进程身份与权限测试账号一致 | 只检查了主进程,未验证 worker | ps、id、systemctl show |
| 完整目录路径 | 每一级目录可穿过,目标文件可读 | 某一级目录缺少 x,或目标文件缺少 r | namei -l、stat、test 结果 |
| 目录 URL | 有明确 index 时返回默认页面;不开放列表时可合理返回 403 | 预期首页却出现 directory index forbidden | root、alias、index 配置和错误日志 |
| 强制访问控制 | 无对应 SELinux 或 AppArmor 拒绝 | 传统权限正常但审计日志仍拒绝 | ausearch、内核日志、上下文 |
| 配置生效状态 | nginx -t 成功,实际请求使用新配置 | 只修改文件但未加载配置 | 测试输出、reload 或 restart 时间 |
| 其他发布文件 | 新发布文件仍保持可读且不扩大写权限 | 本次修复有效,下一次发布再次 403 | 发布前后 ACL、属主、模式对比 |
至少选择三类 URL 复测:一个公开静态文件、一个嵌套目录中的资源,以及一个按设计应该被拒绝的路径。这样可以同时验证读取能力、目录穿越能力和最小权限边界。
留证和复测条件
修复后不要只截图浏览器页面。浏览器的缓存、请求 Host 和实际响应来源都可能造成误判。建议保存以下信息,并记录每次测试的时间:
TS=$(date +%Y%m%d-%H%M%S)
SITE=/srv/www/example
WEBUSER=nginx
sudo nginx -T > "/root/nginx-T.after.$TS.txt"
sudo getfacl -R --absolute-names "$SITE" \
> "/root/site-acl.after.$TS.txt"
sudo ps -eo user,group,pid,args | grep '[n]ginx' \
> "/root/nginx-process.after.$TS.txt"
curl -sS -D "/root/response.$TS.headers.txt" \
-o "/root/response.$TS.body.txt" \
-H 'Host: www.example.com' \
http://127.0.0.1/assets/index.css
sudo chmod 600 \
"/root/nginx-T.after.$TS.txt" \
"/root/site-acl.after.$TS.txt" \
"/root/nginx-process.after.$TS.txt" \
"/root/response.$TS.headers.txt" \
"/root/response.$TS.body.txt"
响应正文和 Nginx 配置可能包含域名、路径、令牌或其他敏感信息,应只保存到管理员可访问的位置。
复测应满足三个条件:请求 URL 和 Host 与故障时一致;本机请求和实际域名请求分别验证;成功请求与负向权限测试都通过。若公开文件已经恢复,但 403 比例在每次发布后再次上升,应把重点转向发布账号的默认 umask、新文件继承的用户组和 ACL,而不是继续给目录增加更宽泛的权限。
只有在用户身份、目录穿越、目标文件读取、Nginx 规则和强制访问控制都通过验收后,才适合根据请求量、请求耗时和错误比例判断是否存在其他容量问题。权限修复的决策边界应始终是:公开资源可按预期读取,受保护资源仍然拒绝,且新增权限没有让 Nginx 获得不必要的写入能力。