/보안/SELinux avc denied 30초 진단 런북: nginx·httpd 접근 거부 해결
보안SELinuxavc denied

SELinux avc denied 30초 진단 런북: nginx·httpd 접근 거부 해결

'SELinux is preventing'·'avc: denied'로 nginx·httpd가 막혔나요? setenforce 0 대신 ausearch·audit2allow·semanage port·httpd_can_network_connect로 진단부터 정식 복구까지 복붙 명령으로 해결하는 실전 런북입니다.

SELinux avc denied 30초 진단 런북: nginx·httpd 접근 거부 해결

SELinux avc denied 30초 진단 런북: nginx·httpd 접근 거부 복붙 해결

파일권한도 정상, 방화벽도 열었는데 왜 막히지?

새벽에 nginx를 재기동했더니 502 Bad Gateway. ls -l로 파일 권한 확인, chown도 맞고, firewall-cmd로 포트도 열었다. 그런데도 Permission denied. 로그를 뒤지다 이런 줄을 만납니다.

CODE
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 -Zfirewall-cmd --list-allgetenforce판정
Permission denied (파일 read)라벨 이상(예: default_t)정상Enforcing파일 컨텍스트 문제
502 Bad Gateway (프록시)정상포트 열림Enforcinghttpd_can_network_connect 불리언
포트 바인딩 실패(name_bind)정상포트 열림Enforcing비표준 포트 미등록
DB/소켓 연결 거부(name_connect)정상정상Enforcing포트 타입 또는 불리언

핵심은 간단합니다. DAC 권한과 방화벽이 정상인데 여전히 막히고 getenforce가 Enforcing이면 SELinux를 의심하세요. 거부 로그는 다음 명령으로 즉시 추출합니다. (모두 root, 서버 셸에서)

Bash
getenforce                              # Enforcing 확인
ausearch -m avc -ts recent              # 최근 avc 거부 로그
journalctl -t setroubleshoot -e         # 사람이 읽기 쉬운 요약
sealert -a /var/log/audit/audit.log     # setroubleshoot-server 설치 시 상세 분석

ausearch 출력의 핵심 필드를 읽는 법입니다.

CODE
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_t
  • tclass : 대상 종류 → tcp_socket

즉 "httpd_t가 tcp 8080 소켓 연결을 거부당했다"로 해석됩니다. 이 세 필드만 읽으면 복구 방향이 정해집니다.

복구 ① 파일·소켓 컨텍스트 바로잡기

가장 흔한 사례. 웹루트를 기본 /var/www에서 /srv/www로 옮겼더니 avc: denied { read }가 뜹니다. 이동한 파일은 원래 라벨(default_t)을 그대로 들고 오기 때문입니다.

기대 컨텍스트를 먼저 확인하고, 규칙을 영구 등록한 뒤 실제 파일에 적용합니다. (root)

Bash
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로 정책 모듈부터 만들면 불필요한 권한을 영구히 허용하게 됩니다. 반드시 컨텍스트·불리언을 먼저 확인하세요.

표준 타입으로 안 풀리는 진짜 예외 상황에서만 정책 모듈을 만듭니다.

Bash
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)

Bash
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가 납니다.

Bash
getsebool -a | grep httpd                           # 후보 불리언 목록
setsebool -P httpd_can_network_connect on           # 리버스 프록시/외부 연결 허용

자주 쓰는 불리언 정리표입니다. -P재부팅 후에도 유지한다는 뜻이라 운영에서는 거의 항상 붙입니다.

불리언용도
httpd_can_network_connecthttpd가 임의 네트워크로 아웃바운드 연결(리버스 프록시 등)
httpd_can_network_connect_dbhttpd가 원격 DB 포트로 연결
httpd_read_user_content사용자 홈 디렉터리 콘텐츠 읽기
httpd_enable_homedirs~/public_html 서비스
nis_enabledNIS 기반 인증 환경 허용

컨테이너(Podman) 팁: 볼륨 마운트 후 컨테이너가 파일을 못 읽으면 container_file_t 라벨 문제입니다. podman run -v /data:/data:Z ...처럼 마운트에 **:Z**를 붙이면 자동으로 라벨을 붙여줍니다.

실무 한마디: 왜 setenforce 0로 도망치면 안 되나

현장에서 가장 많이 보는 실수가 setenforce 0으로 꺼버리고 "해결됐다"고 넘어가는 겁니다. 저도 급할 때 유혹을 느끼지만, 이건 해결이 아니라 문제를 안 보이게 덮는 것입니다. 재부팅하면 다시 Enforcing으로 돌아오고, 그때 왜 막히는지 아무도 기억 못 합니다.

Permissive는 "끄기"가 아니라 **"거부를 전량 로그로 수집하는 진단 도구"**로 써야 합니다. 전체를 끄지 말고, 문제되는 도메인만 permissive로 돌려 어떤 거부가 얼마나 쌓이는지 한 번에 관찰한 뒤 정책을 만드는 게 정석입니다.

Bash
semanage permissive -a httpd_t     # httpd_t만 permissive → 거부 전량 수집
# 로그 수집 후 audit2allow로 정책 검토
semanage permissive -d httpd_t     # 진단 끝나면 원복

운영 반영 전 체크리스트

  • ausearchscontext/tcontext/tclass 3필드를 실제로 읽었는가
  • 컨텍스트·불리언으로 풀리는지 먼저 확인했는가 (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로 적용해야 유지됩니다.

✦ ✦ ✦
편집 검토 · Editorial Review

이 글은 AI 에이전트가 자료 조사와 1차 초안 작성을 담당하고, 사람 편집자가 사실관계·출처·톤과 맥락을 검토한 뒤 발행했습니다. 환경(OS·버전)에 따라 결과가 다를 수 있으니 적용 전 공식 문서를 함께 확인하세요. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.

초안 · AI (Content Reviewer)·검토 · Nodelog 편집자·발행 ·

댓글

첫 번째 댓글을 남겨보세요.