ubuntu/debian 上 apt 安装的 nginx 默认 apparmor 策略过严,需手动添加精确路径规则(如网站根目录、php-fpm 套接字、ssl 私钥、自定义日志),并执行 apparmor_parser -r 重载策略后 systemctl restart nginx 才生效,否则仍报 502/403 或 permission denied。

Ubuntu/Debian 上 apt 安装的 Nginx 默认 AppArmor 策略太严,不手动补充路径规则,服务大概率启动失败或 502/403 报错。
确认 AppArmor 是否真在拦你
别猜,先看日志。Nginx 启动失败或返回 Permission denied、operation not permitted 时,AppArmor 很可能就是元凶。
-
sudo aa-status—— 确认apparmor module is enabled且/usr/sbin/nginx在 “profiles are loaded” 列表里 -
sudo journalctl -u nginx --since "1 hour ago" | grep -i denied—— 直接抓 AppArmor 拒绝记录(关键!) -
sudo aa-status | grep nginx—— 检查当前是enforce还是complain模式;生产环境必须是enforce
往 /etc/apparmor.d/usr.sbin.nginx 里加什么规则
编辑该文件,在 /usr/sbin/nginx PUx, 下方追加你实际用到的路径。规则不是越宽越好,而是越精确越稳。
- 网站根目录(如
/var/www/myapp):/var/www/myapp/** r,(只读)或/var/www/myapp/** rwk,(读写+创建+锁) - PHP-FPM 套接字(如
/run/php/php8.1-fpm.sock):/run/php/php*.sock rw,(通配更稳妥,避免版本升级后失效) - SSL 私钥(如
/etc/ssl/private/mydomain.key):/etc/ssl/private/** r,(AppArmor 不管文件权限,只管是否允许读;密钥本身仍需600) - 自定义日志路径(如
/var/log/nginx/myapp.access.log):/var/log/nginx/myapp.* log,(log是 AppArmor 特殊权限,兼容 logrotate) - ⚠️ 避免
/var/**这类宽泛规则——它会绕过策略本意,还可能被后续安全审计工具标为高危
改完规则后怎么让 Nginx 真的用上
AppArmor 规则不会自动生效,必须 reload + restart 两步缺一不可,且顺序不能错。
-
sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.nginx—— 重载单个 profile(比重启整个 apparmor service 更轻量、更安全) -
sudo systemctl restart nginx—— 必须重启 Nginx,新进程才会按新策略启动 -
sudo aa-status --verbose | grep nginx—— 验证 profile 已加载且无语法错误(注意输出里有没有parse error) - 如果仍失败,可临时切 complain 模式收全拒绝事件:
sudo aa-complain /usr/sbin/nginx,复现请求后跑sudo aa-logprof生成建议规则(但生成结果常偏宽泛,得人工收紧)
为什么 aa-logprof 生成的规则总不准
aa-logprof 只能看到“被拦住的请求”,看不到“没被拦但也不该放行”的行为,所以它倾向于加宽泛路径。
- 比如 Nginx 只访问
/run/php/php8.1-fpm.sock,aa-logprof却可能建议/run/php/** rw, - 它无法区分临时文件、调试 socket、生产 socket,一律按出现过的路径通配
- 真正可用的规则必须基于你明确知道的部署路径来手写,而不是依赖
aa-logprof的“建议”直接复制粘贴 - 调试阶段用
complain+aa-logprof快速兜底,上线前务必人工核对并收紧每一条路径
最易被忽略的一点:AppArmor 策略只对新启动的进程生效。改完配置不 reload、reload 了不 restart nginx,等于白改。另外,路径末尾的 ** 和 * 语义不同,写错会导致规则完全不匹配——这不是语法报错,而是静默失效。










