/인프라/nginx 403 Forbidden 에러, 6대 원인별 5분 진단·해결 가이드
인프라nginx 403403 Forbidden

nginx 403 Forbidden 에러, 6대 원인별 5분 진단·해결 가이드

nginx 403 Forbidden을 권한·index 부재·root/alias·SELinux·deny·try_files 6대 원인으로 분류해 error_log 문자열로 진단하고, ls·namei·chcon 복붙 명령으로 상황별로 바로 해결하세요.

nginx 403 Forbidden 에러, 6대 원인별 5분 진단·해결 가이드

nginx 403 Forbidden 에러, 6대 원인별로 5분 안에 진단·해결하기

"방금 전까지 잘 되던 페이지가 왜 403?"

배포를 막 끝냈는데 브라우저에 403 Forbidden이 뜬다. 많은 분들이 이때 반사적으로 chmod 777을 갈겨버립니다. 운이 좋으면 풀리지만, 보안 구멍만 남기고 정작 다음에 또 403을 만나면 똑같이 헤매죠.

핵심부터 말씀드리면, nginx의 403은 권한 문제만이 아닙니다. 원인이 최소 6가지이고, 각 원인은 error_log에 서로 다른 문자열을 남깁니다. 즉, 추측으로 권한을 만지는 게 아니라 로그 한 줄로 원인을 특정하는 게 정석입니다.

당황하지 말고, 일단 로그부터 봅시다.

Bash
# 가장 먼저 실행할 명령 — 실제 원인 문자열을 확인
sudo tail -n 30 /var/log/nginx/error.log

5초 만에 방향 잡기: 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

실제 로그 샘플은 이렇게 생겼습니다.

LOG
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 forbiddenls -la /var/www/htmlindex 파일 배치 또는 autoindex on
root/alias 경로 오설정open() ... No such file 또는 403nginx -T | grep -E 'root|alias'경로 점검, alias trailing slash 정리
SELinux 컨텍스트Permission denied (13)getenforce + ausearch -m avcchcon -t httpd_sys_content_t
deny 규칙access forbidden by rulenginx -T | grep -E 'deny|allow'allow/deny 순서·대상 IP 재검토
try_files·location 충돌(명시적 에러 없이 403)nginx -T 로 location 블록 검토try_files·location 우선순위 정리

복붙용 진단·해결 명령어 모음

Bash
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이라도 서빙 구조에 따라 원인 분포가 다릅니다.

정적사이트 — ②와 ⑥이 흔함

Nginx
server {
    root /var/www/html;
    index index.html;
    location / {
        try_files $uri $uri/ =404;   # index 부재 시 403 대신 404로 명확히
    }
}

try_files$uri/가 있으면 디렉터리 자동탐색을 시도하다 index가 없으면 403이 납니다. =404를 명시하면 의도가 분명해집니다.

리버스 프록시 — location 매칭 누락

Nginx
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 패턴

Nginx
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분 진단 체크리스트

  1. error_log 마지막 30줄 확인 → 문자열로 원인 분기
  2. index/directory 문자열 → autoindex 또는 index 배치
  3. access forbidden by rule → deny/allow 순서 점검
  4. Permission deniednamei -l로 경로 권한 추적
  5. 권한 멀쩡한데도 거부 → getenforce + ausearch로 SELinux 확인
  6. 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. aliaslocation 경로를 치환하는 방식이라 슬래시 정합성이 중요합니다. location /img/ { alias /data/img/; }처럼 양쪽 모두 슬래시를 맞춰야 하며, 한쪽만 빠지면 경로가 어긋나 403/404가 발생합니다.

Q. 리버스 프록시인데 403이면 nginx 문제인가요? A. 아닐 수 있습니다. nginx가 정상적으로 proxy_pass했는데 백엔드(업스트림)가 403을 돌려준 것일 수 있습니다. error_log에 nginx 고유 문자열이 없다면 백엔드 애플리케이션 로그를 확인하세요.

✦ ✦ ✦
편집 검토 · Editorial Review

이 글은 AI 에이전트가 1차 초안을 작성한 뒤, 사람 편집자가 사실관계·출처·톤과 맥락을 검토하여 발행했습니다. 오류나 부정확한 내용이 확인되면 24시간 이내에 정정합니다.

작성 · Content Reviewer·검토 · 사람 편집자·발행 · 2026년 6월 13일

댓글

불러오는 중...