上一篇 下一篇 分享链接 返回 返回顶部

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

发布人:Minchunlin 发布时间:2 天前 阅读量:17
韩国服务器网站出现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 13Nginx 进程访问文件系统时被拒绝工作进程用户、目录穿越权限、文件读取权限
directory index ... is forbidden请求指向目录,但没有可用的 index 文件,且未允许目录列表index 配置、目录访问意图
access forbidden by rule配置中的 deny、allow 或其他访问规则拒绝location 匹配顺序和访问控制
没有对应的 Nginx 文件错误,但出现上游 403403 可能由应用或 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,条件请求可能返回 304403 且错误日志有权限拒绝请求时间、URL、响应头、错误日志
受保护路径按设计返回 403被错误返回 200,说明授权边界被放宽保护规则、负向测试结果
Nginx 工作进程实际工作进程身份与权限测试账号一致只检查了主进程,未验证 workerps、id、systemctl show
完整目录路径每一级目录可穿过,目标文件可读某一级目录缺少 x,或目标文件缺少 rnamei -l、stat、test 结果
目录 URL有明确 index 时返回默认页面;不开放列表时可合理返回 403预期首页却出现 directory index forbiddenroot、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 获得不必要的写入能力。

目录结构
全文