nginx 403 Forbidden 에러, 6대 원인별로 5분 안에 진단·해결하기
"방금 전까지 잘 되던 페이지가 왜 403?"
배포를 막 끝냈는데 브라우저에 403 Forbidden이 뜬다. 많은 분들이 이때 반사적으로 chmod 777을 갈겨버립니다. 운이 좋으면 풀리지만, 보안 구멍만 남기고 정작 다음에 또 403을 만나면 똑같이 헤매죠.
핵심부터 말씀드리면, nginx의 403은 권한 문제만이 아닙니다. 원인이 최소 6가지이고, 각 원인은 error_log에 서로 다른 문자열을 남깁니다. 즉, 추측으로 권한을 만지는 게 아니라 로그 한 줄로 원인을 특정하는 게 정석입니다.
당황하지 말고, 일단 로그부터 봅시다.
# 가장 먼저 실행할 명령 — 실제 원인 문자열을 확인
sudo tail -n 30 /var/log/nginx/error.log5초 만에 방향 잡기: error_log 문자열로 분기하기
로그를 열면 보통 아래 세 문자열 중 하나가 보입니다. 이게 진단의 분기점입니다.
| error_log 대표 문자열 | 의심 원인 |
|---|---|
directory index of "/var/www/html/" is forbidden | ② index 파일 부재 (autoindex off) |
access forbidden by rule | ⑤ deny 규칙 |
Permission denied (errno 13) | ① 파일/디렉터리 권한 또는 ④ SELinux |
실제 로그 샘플은 이렇게 생겼습니다.
2026/06/14 10:21:33 [error] 812#812: *5 directory index of "/var/www/html/" is forbidden, client: 10.0.0.5 ...
2026/06/14 10:24:01 [error] 812#812: *7 access forbidden by rule, client: 10.0.0.5 ...
2026/06/14 10:27:18 [error] 812#812: *9 open() "/var/www/html/index.html" failed (13: Permission denied) ...Permission denied인데 권한은 멀쩡해 보인다? 그럼 십중팔구 SELinux입니다. (RHEL/CentOS/Rocky 계열에서 특히 흔함)
403이 뜨는 6대 원인 분류표
| # | 원인 | error_log 문자열 | 1줄 진단 명령 | 핵심 해결책 |
|---|---|---|---|---|
| ① | 파일/디렉터리 권한 | Permission denied (13) | namei -l /var/www/html/index.html | 디렉터리 755·파일 644, 소유자 일치 |
| ② | index 부재 | directory index ... is forbidden | ls -la /var/www/html | index 파일 배치 또는 autoindex on |
| ③ | root/alias 경로 오설정 | open() ... No such file 또는 403 | nginx -T | grep -E 'root|alias' | 경로 점검, alias trailing slash 정리 |
| ④ | SELinux 컨텍스트 | Permission denied (13) | getenforce + ausearch -m avc | chcon -t httpd_sys_content_t |
| ⑤ | deny 규칙 | access forbidden by rule | nginx -T | grep -E 'deny|allow' | allow/deny 순서·대상 IP 재검토 |
| ⑥ | try_files·location 충돌 | (명시적 에러 없이 403) | nginx -T 로 location 블록 검토 | try_files·location 우선순위 정리 |
복붙용 진단·해결 명령어 모음
ls -la /var/www/html # 파일/디렉터리 소유자·권한 한눈에 확인
namei -l /var/www/html/index.html # 경로 전체(상위 디렉터리 포함) 권한을 단계별 추적
sudo chown -R www-data:www-data /var/www/html # Debian/Ubuntu 소유자 일치 (RHEL은 nginx:nginx)
sudo chmod -R 755 /var/www/html # 디렉터리 진입권한 부여 (파일은 추후 644 권장)
getenforce # SELinux 모드 확인 (Enforcing이면 의심)
sudo chcon -R -t httpd_sys_content_t /var/www/html # nginx가 읽을 수 있는 컨텍스트 부여
sudo ausearch -m avc -ts recent # 최근 SELinux 거부(AVC) 로그 확인
nginx -t && systemctl reload nginx # 설정 문법 검증 후 무중단 리로드⚠️ 소유자 주의: **Debian/Ubuntu는
www-data, RHEL/CentOS/Rocky는nginx**가 기본 워커 유저입니다.ps aux | grep nginx로 실제 워커 유저를 확인하세요.
상황별 실전 해결
같은 403이라도 서빙 구조에 따라 원인 분포가 다릅니다.
정적사이트 — ②와 ⑥이 흔함
server {
root /var/www/html;
index index.html;
location / {
try_files $uri $uri/ =404; # index 부재 시 403 대신 404로 명확히
}
}try_files에 $uri/가 있으면 디렉터리 자동탐색을 시도하다 index가 없으면 403이 납니다. =404를 명시하면 의도가 분명해집니다.
리버스 프록시 — location 매칭 누락
location /api/ {
proxy_pass http://127.0.0.1:8080/;
}
# location / 가 정의 안 되어 정적 root를 찾다 403/404리버스 프록시에서 403이 뜨면 nginx가 아니라 백엔드(업스트림)가 돌려준 403일 수 있습니다. error_log에 nginx 문자열이 없다면 업스트림 응답을 의심하세요.
PHP-FPM — try_files + fastcgi 패턴
location ~ \.php$ {
try_files $uri =404;
include fastcgi_params;
fastcgi_pass unix:/run/php/php-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}try_files $uri =404가 빠지면 존재하지 않는 PHP 경로에서 403이 발생할 수 있습니다.
실무 경험 한마디: 요즘은 컨테이너가 진짜 함정
최근 현장에서 403 신고가 급증하는 1순위 원인은 의외로 Docker/k8s 볼륨 마운트 시 UID/GID 불일치입니다. 호스트의 1000:1000 파일을 컨테이너 안 nginx(UID 101)가 못 읽어 Permission denied가 나는 식이죠. 이럴 땐 호스트에서 chmod하기 전에 컨테이너 안에서 id로 워커 UID를 먼저 확인하세요. 그리고 RHEL 계열에서 "SELinux 끄면 되네"라고 setenforce 0 하는 건 임시방편일 뿐입니다. 컨텍스트를 고치는 게 정답이고, 권한·컨텍스트는 Ansible로 코드화해두면 재발이 거의 사라집니다.
5분 진단 체크리스트
error_log마지막 30줄 확인 → 문자열로 원인 분기index/directory문자열 → autoindex 또는 index 배치access forbidden by rule→ deny/allow 순서 점검Permission denied→namei -l로 경로 권한 추적- 권한 멀쩡한데도 거부 →
getenforce+ausearch로 SELinux 확인 nginx -t && systemctl reload nginx로 마무리
함께 보면 좋은 글: 업로드가 막히면 413 Request Entity Too Large, 백엔드 연결이 끊기면 502/504 Gateway 트러블슈팅 가이드를 참고하세요.
자주 묻는 질문 (FAQ)
Q. chmod 777로 해결됐는데 왜 쓰면 안 되나요? A. 누구나 읽고 쓰고 실행 가능한 상태라 웹쉘 업로드·변조 위험이 큽니다. 디렉터리는 755, 파일은 644가 표준이며, 쓰기가 필요한 디렉터리만 선별적으로 권한을 주세요.
Q. 파일 권한은 다 맞는데도 403이 떠요.
A. 상위 디렉터리(예: 홈 디렉터리 700)에서 막히는 경우가 많습니다. namei -l /home/user/site/index.html로 경로 전체를 추적하면 어느 단계에서 막혔는지 한 줄로 보입니다. nginx 워커가 경로 끝까지 도달하려면 모든 상위 디렉터리에 실행(x) 권한이 필요합니다.
Q. SELinux를 그냥 끄면 안 되나요?
A. setenforce 0은 보안 계층 전체를 내리는 행위입니다. RHEL 계열은 Enforcing 유지가 권장 흐름이니, chcon -t httpd_sys_content_t로 컨텍스트만 고치고 영구 적용은 semanage fcontext로 처리하세요.
Q. alias 끝 슬래시 유무로 왜 403/404가 갈리나요?
A. alias는 location 경로를 치환하는 방식이라 슬래시 정합성이 중요합니다. location /img/ { alias /data/img/; }처럼 양쪽 모두 슬래시를 맞춰야 하며, 한쪽만 빠지면 경로가 어긋나 403/404가 발생합니다.
Q. 리버스 프록시인데 403이면 nginx 문제인가요?
A. 아닐 수 있습니다. nginx가 정상적으로 proxy_pass했는데 백엔드(업스트림)가 403을 돌려준 것일 수 있습니다. error_log에 nginx 고유 문자열이 없다면 백엔드 애플리케이션 로그를 확인하세요.
이 글은 AI 에이전트가 1차 초안을 작성한 뒤, 사람 편집자가 사실관계·출처·톤과 맥락을 검토하여 발행했습니다. 오류나 부정확한 내용이 확인되면 24시간 이내에 정정합니다.
댓글
불러오는 중...