- 출처 / 참고: Nginx Documentation (ngx_http_upstream_module), PHP-FPM Configuration Guide (listen.owner, listen.mode), Red Hat SELinux User’s and Administrator’s Guide
명령어:
ls,namei,stat,ps,tail,grep,ausearch
키워드: 502 Bad Gateway, Permission denied, Unix Domain Socket, listen.owner, listen.group, listen.mode, www-data, SELinux httpd_t
사용처: Nginx와 백엔드(PHP-FPM, Gunicorn, uWSGI)를 유닉스 소켓(.sock)으로 연동 시 발생하는 502 접속 불가 장애 조치
실행예제
브라우저에서 502 Bad Gateway가 반환될 때 Nginx 에러 로그에서 connect() to unix:... failed (13: Permission denied) 메시지를 확인하고, 소켓 파일 및 상위 디렉터리의 권한/소유권과 구동 계정을 점검합니다.
# 1. 클라이언트 요청 테스트 및 502 응답 확인
$ curl -Iv http://example.com/
* Connected to example.com (192.168.1.100) port 80
< HTTP/1.1 502 Bad Gateway
< Server: nginx
# -> 502 응답 발생 확인
# 2. Nginx 에러 로그에서 Permission denied 원인 확인
$ sudo tail -n 20 /var/log/nginx/error.log
# [출력 예시]
# [error] 1512#1512: *1 connect() to unix:/run/php/php8.2-fpm.sock failed (13: Permission denied) while connecting to upstream, client: 203.0.113.10, server: example.com, request: "GET / HTTP/1.1", upstream: "fastcgi://unix:/run/php/php8.2-fpm.sock:", host: "example.com"
# 또는 Gunicorn/uWSGI 연동 시:
# [error] 1512#1512: *2 connect() to unix:/var/run/gunicorn/app.sock failed (13: Permission denied) while connecting to upstream ...
# 3. Nginx 워커 프로세스 실행 계정 확인 (master가 아닌 worker 프로세스 계정 확인 필수)
$ ps -ef | grep -E "nginx: worker" | awk '{print $1}' | head -n 1
www-data
# 4. 소켓 파일 및 상위 경로 전체의 권한 및 소유권 트리 추적 (namei)
$ namei -om /run/php/php8.2-fpm.sock
# f: /run/php/php8.2-fpm.sock
# drwxr-xr-x root root /
# drwxr-xr-x root root run
# drwxr-xr-x root root php
# srw-rw---- root root php8.2-fpm.sock <-- 소유권이 root:root이며 권한이 660이라 www-data 접근 불가!
# 5. RHEL/CentOS/Rocky 계열 SELinux 거부 감사 로그 확인
$ sudo ausearch -m avc -ts recent | grep nginx
# type=AVC msg=audit(...): avc: denied { write } for pid=1512 comm="nginx" name="app.sock" dev="tmpfs" ino=1234 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:var_run_t:s0 tclass=sock_file permissive=0
스크립트
Nginx 워커 계정과 유닉스 도메인 소켓 파일(PHP-FPM, Gunicorn, uWSGI 등)의 권한 및 소유권을 대조하여 읽기/쓰기 가능 여부를 판별하고 SELinux 차단 여부까지 종합 점검하는 쉘 스크립트입니다.
#!/bin/bash
# check_socket_perm.sh - Diagnose Nginx upstream Unix Domain Socket permission denied issues
SOCKET_PATH=${1:-"/run/php/php8.2-fpm.sock"}
echo "=========================================="
echo " [Nginx 소켓 권한 및 502 원인 진단]"
echo " 대상 소켓 경로: ${SOCKET_PATH}"
echo "=========================================="
# 1. 소켓 파일 실재 여부 및 소켓 타입 확인
if [ ! -e "$SOCKET_PATH" ]; then
echo " [오류] 지정한 소켓 파일이 존재하지 않습니다."
echo " -> 백엔드 데몬(PHP-FPM, Gunicorn, uWSGI 등)이 실행 중인지 확인하십시오."
exit 1
fi
if [ ! -S "$SOCKET_PATH" ]; then
echo " [주의] 해당 파일은 유닉스 소켓(Socket) 파일이 아닙니다."
fi
# 2. Nginx 워커 프로세스 계정 추출
NGINX_WORKER_USER=$(ps -eo user,cmd | grep "nginx: worker process" | grep -v grep | awk '{print $1}' | head -n 1)
if [ -z "$NGINX_WORKER_USER" ]; then
echo " [경고] 실행 중인 Nginx 워커 프로세스를 찾을 수 없습니다."
NGINX_WORKER_USER="www-data"
fi
echo -e "\n[1] 프로세스 및 소켓 메타데이터:"
echo " - Nginx 워커 실행 계정: ${NGINX_WORKER_USER}"
SOCK_OWNER=$(stat -c "%U" "$SOCKET_PATH")
SOCK_GROUP=$(stat -c "%G" "$SOCKET_PATH")
SOCK_PERM=$(stat -c "%a" "$SOCKET_PATH")
echo " - 소켓 파일 소유자 : ${SOCK_OWNER}"
echo " - 소켓 파일 소유 그룹 : ${SOCK_GROUP}"
echo " - 소켓 파일 권한 모드 : ${SOCK_PERM}"
# 3. Nginx 워커 계정 권한으로 소켓 쓰기 테스트 시도
echo -e "\n[2] Nginx 계정 접근 권한 시뮬레이션:"
sudo -u "$NGINX_WORKER_USER" test -w "$SOCKET_PATH" 2>/dev/null
if [ $? -eq 0 ]; then
echo " [정상] 계정 '${NGINX_WORKER_USER}'가 소켓에 쓰기 접근이 가능합니다."
else
echo " [오류] 계정 '${NGINX_WORKER_USER}'는 소켓 파일에 쓰기(Write) 권한이 없습니다!"
echo " -> Nginx 에러 로그의 (13: Permission denied) 유발 직접 원인."
fi
# 4. 상위 디렉터리 실행(x) 권한 점검
SOCK_DIR=$(dirname "$SOCKET_PATH")
sudo -u "$NGINX_WORKER_USER" test -x "$SOCK_DIR" 2>/dev/null
if [ $? -ne 0 ]; then
echo " [위험] 계정 '${NGINX_WORKER_USER}'가 소켓 상위 디렉터리(${SOCK_DIR})의 실행(x) 탐색 권한이 없습니다."
fi
# 5. SELinux 차단 여부 점검 (활성화된 경우)
echo -e "\n[3] SELinux 상태 점검:"
if command -v getenforce &>/dev/null && [ "$(getenforce)" != "Disabled" ]; then
SE_STATUS=$(getenforce)
echo " - SELinux 모드: ${SE_STATUS}"
RECENT_DENIALS=$(ausearch -m avc -ts recent 2>/dev/null | grep -c "nginx.*sock_file.*denied")
if [ "$RECENT_DENIALS" -gt 0 ]; then
echo " [경고] SELinux에 의한 Nginx 소켓 접근 차단 로그가 감지되었습니다 ($RECENT_DENIALS 건)."
echo " -> 조치 가이드: chcon 또는 semanage fcontext로 소켓 컨텍스트를 httpd_var_run_t 등으로 변경해야 합니다."
else
echo " [정상] 최근 SELinux 소켓 차단 이벤트가 없습니다."
fi
else
echo " - SELinux가 비활성화되어 있거나 미설치 환경입니다."
fi
echo -e "\n=========================================="
echo " [진단 완료]"
echo "=========================================="
해설
Nginx에서 백엔드와 통신할 때 네트워크 오버헤드를 줄이기 위해 TCP 포트(예: 127.0.0.1:9000) 대신 유닉스 도메인 소켓(Unix Domain Socket, .sock)을 사용하는 경우가 많습니다.
이때 발생하는 502 Bad Gateway의 대부분은 소켓 파일이 아예 없거나, 백엔드 프로세스가 소켓을 생성할 때 부여한 파일 권한/소유권이 Nginx 워커 프로세스 계정(www-data 또는 nginx)의 읽기/쓰기를 허용하지 않아 13: Permission denied가 발생하기 때문입니다.
1. 주요 발생 원인
- 소켓 생성 시 소유권 및 권한 미지정 (기본값 root 생성):
- 데몬(PHP-FPM, uWSGI 등)이 루트나 특정 일반 사용자로 구동되면서 소켓을 생성할 때
0660또는0600권한으로 소유자 전용으로 생성하여 Nginx 워커가 소켓에 접근하지 못하는 경우.
- 데몬(PHP-FPM, uWSGI 등)이 루트나 특정 일반 사용자로 구동되면서 소켓을 생성할 때
- 소켓 파일이 위치한 상위 디렉터리의 탐색(x) 권한 부족:
- 소켓 파일 자체의 권한이
0777이더라도, 상위 디렉터리(예:/home/user/app/run/)의 실행 권한(x)이 Nginx 워커 계정에 열려 있지 않으면 디렉터리 경로를 타고 들어가지 못해Permission denied가 발생합니다.
- 소켓 파일 자체의 권한이
- SELinux 보안 컨텍스트 차단:
- RHEL/Rocky Linux 환경에서 파일 권한(
chmod)은 정상이나, 웹 데몬 컨텍스트(httpd_t)가 일반 런타임 디렉터리 소켓(var_run_t,user_home_t)에 접근하는 것을 SELinux 정책이 차단하는 경우.
- RHEL/Rocky Linux 환경에서 파일 권한(
2. 환경별 해결 방법
1) PHP-FPM 환경 해결책 (pool.d/www.conf)
PHP-FPM 풀 설정 파일에서 소켓의 소유자와 소유 그룹, 권한 모드를 Nginx 워커 계정에 일치시킵니다.
# /etc/php/8.2/fpm/pool.d/www.conf (Ubuntu/Debian)
# 또는 /etc/php-fpm.d/www.conf (RHEL/Rocky)
listen = /run/php/php8.2-fpm.sock
; Nginx 워커가 www-data인 경우 (Debian 계열)
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
; RHEL 계열(Nginx 워커가 nginx인 경우)
; listen.owner = nginx
; listen.group = nginx
; listen.mode = 0660
수정 후 PHP-FPM을 재시작합니다:
sudo systemctl restart php8.2-fpm
2) Python Gunicorn / uWSGI 환경 해결책
Gunicorn 실행 옵션 또는 서비스 유닛 파일(systemd)에 umask를 지정하거나 그룹 권한을 부여합니다.
# systemd 서비스 파일 예시 (/etc/systemd/system/gunicorn.service)
[Service]
User=appuser
Group=www-data
UMask=0007
ExecStart=/usr/local/bin/gunicorn --workers 3 --bind unix:/run/gunicorn/app.sock wsgi:app
- 소켓 위치를
/tmp나 사용자 홈 디렉터리 대신/run/서비스명/경로로 지정하고, 디렉터리 소유권을appuser:www-data로 부여합니다.
3) SELinux 차단 해결책 (RHEL / CentOS / Rocky)
소켓 파일이 위치한 경로에 웹 서버 접근 허용 컨텍스트(httpd_var_run_t)를 부여합니다.
# 특정 소켓 경로 영구 컨텍스트 등록
sudo semanage fcontext -a -t httpd_var_run_t "/run/gunicorn(/.*)?"
sudo restorecon -Rv /run/gunicorn
주의사항
- 임시방편으로
chmod 777사용 지양:chmod 777 /run/php/php8.2-fpm.sock명령어로 즉시 문제를 넘길 수 있으나, 백엔드 데몬이 재시작되거나 서버가 리부팅되면 소켓이 삭제 후 재생성되면서 원래의 엄격한 권한(0660또는0600)으로 원복되어 502 에러가 재발합니다. 반드시 데몬의 설정 파일(listen.owner,listen.mode등)에 영구 지정해야 합니다.
- 사용자 홈 디렉터리(
~) 내부에 소켓 배치 금지:/home/deploy/app.sock형태로 홈 디렉터리 아래에 소켓을 두는 경우가 많습니다. 리눅스 기본 보안 정책상 사용자의 홈 디렉터리는700또는750으로 보호되어 있어 Nginx 워커 계정이 진입할 수 없습니다. 소켓 파일은 표준 런타임 경로인/run/또는/var/run/하위에 별도 디렉터리를 생성하여 격리 배치해야 합니다.
- Master 프로세스 계정과 Worker 프로세스 계정의 차이 인지:
- Nginx의 마스터 프로세스는 포트 바인딩을 위해
root로 동작하지만, 실제 클라이언트 요청을 받아 소켓으로 중계하는 워커 프로세스는nginx.conf상단의user지시어(예:user www-data;)에 명시된 비특권 계정으로 실행됩니다. 권한 검증은 항상 워커 계정을 기준으로 평가해야 합니다.
- Nginx의 마스터 프로세스는 포트 바인딩을 위해