SELinux avc denied 30초 진단 런북: nginx·httpd 접근 거부 복붙 해결
파일권한도 정상, 방화벽도 열었는데 왜 막히지?
새벽에 nginx를 재기동했더니 502 Bad Gateway. ls -l로 파일 권한 확인, chown도 맞고, firewall-cmd로 포트도 열었다. 그런데도 Permission denied. 로그를 뒤지다 이런 줄을 만납니다.
SELinux is preventing /usr/sbin/nginx from name_connect access on the tcp_socket port 8080.범인은 SELinux의 MAC(강제적 접근 제어) 정책입니다. 리눅스의 일반 권한(DAC)이 다 통과해도 SELinux는 별도로 "이 프로세스가 이 자원에 접근해도 되는가"를 라벨로 판단합니다. 이 글은 SELinux 개념 강의가 아니라, 로그 한 줄에서 복구 명령까지 30초 만에 가는 런북입니다. Rocky·AlmaLinux 전환 이후 RHEL 계열은 Enforcing이 기본으로 유지되는 환경이 늘어, 이 패턴을 아는 것과 모르는 것의 차이가 점점 커지고 있습니다.
30초 증상 감별표 + 즉시 진단
"파일권한·방화벽 다 정상 + Enforcing = SELinux 확정"을 한눈에 보는 표입니다.
| 현상 | ls -Z | firewall-cmd --list-all | getenforce | 판정 |
|---|---|---|---|---|
| Permission denied (파일 read) | 라벨 이상(예: default_t) | 정상 | Enforcing | 파일 컨텍스트 문제 |
| 502 Bad Gateway (프록시) | 정상 | 포트 열림 | Enforcing | httpd_can_network_connect 불리언 |
포트 바인딩 실패(name_bind) | 정상 | 포트 열림 | Enforcing | 비표준 포트 미등록 |
DB/소켓 연결 거부(name_connect) | 정상 | 정상 | Enforcing | 포트 타입 또는 불리언 |
핵심은 간단합니다. DAC 권한과 방화벽이 정상인데 여전히 막히고 getenforce가 Enforcing이면 SELinux를 의심하세요. 거부 로그는 다음 명령으로 즉시 추출합니다. (모두 root, 서버 셸에서)
getenforce # Enforcing 확인
ausearch -m avc -ts recent # 최근 avc 거부 로그
journalctl -t setroubleshoot -e # 사람이 읽기 쉬운 요약
sealert -a /var/log/audit/audit.log # setroubleshoot-server 설치 시 상세 분석ausearch 출력의 핵심 필드를 읽는 법입니다.
type=AVC msg=audit(...): avc: denied { name_connect } for pid=1234 comm="nginx"
dest=8080 scontext=system_u:system_r:httpd_t:s0
tcontext=system_u:object_r:unreserved_port_t:s0 tclass=tcp_socket permissive=0{ name_connect }: 거부된 행위(read / write / name_bind / name_connect 등)scontext: 행위 주체 도메인 →httpd_t(nginx도 httpd_t로 동작)tcontext: 대상의 타입 →unreserved_port_ttclass: 대상 종류 →tcp_socket
즉 "httpd_t가 tcp 8080 소켓 연결을 거부당했다"로 해석됩니다. 이 세 필드만 읽으면 복구 방향이 정해집니다.
복구 ① 파일·소켓 컨텍스트 바로잡기
가장 흔한 사례. 웹루트를 기본 /var/www에서 /srv/www로 옮겼더니 avc: denied { read }가 뜹니다. 이동한 파일은 원래 라벨(default_t)을 그대로 들고 오기 때문입니다.
기대 컨텍스트를 먼저 확인하고, 규칙을 영구 등록한 뒤 실제 파일에 적용합니다. (root)
matchpathcon /var/www/html/index.html # 기대 라벨 확인 → httpd_sys_content_t
semanage fcontext -a -t httpd_sys_content_t "/srv/www(/.*)?"
restorecon -Rv /srv/www # 규칙대로 파일 라벨 재적용
ls -Z /srv/www # httpd_sys_content_t 확인semanage fcontext는 "이 경로는 이 타입이어야 한다"는 영구 규칙을 등록하고, restorecon이 그 규칙대로 실제 라벨을 씌웁니다. 둘은 짝입니다. restorecon만 하면 재발하고, chcon으로 임시로 바꾸면 다음 restorecon 때 되돌아갑니다.
⚠️ audit2allow 남발 금지 — 위처럼 표준 컨텍스트나 불리언으로 풀리는 문제를
audit2allow로 정책 모듈부터 만들면 불필요한 권한을 영구히 허용하게 됩니다. 반드시 컨텍스트·불리언을 먼저 확인하세요.
표준 타입으로 안 풀리는 진짜 예외 상황에서만 정책 모듈을 만듭니다.
ausearch -m avc -ts recent | audit2allow -M mymodule # mymodule.te / .pp 생성
cat mymodule.te # 무엇을 허용하는지 눈으로 검토(중요)
semodule -i mymodule.pp # 모듈 설치
semodule -l | grep mymodule # 적용 확인.te 파일을 열어 어떤 규칙이 추가되는지 반드시 검토하고 넣으세요. "묻지마 허용"은 SELinux를 켜둔 의미를 없앱니다.
복구 ② 포트·불리언으로 네트워크 거부 풀기
비표준 포트 바인딩 실패(name_bind)는 포트 타입 등록으로 해결합니다. nginx/httpd를 8080에 띄운 경우입니다. (root)
semanage port -l | grep http_port_t # 현재 등록 포트 확인
semanage port -a -t http_port_t -p tcp 8080 # 8080을 http 타입으로 추가
# 이미 다른 타입으로 등록돼 있으면 -a 대신 -m(수정)
semanage port -m -t http_port_t -p tcp 8080리버스 프록시·DB 연결 거부(name_connect)는 대개 포트가 아니라 불리언 문제입니다. httpd_t는 기본적으로 아웃바운드 네트워크 연결이 막혀 있어, 프록시나 외부 DB 연결 시 502가 납니다.
getsebool -a | grep httpd # 후보 불리언 목록
setsebool -P httpd_can_network_connect on # 리버스 프록시/외부 연결 허용자주 쓰는 불리언 정리표입니다. -P는 재부팅 후에도 유지한다는 뜻이라 운영에서는 거의 항상 붙입니다.
| 불리언 | 용도 |
|---|---|
httpd_can_network_connect | httpd가 임의 네트워크로 아웃바운드 연결(리버스 프록시 등) |
httpd_can_network_connect_db | httpd가 원격 DB 포트로 연결 |
httpd_read_user_content | 사용자 홈 디렉터리 콘텐츠 읽기 |
httpd_enable_homedirs | ~/public_html 서비스 |
nis_enabled | NIS 기반 인증 환경 허용 |
컨테이너(Podman) 팁: 볼륨 마운트 후 컨테이너가 파일을 못 읽으면 container_file_t 라벨 문제입니다. podman run -v /data:/data:Z ...처럼 마운트에 **:Z**를 붙이면 자동으로 라벨을 붙여줍니다.
실무 한마디: 왜 setenforce 0로 도망치면 안 되나
현장에서 가장 많이 보는 실수가 setenforce 0으로 꺼버리고 "해결됐다"고 넘어가는 겁니다. 저도 급할 때 유혹을 느끼지만, 이건 해결이 아니라 문제를 안 보이게 덮는 것입니다. 재부팅하면 다시 Enforcing으로 돌아오고, 그때 왜 막히는지 아무도 기억 못 합니다.
Permissive는 "끄기"가 아니라 **"거부를 전량 로그로 수집하는 진단 도구"**로 써야 합니다. 전체를 끄지 말고, 문제되는 도메인만 permissive로 돌려 어떤 거부가 얼마나 쌓이는지 한 번에 관찰한 뒤 정책을 만드는 게 정석입니다.
semanage permissive -a httpd_t # httpd_t만 permissive → 거부 전량 수집
# 로그 수집 후 audit2allow로 정책 검토
semanage permissive -d httpd_t # 진단 끝나면 원복운영 반영 전 체크리스트
-
ausearch로scontext/tcontext/tclass3필드를 실제로 읽었는가 - 컨텍스트·불리언으로 풀리는지 먼저 확인했는가 (audit2allow는 최후)
-
.te내용을 검토했는가 - 불리언·포트 명령에
-P/영구 등록이 반영됐는가 - IaC로 코드화했는가 — Ansible
sefcontext·seboolean·seport모듈로 정책을 저장소에 남기면 서버 재구축 시 재발을 막습니다
참고: 공식 문서
이 글에서 다루는 동작·설정·에러의 1차 출처는 다음 공식 문서입니다. 버전별 옵션과 정확한 동작은 여기서 확인하세요.
자주 묻는 질문 (FAQ)
Q. setenforce 0을 하면 되긴 하는데, 왜 쓰지 말라고 하나요?
A. 임시로 전체 정책을 무력화하는 것이라 보안 통제가 사라지고, 재부팅하면 원복돼 문제가 반복됩니다. 진단이 목적이라면 전체가 아닌 특정 도메인만 semanage permissive -a로 처리해 거부 로그만 수집하세요.
Q. audit2allow로 만든 모듈은 안전한가요?
A. 생성된 .te 파일을 검토하지 않고 설치하면 필요 이상의 권한을 영구 허용할 수 있습니다. 컨텍스트/불리언으로 해결되는 거부는 그쪽으로 처리하고, audit2allow는 표준 방법으로 안 풀리는 예외에만 .te를 확인한 뒤 사용하세요.
Q. 파일 라벨을 chcon으로 바꿨는데 restorecon 후 되돌아갑니다.
A. chcon은 임시 변경이라 정책 규칙과 어긋나면 원복됩니다. semanage fcontext -a -t <타입> "<경로>(/.*)?"로 규칙을 영구 등록한 뒤 restorecon -Rv로 적용해야 유지됩니다.
이 글은 AI 에이전트가 자료 조사와 1차 초안 작성을 담당하고, 사람 편집자가 사실관계·출처·톤과 맥락을 검토한 뒤 발행했습니다. 환경(OS·버전)에 따라 결과가 다를 수 있으니 적용 전 공식 문서를 함께 확인하세요. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.
댓글
첫 번째 댓글을 남겨보세요.