/보안/SELinux avc denied 30-second diagnostic runbook: fixing nginx/httpd access denials
SecuritySELinuxavc denied

SELinux avc denied 30-second diagnostic runbook: fixing nginx/httpd access denials

Blocked by "SELinux is preventing" or "avc: denied" on nginx/httpd? A hands-on runbook with copy-paste commands—from diagnosis through a proper fix using ausearch, audit2allow, semanage port, and httpd_can_network_connect—instead of setenfo

SELinux avc denied 30-second diagnostic runbook: fixing nginx/httpd access denials

SELinux avc denied 30-second diagnostic runbook: copy-paste fixes for nginx/httpd access denials

File permissions look fine, the firewall is open—so why is it still blocked?

You restarted nginx in the middle of the night and got a 502 Bad Gateway. You checked file permissions with ls -l, chown is correct, and you opened the port with firewall-cmd. Still Permission denied. Digging through the logs, you find a line like this:

CODE
SELinux is preventing /usr/sbin/nginx from name_connect access on the tcp_socket port 8080.

The culprit is SELinux MAC (Mandatory Access Control) policy. Even after Linux DAC (discretionary) permissions all pass, SELinux separately decides—by labels—whether this process may access this resource. This post is not a SELinux concepts lecture; it is a runbook that takes you from a single log line to a recovery command in 30 seconds. After Rocky/AlmaLinux migrations, more RHEL-family environments keep Enforcing as the default, so knowing this pattern versus not knowing it matters more and more.

30-second symptom triage table + immediate diagnosis

A table so you can see at a glance: "file permissions and firewall are fine + Enforcing = SELinux."

Symptomls -Zfirewall-cmd --list-allgetenforceVerdict
Permission denied (file read)Wrong label (e.g. default_t)OKEnforcingFile context problem
502 Bad Gateway (proxy)OKPort openEnforcinghttpd_can_network_connect boolean
Port bind failure (name_bind)OKPort openEnforcingNon-standard port not registered
DB/socket connect denied (name_connect)OKOKEnforcingPort type or boolean

The core idea is simple. If DAC permissions and the firewall are fine, you are still blocked, and getenforce is Enforcing, suspect SELinux. Extract denial logs immediately with the following commands. (all as root, on the server shell)

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

How to read the key fields in ausearch output.

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 } : the denied action (read / write / name_bind / name_connect, etc.)
  • scontext : the acting subject's domain → httpd_t (nginx also runs as httpd_t)
  • tcontext : the target's type → unreserved_port_t
  • tclass : the target class → tcp_socket

In other words: "httpd_t was denied a TCP connection to port 8080." Those three fields alone tell you which way to recover.

Recovery ① Fix file and socket contexts

The most common case. You moved the web root from the default /var/www to /srv/www and now you get avc: denied { read }. Moved files keep their original label (default_t).

First confirm the expected context, permanently register the rule, then apply it to the actual files. (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 registers a permanent rule that "this path must have this type," and restorecon applies the actual labels according to that rule. They are a pair. restorecon alone will regress; changing labels temporarily with chcon will revert on the next restorecon.

⚠️ Do not overuse audit2allow — if you jump straight to an audit2allow policy module for a problem that is solved by a standard context or boolean, you permanently allow unnecessary permissions. Always check contexts and booleans first.

Only create a policy module for genuine exceptions that standard types cannot fix.

Bash
ausearch -m avc -ts recent | audit2allow -M mymodule   # mymodule.te / .pp 생성
cat mymodule.te                                        # 무엇을 허용하는지 눈으로 검토(중요)
semodule -i mymodule.pp                                # 모듈 설치
semodule -l | grep mymodule                            # 적용 확인

Always open the .te file, review what rules are being added, and only then install. "Allow everything blindly" defeats the point of leaving SELinux on.

Recovery ② Unblock network denials with ports and booleans

Non-standard port bind failures (name_bind) are fixed by registering the port type. This is the case when you run nginx/httpd on 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

Reverse proxy / DB connection denials (name_connect) are usually a boolean problem, not a port problem. httpd_t is blocked from outbound network connections by default, so you get 502s when proxying or connecting to an external DB.

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

A table of commonly used booleans. -P means persist across reboot, so in production you almost always include it.

BooleanPurpose
httpd_can_network_connectAllow httpd outbound connections to arbitrary networks (reverse proxy, etc.)
httpd_can_network_connect_dbAllow httpd to connect to remote DB ports
httpd_read_user_contentRead content from user home directories
httpd_enable_homedirsServe ~/public_html
nis_enabledAllow NIS-based authentication environments

Container (Podman) tip: If a container cannot read files after a volume mount, it is a container_file_t label problem. Adding :Z to the mount, as in podman run -v /data:/data:Z ..., applies the label automatically.

A practical note: why you should not run away with setenforce 0

The most common field mistake is turning SELinux off with setenforce 0 and calling it "fixed." I feel the temptation when things are urgent too, but this is not a fix—it is hiding the problem. After a reboot it goes back to Enforcing, and nobody remembers why it was blocked.

Permissive is not "off"; it is a "diagnostic tool that logs every denial." Do not disable the whole system. Put only the offending domain into permissive, observe which denials pile up, then write policy. That is the proper approach.

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

Pre-production checklist

  • Did you actually read the three ausearch fields: scontext / tcontext / tclass?
  • Did you check whether a context or boolean would fix it first? (audit2allow is last resort)
  • Did you review the .te contents?
  • Did you persist booleans/ports with -P / permanent registration?
  • Did you encode it in IaC? With Ansible sefcontext, seboolean, and seport modules, storing policy in the repo prevents recurrence when you rebuild servers

References: official docs

The primary sources for the behavior, settings, and errors in this post are the following official documents. Check version-specific options and exact behavior there.

FAQ

Q. setenforce 0 does work—why shouldn't I use it? A. It temporarily disables the entire policy, so security controls vanish, and a reboot restores Enforcing so the problem repeats. If the goal is diagnosis, do not disable everything—put only the specific domain into semanage permissive -a and collect denial logs.

Q. Are modules created with audit2allow safe? A. Installing without reviewing the generated .te file can permanently allow more permissions than you need. Handle denials that a context or boolean can fix that way, and use audit2allow only for exceptions that standard methods cannot solve—after reviewing the .te.

Q. I changed the file label with chcon, but it reverts after restorecon. A. chcon is a temporary change and reverts if it disagrees with policy. Permanently register the rule with semanage fcontext -a -t <type> "<path>(/.*)?" and then apply with restorecon -Rv.

확인 정보
✦ ✦ ✦
편집 검토 · Editorial Review

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

편집 책임 · Nodelog 기술 편집팀·발행 · ·업데이트 ·

Comments

Be the first to comment.