시스템 관리_25 Nginx 업스트림 소켓 Permission denied 권한 거부로 인한 502 Bad Gateway 오류 원인 분석 및 해결 가이드

 
  • 출처 / 참고: 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. 주요 발생 원인

  1. 소켓 생성 시 소유권 및 권한 미지정 (기본값 root 생성):
    • 데몬(PHP-FPM, uWSGI 등)이 루트나 특정 일반 사용자로 구동되면서 소켓을 생성할 때 0660 또는 0600 권한으로 소유자 전용으로 생성하여 Nginx 워커가 소켓에 접근하지 못하는 경우.
  2. 소켓 파일이 위치한 상위 디렉터리의 탐색(x) 권한 부족:
    • 소켓 파일 자체의 권한이 0777이더라도, 상위 디렉터리(예: /home/user/app/run/)의 실행 권한(x)이 Nginx 워커 계정에 열려 있지 않으면 디렉터리 경로를 타고 들어가지 못해 Permission denied가 발생합니다.
  3. SELinux 보안 컨텍스트 차단:
    • RHEL/Rocky Linux 환경에서 파일 권한(chmod)은 정상이나, 웹 데몬 컨텍스트(httpd_t)가 일반 런타임 디렉터리 소켓(var_run_t, user_home_t)에 접근하는 것을 SELinux 정책이 차단하는 경우.

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

   

주의사항

  1. 임시방편으로 chmod 777 사용 지양:
    • chmod 777 /run/php/php8.2-fpm.sock 명령어로 즉시 문제를 넘길 수 있으나, 백엔드 데몬이 재시작되거나 서버가 리부팅되면 소켓이 삭제 후 재생성되면서 원래의 엄격한 권한(0660 또는 0600)으로 원복되어 502 에러가 재발합니다. 반드시 데몬의 설정 파일(listen.owner, listen.mode 등)에 영구 지정해야 합니다.
  2. 사용자 홈 디렉터리(~) 내부에 소켓 배치 금지:
    • /home/deploy/app.sock 형태로 홈 디렉터리 아래에 소켓을 두는 경우가 많습니다. 리눅스 기본 보안 정책상 사용자의 홈 디렉터리는 700 또는 750으로 보호되어 있어 Nginx 워커 계정이 진입할 수 없습니다. 소켓 파일은 표준 런타임 경로인 /run/ 또는 /var/run/ 하위에 별도 디렉터리를 생성하여 격리 배치해야 합니다.
  3. Master 프로세스 계정과 Worker 프로세스 계정의 차이 인지:
    • Nginx의 마스터 프로세스는 포트 바인딩을 위해 root로 동작하지만, 실제 클라이언트 요청을 받아 소켓으로 중계하는 워커 프로세스는 nginx.conf 상단의 user 지시어(예: user www-data;)에 명시된 비특권 계정으로 실행됩니다. 권한 검증은 항상 워커 계정을 기준으로 평가해야 합니다.