/보안/SELinux Permission denied 해결법 — chmod 정상인데 막힐 때(avc denied)
보안SELinuxavc denied

SELinux Permission denied 해결법 — chmod 정상인데 막힐 때(avc denied)

chmod·chown은 정상인데 nginx·httpd가 Permission denied를 낸다면 SELinux가 범인입니다. getenforce·ausearch·audit2allow·restorecon·setsebool로 avc denied를 분기별 진단하고 복붙 명령으로 바로 푸는 실전 가이드.

SELinux Permission denied 해결법 — chmod 정상인데 막힐 때(avc denied)

SELinux 'Permission denied' 완벽 진단 가이드 — chmod는 정상인데 접근이 막힐 때

chmod 777을 줘도 안 풀리는 'Permission denied'의 정체

분명히 chmod 777을 주고 chown nginx:nginx까지 했는데도 nginx가 502를 뱉거나, 웹 페이지가 403을 던지거나, 애플리케이션 로그에 Permission denied가 찍힙니다. ls -l로 봐도 권한은 멀쩡합니다. 이쯤 되면 범인은 거의 확정입니다. SELinux입니다.

우리가 익숙한 chmod/chown은 DAC(임의적 접근 제어)입니다. 그런데 RHEL·Rocky·AlmaLinux 9/10 계열은 기본적으로 SELinux(MAC, 강제적 접근 제어)가 Enforcing 상태로 켜져 있습니다. DAC를 통과해도 MAC 라벨(컨텍스트)이 맞지 않으면 커널이 한 번 더 막습니다. 그래서 권한이 정상이어도 거부가 납니다.

이 글은 SELinux 개념 강의가 아닙니다. 지금 막힌 접근을 당장 푸는 명령을 분기별로 정리한 실전 가이드입니다. 급하면 1단계로 범인을 확정하고, 2단계에서 본인 증상에 맞는 블록만 복붙하세요.

1단계 — 범인이 SELinux인지 30초 만에 확인하기

먼저 SELinux가 켜져 있는지 한 줄로 봅니다.

Bash
getenforce
# Enforcing  → SELinux가 막고 있을 가능성 높음
# Permissive → 로그만 남기고 막지 않음 (다른 원인 의심)
# Disabled   → SELinux는 무죄

Enforcing이면 차단 로그를 잡습니다. 실시간으로 보려면:

Bash
tail -f /var/log/audit/audit.log | grep denied
# 다른 터미널에서 막힌 동작(웹 새로고침 등)을 다시 시도

이미 발생한 로그는 ausearch로 잡습니다.

Bash
ausearch -m avc -ts recent      # 최근 10분
ausearch -m avc -ts today       # 오늘 전체

그리고 왜 막혔는지 사람이 읽을 수 있게 번역해 줍니다.

Bash
ausearch -m avc -ts recent | audit2why

setroubleshoot가 설치돼 있다면 sealert가 친절한 해결책까지 제안합니다.

Bash
dnf install -y setroubleshoot-server
sealert -a /var/log/audit/audit.log

avc: denied 로그 한 줄 해부

실제 로그는 이렇게 생겼습니다. 어디를 봐야 하는지 화살표로 짚어 보겠습니다.

CODE
type=AVC msg=audit(1718440000.123:456): avc:  denied  { read } for
  pid=2314 comm="nginx"                ← ① 누가 (프로세스 이름)
  name="index.html" dev="vda1" ino=12345
  scontext=system_u:system_r:httpd_t:s0          ← ② 출발지 라벨(프로세스)
  tcontext=unconfined_u:object_r:user_home_t:s0  ← ③ 도착지 라벨(파일) ← 범인!
  tclass=file permissive=0              ← ④ 무엇에 대한 접근인지(file/dir/tcp_socket…)

해석은 단순합니다. httpd_t(웹서버)가 user_home_t(홈 디렉터리 라벨이 붙은 파일)를 read하려다 막혔다는 뜻입니다. 웹 콘텐츠는 httpd_sys_content_t여야 하는데 라벨이 틀려서 거부된 전형적인 케이스죠. 즉 tcontext가 잘못된 컨텍스트라는 게 핵심 단서입니다.

2단계 — 원인 분기별 정밀 해결 (핵심 섹션)

로그의 tclasstcontext를 보면 어느 분기인지 거의 판별됩니다.

① 파일 컨텍스트가 틀린 경우 (가장 흔함)

tclass=file/dir이고 라벨이 default_t, user_home_t, var_t 등 엉뚱하게 붙어 있으면 컨텍스트 문제입니다. 먼저 확인:

Bash
ls -Z /var/www/html
# unconfined_u:object_r:default_t:s0  index.html   ← 잘못됨

표준 경로라면 정책 기본값으로 복원하면 끝입니다.

Bash
restorecon -Rv /var/www/html

웹루트를 /data/www 같은 비표준 경로로 옮겼다면, 먼저 그 경로의 기본 컨텍스트 규칙을 등록한 뒤 복원합니다.

Bash
semanage fcontext -a -t httpd_sys_content_t "/data/www(/.*)?"
restorecon -Rv /data/www

② 비표준 포트·디렉터리 정책이 필요한 경우

nginx를 8080 같은 비표준 포트로 띄우면 tclass=tcp_socket, name_bind 거부가 납니다. 포트 라벨을 추가하세요.

Bash
semanage port -a -t http_port_t -p tcp 8080
# 이미 등록된 포트를 수정할 땐 -a 대신 -m
semanage port -l | grep http_port_t   # 확인

③ boolean이 꺼져 있는 경우

가장 놓치기 쉬운 케이스입니다. 예를 들어 PHP가 외부 API나 DB에 붙어야 하는데 httpd_can_network_connect가 꺼져 있으면 네트워크 연결이 막힙니다. 현재 값 확인 후 켭니다.

Bash
getsebool -a | grep httpd
setsebool -P httpd_can_network_connect on

자주 막히는 boolean 비교표:

Boolean용도켜는 명령
httpd_can_network_connect웹서버가 외부 호스트/API로 아웃바운드 연결setsebool -P httpd_can_network_connect on
httpd_can_network_connect_db웹서버가 원격 DB(MySQL/PostgreSQL)에 접속setsebool -P httpd_can_network_connect_db on
httpd_use_nfsNFS 마운트 경로를 웹 콘텐츠로 사용setsebool -P httpd_use_nfs on
httpd_enable_homedirs~user/public_html 제공setsebool -P httpd_enable_homedirs on
nis_enabledNIS/외부 인증 연동 시 광범위 네트워크 허용setsebool -P nis_enabled on

-P 옵션이 생명입니다. -P 없이 setsebool을 실행하면 메모리에만 적용돼 재부팅하면 원래대로 돌아갑니다. "분명히 켰는데 서버 재시작 후 또 막혔다"는 사고의 90%가 -P 누락입니다. 영구 적용은 반드시 -P.

④ 진짜 커스텀 정책이 필요한 경우

위 셋 어디에도 해당하지 않는 정상 동작인데 막힌다면, 차단 로그로 전용 정책 모듈을 만듭니다.

Bash
# nginx가 발생시킨 거부만 모아 모듈 생성
ausearch -c 'nginx' --raw | audit2allow -M nginxlocal
# 생성된 .pp 설치
semodule -i nginxlocal.pp

# 무엇을 허용할지 사람이 먼저 읽고 판단할 것
ausearch -c 'nginx' --raw | audit2allow

설치 후 동작을 재시도하고 더 이상 새 avc: denied가 안 뜨는지 검증하세요. 모듈 목록은 semodule -l | grep nginxlocal로 확인합니다.

3단계 — 'setenforce 0' 임시 우회의 함정과 올바른 사용법

급한 마음에 setenforce 0을 치면 거짓말처럼 풀립니다. Permissive 모드는 거부를 차단하지 않고 로그만 남기기 때문이죠. 하지만 운영 서버를 그대로 Permissive로 두는 건 보안 통제를 통째로 내려놓는 것과 같습니다. 침해 시 SELinux가 막아 줬을 측면 이동·파일 접근이 전부 열립니다.

올바른 워크플로우는 "끄기"가 아니라 "진단용 잠깐 열기"입니다.

Bash
setenforce 0                 # 진단용으로 잠깐 Permissive
# 막혔던 동작을 전부 한 번씩 재현 → 모든 거부 로그 수집
ausearch -m avc -ts recent | audit2allow -M myapp
semodule -i myapp.pp         # 수집한 정책을 정식 적용
setenforce 1                 # 다시 Enforcing으로 복귀

/etc/selinux/config에서 SELINUX=disabled로 영구 비활성화하는 것은 최후의 수단입니다. 한 번 disabled로 부팅하면 파일 라벨이 갱신되지 않아, 나중에 다시 켤 때 전체 파일시스템 재라벨링(touch /.autorelabel 후 재부팅)이 필요해 운영 부담이 큽니다. 클라우드 이미지·컨테이너 호스트에서 SELinux 활성 비중이 늘어나는 요즘 흐름을 봐도, "끄는 게 아니라 다루는 것"이 정석입니다.

실무 경험 한마디: 제가 운영하던 환경에서 배포할 때마다 간헐적으로 403이 떴는데, 원인은 CI가 rsync로 파일을 올리면서 컨텍스트가 default_t로 덮인 거였습니다. 배포 스크립트 마지막 줄에 restorecon -Rv /var/www/html 한 줄을 넣자 재발이 사라졌습니다. SELinux 문제의 상당수는 "거창한 정책"이 아니라 컨텍스트 복원 한 줄로 끝납니다.

결론: 복붙 가능한 명령 치트시트

권한은 정상인데 거부가 날 때, 아래 순서대로만 가면 됩니다.

증상진단해결
403 / Permission denied (권한 정상)getenforce → Enforcing?아래로 진행
어떤 동작이 막혔는지 모름ausearch -m avc -ts recent | audit2why원인 분기 판별
파일 라벨이 틀림(ls -Z)tcontext가 default_trestorecon -Rv /var/www/html
비표준 웹루트라벨 규칙 없음semanage fcontext -a -t httpd_sys_content_t "/data/www(/.*)?"restorecon -Rv
비표준 포트(8080)tclass=tcp_socketsemanage port -a -t http_port_t -p tcp 8080
외부 연결/DB 막힘getsebool -a | grep httpdsetsebool -P httpd_can_network_connect on
위 어디에도 없음정상 동작인데 거부ausearch -c 'nginx' --raw | audit2allow -M nginxlocal && semodule -i nginxlocal.pp

마무리 체크리스트: ① getenforce로 범인 확정 → ② ausearch로 로그 확보 → ③ 분기에 맞는 명령 적용 → ④ setsebool엔 항상 -P → ⑤ 진단이 끝나면 setenforce 1로 복귀.

자주 묻는 질문 (FAQ)

Q. audit2allow가 만들어 준 정책을 그냥 다 allow 해도 되나요? A. 위험합니다. audit2allow는 "막힌 동작을 전부 허용"하는 규칙을 생성할 뿐, 그 동작이 정당한지는 판단하지 않습니다. 반드시 | audit2allow로 어떤 권한을 여는지 먼저 사람이 읽고, 정상 동작에 필요한 것만 모듈로 만드세요. 침해로 발생한 거부까지 통째로 허용하면 보안 구멍이 됩니다.

Q. setsebool로 켰는데 재부팅하면 또 막혀요. A. -P 옵션을 빠뜨렸을 확률이 가장 높습니다. -P 없이 실행하면 런타임 메모리에만 적용되어 재부팅 시 초기화됩니다. setsebool -P ...로 영구 적용하세요.

Q. 그냥 SELinux를 꺼버리면 안 되나요? A. 당장은 편하지만 권장하지 않습니다. RHEL 9·Rocky·AlmaLinux 9/10은 기본 Enforcing이며 클라우드·컨테이너 환경에서 SELinux 활용이 늘고 있습니다. 진단이 필요하면 잠깐 setenforce 0으로 로그를 수집하고, 정책을 적용한 뒤 setenforce 1로 돌아오는 워크플로우를 쓰는 게 안전합니다.

✦ ✦ ✦
편집 검토 · Editorial Review

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

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

댓글

불러오는 중...