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

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

This article is not yet available in English. Showing the original Korean version.

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 트러블슈팅 가이드를 참고하세요.

참고: 공식 문서

이 글에서 다루는 동작·설정·에러의 1차 출처는 다음 공식 문서입니다. 버전별 옵션과 정확한 동작은 여기서 확인하세요.

자주 묻는 질문 (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

Nodelog는 모든 콘텐츠의 내용과 출처를 공개 전에 검토합니다. 환경(OS·버전)에 따라 결과가 달라질 수 있는 기술 정보는 공식 문서와 함께 확인하며, 검토 기준과 정정 원칙은 편집 정책에서 안내합니다. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.

편집 책임 · Nodelog 기술 편집팀·발행 ·

Comments

Be the first to comment.