Nginx + php-fpm 환경에서 장애를 가장 빨리 줄이는 방법은 “증상(에러 메시지) → 원인 후보 → 확인 명령 → 수정”을 짧게 도는 겁니다. 아래는 현장에서 가장 자주 마주치는 메시지를 기준으로 한 if/then 분기 트리입니다.

보안 필터와 서버 파이프라인을 상징하는 일러스트

가능하면 먼저 Nginx error log와 php-fpm log를 동시에 열어두고(시간대 맞춰서), 같은 요청이 양쪽에서 어떻게 기록되는지부터 확인하세요.

0) 시작 전 공통 체크리스트(먼저 이것부터)

에러 메시지가 무엇이든, 아래는 5분 안에 해볼 만한 기본 확인입니다.

  • 로그 위치 확인: Nginx error log, php-fpm error log(또는 systemd journal)
  • php-fpm 상태: 프로세스가 살아있는지, 소켓/포트가 열려있는지
  • fastcgi 설정: fastcgi_pass 대상(Unix socket/127.0.0.1:9000)과 실제 php-fpm listen 일치
  • document root: Nginx root/alias와 실제 배포 경로 일치, 심볼릭 링크 포함
  • 권한: Nginx 사용자(www-data/nginx)와 php-fpm pool 사용자(user/group) 조합이 파일/디렉터리에 접근 가능한지
  • 보안 모듈: SELinux/AppArmor enforcing 여부, 차단 로그 존재 여부

공통 팁: 재시작보다 먼저 nginx -t로 설정 문법/경로 오류를 잡고, php-fpm은 pool 설정 변경 시만 재로드하는 습관이 안전합니다.

1) if 502 Bad Gateway / upstream prematurely closed connection then

의미: Nginx가 php-fpm(upstream)과 통신하다가 연결 실패/조기 종료를 만난 상태입니다.

502 에러 원인 분기를 나타내는 간단한 흐름도

  • if Nginx error log에 “connect() to unix:/... failed (2: No such file or directory)” then
    • php-fpm이 Unix socket을 생성하지 않았거나 경로가 다릅니다.
    • 확인: php-fpm pool 설정의 listen 경로와 Nginx fastcgi_pass 경로가 같은지 비교하세요.
    • 추가 확인: socket 디렉터리(/run/php 등)가 부팅 후 생성되는 구조인지(런타임 디렉터리) 확인합니다.
  • if “connect() ... failed (13: Permission denied)” then
    • 소켓 파일 권한/소유자 문제 가능성이 큽니다.
    • 확인: pool의 listen.owner, listen.group, listen.mode 값과 Nginx worker 실행 사용자 일치 여부를 봅니다.
    • 보안 관점: 권한을 무작정 777로 풀지 말고, Nginx 사용자만 접근하도록 최소 권한으로 맞추는 게 안전합니다.
  • if “upstream timed out (110: Connection timed out) while reading response header from upstream” then
    • php-fpm이 너무 느리거나(쿼리/외부 API/파일 IO), pool이 꽉 찼거나, 타임아웃이 짧습니다.
    • 확인: php-fpm pool의 pm.max_children, slowlog, request_terminate_timeout, Nginx fastcgi_read_timeout을 점검합니다.
    • 보안 관점: 공격성 트래픽(비정상 대용량 POST, 느린 요청)으로 pool이 고갈될 수도 있으니, 요청 크기 제한과 rate limit을 같이 고려하세요.
  • if “upstream prematurely closed connection while reading response header from upstream” then
    • php-fpm 워커가 죽었거나(PHP fatal, OOM), 스크립트가 강제 종료되었을 수 있습니다.
    • 확인: php-fpm error log에 fatal error/OOM killer 흔적이 있는지, 해당 요청 URI가 반복되는지 봅니다.
    • 보안 관점: 특정 파라미터 조합에서 fatal을 유발(입력 검증 부재)하는 경우가 있어, 재현되는 요청 파라미터를 분리해 점검하세요(개인정보/토큰은 마스킹).

빠른 결론: 502는 “연결(소켓/포트) 문제”와 “php-fpm이 응답을 못함(과부하/크래시/시간초과)”로 크게 갈립니다. 로그 한 줄이 갈림길입니다.

2) if 403 Forbidden then(권한/경로/보안 정책 분기)

의미: Nginx가 요청을 허용하지 않았습니다. PHP까지 못 가는 경우가 많습니다.

  • if Nginx error log에 “directory index of ... is forbidden” then
    • index.php/index.html이 없거나, autoindex가 꺼져 있고 디렉터리 목록을 보여줄 수 없는 상태입니다.
    • 해결: 정상 엔트리 파일을 배포하거나, 의도라면 301/404로 처리하세요.
  • if “access forbidden by rule” 또는 location/deny 관련 메시지 then
    • location 블록의 deny/allow, internal, 만족 조건 등이 걸렸을 수 있습니다.
    • 해결: 관리자 경로(/admin 등)를 IP allowlist로 묶었다면, 운영 IP가 바뀐 건 아닌지부터 확인하세요.
    • 보안 관점: 실수로 전체에 allow를 열어버리는 수정은 피하고, 필요한 경로만 최소 허용으로 유지합니다.
  • if “permission denied”가 파일/디렉터리 경로와 함께 뜬다면 then
    • Nginx 사용자가 상위 디렉터리 실행 권한(x)이 없어도 403이 납니다(파일 권한만 봐서는 놓치기 쉬움).
    • 해결: 프로젝트 루트까지의 각 디렉터리에 접근 가능한지(최소한 x) 확인합니다.
    • 보안 관점: 업로드 디렉터리에는 실행 권한을 주지 않거나, PHP 실행이 절대 되지 않도록 location 규칙으로 막는 것이 안전합니다.
  • if SELinux/AppArmor 환경이라면 then
    • 권한이 맞아도 보안 정책이 차단하면 403/502처럼 보일 수 있습니다.
    • 확인: 차단 로그가 있는지부터 확인하고, 필요 최소한의 컨텍스트/프로파일 조정으로 해결합니다(무작정 비활성화는 마지막 수단).

403이 갑자기 늘었다면, 최근 배포에서 root/alias 변경, 심볼릭 링크, 보안 규칙 추가가 있었는지부터 되짚는 게 가장 빠릅니다.

3) if “No input file specified.” then(PHP-FPM 경로 매핑 오류)

의미: php-fpm이 SCRIPT_FILENAME(실행할 파일 경로)을 받았는데, 실제 파일을 찾지 못했습니다. 대개 Nginx의 root/alias와 fastcgi_param 설정 불일치에서 발생합니다.

URL과 파일 경로 매핑 오류를 보여주는 폴더 구조

  • if location에 alias를 쓰고 있다면 then
    • alias는 root와 경로 결합 방식이 달라서, fastcgi_param SCRIPT_FILENAME을 잘못 만들기 쉽습니다.
    • 대응: 현재 설정에서 $document_root, $realpath_root, $request_filename이 각각 무엇을 가리키는지 로그로 확인하고, 실제 파일 경로와 맞춥니다.
  • if 심볼릭 링크를 사용한다면 then
    • deploy/current 같은 링크 구조에서 $document_root는 가리키지만 php-fpm이 접근 못 하거나, realpath 기준이 달라질 수 있습니다.
    • 대응: real path 기준으로 전달하는 방식($realpath_root 활용)을 검토하고, 링크/실경로 모두에서 권한을 확인합니다.
  • if php-fpm pool의 chroot를 쓰고 있다면 then
    • chroot 내부 기준으로 경로가 해석됩니다. Nginx가 넘기는 절대경로가 chroot 밖이면 무조건 못 찾습니다.
    • 대응: Nginx에서 넘기는 경로를 chroot 기준으로 맞추거나, chroot 설계를 재검토합니다.
    • 보안 관점: chroot는 강력하지만 운영 복잡도가 커집니다. 도입했다면 로그/모니터링까지 세트로 관리하세요.

실무 팁: “No input file specified.”는 코드 문제가 아니라 서버 설정의 경로 조립 문제인 경우가 대부분입니다. 해당 URI가 어떤 파일로 매핑되어야 하는지부터 종이에 한 번 써보면 빨리 풀립니다.

4) if “Primary script unknown” then(SCRIPT_FILENAME/권한/fastcgi 설정)

의미: php-fpm이 받은 스크립트 경로가 비정상(없음/접근 불가)이라 실행을 못 합니다. “No input file specified.”와 비슷하지만, 권한/오너 이슈도 더 자주 섞입니다.

  • if php-fpm 로그에 script filename이 찍힌다면 then
    • 그 경로가 실제 존재하는지, php-fpm pool 사용자로 읽을 수 있는지 확인합니다.
    • 특히 Nginx는 읽을 수 있어도, php-fpm pool 사용자가 못 읽으면 이 에러가 날 수 있습니다.
  • if open_basedir를 사용 중이라면 then
    • 스크립트/require/include가 open_basedir 밖을 참조하면서 부수적으로 실패할 수 있습니다.
    • 보안 관점: open_basedir는 우회/예외 관리가 어렵습니다. 쓰는 경우, 허용 경로를 최소화하고 배포 경로 변경 시 같이 갱신하세요.
  • if try_files로 라우팅(프론트 컨트롤러) 구성이라면 then
    • 존재하지 않는 경로를 index.php로 넘겨야 하는데, try_files 순서/매개변수가 꼬이면 실제 파일 경로가 엉킬 수 있습니다.
    • 대응: 정적 파일은 먼저, 그 다음 /index.php?$query_string 패턴이 의도대로 동작하는지 확인합니다.

5) 보안(입력 검증/권한) 관점에서 같이 점검할 것

장애를 고치면서 보안을 같이 챙기면, 같은 유형의 문제가 반복될 확률이 줄어듭니다. 아래는 php-fpm/Nginx 조합에서 특히 효과가 큰 포인트입니다.

  • 업로드 디렉터리 분리: 업로드 경로에서는 PHP 실행이 되지 않도록 location 규칙을 두고, 실행 권한/스크립트 처리 자체를 차단합니다.
  • 최소 권한 원칙: php-fpm pool 사용자와 Nginx 사용자를 분리/정리하고, 소켓/파일 권한은 필요한 범위만 허용합니다(문제 해결을 위해 광범위 권한 부여는 지양).
  • 요청 크기/시간 제한: client_max_body_size, fastcgi_read_timeout, php.ini의 max_execution_time 등을 서비스 특성에 맞게 설정해 DoS성 요청의 피해를 줄입니다.
  • 입력 검증을 “서버 자원 관점”으로도 보기: 문자열 길이 제한, 배열 깊이 제한, JSON 파싱 크기 제한 같은 방어가 없으면 특정 입력에서 CPU/메모리를 과소비해 502로 이어질 수 있습니다.
  • 로그에 민감정보 남기지 않기: 디버깅 시 request body/헤더를 통째로 남기면 토큰/비밀번호가 저장될 수 있습니다. 필요한 키만 마스킹해서 기록하세요.

특히 “특정 파라미터에서만 502/timeout” 패턴이 보이면, 단순 성능 문제가 아니라 입력 검증 부재로 인한 자원 고갈 가능성도 같이 의심해볼 만합니다.

마무리

502/403/No input file specified는 각각 메시지가 힌트를 많이 줍니다. 로그 한 줄을 기준으로 if/then으로 분기해서 “연결-경로-권한-정책-자원” 순서로 좁히면 대부분 빠르게 결론이 납니다.

문제를 해결한 뒤에는 업로드 경로 실행 차단, 최소 권한, 입력 검증(길이/형식/크기)까지 같이 점검해두면 같은 장애가 보안 이슈로 번지는 걸 줄일 수 있습니다.