Windows 환경에서 Nginx로 PHP를 서비스할 때 “느려졌다”는 느낌은 대부분 로그 한 줄로 시작합니다. 성능 문제는 원인이 여러 갈래로 갈라지기 때문에, 감으로 튜닝하기보다 에러 메시지를 먼저 해석해서 범위를 좁히는 게 제일 빠릅니다.
아래는 자주 만나는 에러/경고 문장 기준으로 따라가는 if/then 분기 트리입니다.
0) 시작 전: 로그 위치와 확인 순서(Windows)
if “어떤 메시지로 분기해야 할지 모르겠다” then 먼저 로그를 한 곳에 모으고, 시간대 기준으로 정렬해서 봅니다.
- Nginx error.log: 대부분의 핵심 힌트가 여기 나옵니다(업스트림 타임아웃, 버퍼, 연결 문제 등).
- Nginx access.log: 특정 URI만 느린지, 응답 시간($request_time)이 튀는지 확인합니다(설정되어 있다면).
- PHP 로그: php.ini의 error_log 또는 애플리케이션 로그(프레임워크 로그 포함).
- Windows Event Viewer: 프로세스 크래시/권한/서비스 관련 단서가 남는 경우가 있습니다.
then 같은 시각대(초 단위)로 Nginx error.log ↔ PHP 로그를 맞춰보면 “Nginx가 기다리다 포기했는지”, “PHP가 실제로 느린지”가 구분됩니다.
1) if Nginx에 “upstream timed out” / “FastCGI sent in stderr: ...”가 보이면
이 조합은 “Nginx가 PHP 응답을 기다리다 시간 초과”이거나 “PHP가 에러를 내면서 stderr로 뭔가를 뱉는 중”인 경우가 많습니다.
- if error.log에 upstream timed out (110: Connection timed out) then
-
- 먼저 “느린 요청”인지 확인: 같은 URI에서 반복되는지, 특정 시간대(배치/크론 대체 작업)인지 확인합니다.
- then PHP 쪽 최대 실행시간/타임아웃도 같이 봅니다: PHP의 max_execution_time, 그리고 PHP-FPM을 쓰는 구조라면 request_terminate_timeout 같은 제한(Windows에선 PHP-FPM 운용이 흔치 않지만, 구성에 따라 다를 수 있습니다).
- then Nginx의 fastcgi_read_timeout을 무작정 늘리기 전에, 실제로 무엇이 오래 걸리는지(외부 API, DB, 파일 I/O)를 먼저 좁힙니다.
- if FastCGI sent in stderr 뒤에 PHP 에러가 붙어 나오면 then
-
- 에러 문장을 그대로 PHP 로그에서도 찾아, 어느 파일/라인에서 났는지 확인합니다.
- then 해당 에러가 성능 저하를 유발하는 타입인지(무한 루프, 재시도, 대량 warning 발생) 판단합니다. warning이 초당 수천 번 쌓이면 그 자체로 느려질 수 있습니다.
핵심은 then “시간 초과를 늘릴지”가 아니라, if “PHP가 오래 걸리는 이유가 뭔지”를 먼저 찾는 것입니다.
2) if “upstream sent too big header” / “buffered to a temporary file”가 보이면
이쪽은 ‘성능 저하’처럼 보이지만, 실제로는 버퍼/헤더/응답 크기 문제인 경우가 많습니다.
- if upstream sent too big header then
-
- 대개 쿠키가 과도하거나(세션/추적 쿠키 과다), 애플리케이션이 헤더를 많이/크게 보내는 상황입니다.
- then “왜 헤더가 커졌나”부터 확인합니다. 예: 세션에 큰 데이터를 넣어 쿠키 기반 세션이 비대해짐, Set-Cookie가 여러 개 반복.
- then 필요한 경우 fastcgi_buffer_size / fastcgi_buffers 조정도 가능하지만, 원인을 줄이는 게 장기적으로 안전합니다.
- if buffered to a temporary file then
-
- Nginx가 응답을 메모리 버퍼에 못 담고 디스크(임시 파일)로 내렸다는 뜻이라, Windows 디스크 I/O에 따라 체감 성능이 크게 흔들릴 수 있습니다.
- then 응답이 큰 페이지(리포트/엑셀/대용량 JSON)인지, gzip/streaming이 가능한지, 페이지네이션이 가능한지부터 봅니다.
then “버퍼만 늘려서 해결”하면 잠깐 좋아져도, 어느 순간 다시 터집니다. 메시지가 말해주는 ‘크기’를 줄이는 방향이 우선입니다.
3) if 404/403/“The system cannot find the path specified” 같은 경로/권한 메시지가 섞이면
성능 글인데 경로/권한이 왜 나오냐면, Windows에서는 경로 문제로 재시도/폴백이 반복되면서 느려지는 경우가 종종 있습니다(특히 rewrite, front controller 구조).
- if Nginx 에러 로그에 CreateFile() ... failed (3: The system cannot find the path specified) then
-
- then root/alias 설정과 실제 디렉터리 구조가 일치하는지 먼저 확인합니다.
- then try_files가 불필요하게 많은 경로를 탐색하지 않는지 봅니다. (예: 존재하지 않는 경로를 여러 번 찍고 마지막에만 index.php로 가는 구조)
- if 403 forbidden가 간헐적으로 나오면 then
-
- Windows NTFS 권한(읽기/실행)과 Nginx 워커 프로세스가 접근하는 계정 권한을 확인합니다.
- then 정적 파일이 막혀서 매번 PHP로 우회 처리되는 구성은 없는지 점검합니다(정적은 정적으로 처리하는 게 기본적으로 빠릅니다).
then 경로/권한이 깔끔해지면 “느린데 이유를 모르겠다”는 케이스가 의외로 많이 정리됩니다.
4) if “connect() failed (10061)” / “No connection could be made”가 보이면
Windows에서 10061 계열은 “대상 포트에 서비스가 안 떠 있거나, 방화벽/보안 제품/포트 충돌로 연결이 거절됨”에 가깝습니다. 성능 문제처럼 보이지만, 사실은 연결 실패 → 재시도/대기 때문에 느린 경우가 많습니다.
- if Nginx가 PHP upstream(127.0.0.1:9000 등)에 연결하다 10061 then
-
- then 해당 포트에 PHP가 실제로 떠 있는지(프로세스/서비스)부터 확인합니다.
- then 동일 PC에서만 재현인지, 특정 시간대에만 재현인지 확인합니다(보안 제품의 스캔/격리, 업데이트 작업 등).
- if DB/Redis 등 다른 upstream으로 가다 10061 then
-
- then 애플리케이션이 타임아웃을 길게 잡고 “기다리다 포기”하고 있을 수 있으니, 연결 타임아웃/재시도 정책을 점검합니다.
then “느리다”가 아니라 “붙었다 끊겼다”라면, 튜닝보다 안정화(항상 떠 있게/항상 연결되게)가 우선입니다.
5) if 에러는 없는데 CPU/메모리/디스크가 튄다면(체크리스트)
로그에 결정타가 없을 때는, then “리소스 병목”을 체크리스트로 빠르게 훑는 게 효율적입니다.
- CPU: 특정 PHP 작업이 CPU를 계속 잡는지(이미지 처리, 암호화, 대량 JSON 인코딩).
- 메모리: PHP 메모리 제한에 걸리기 직전까지 가서 스왑처럼 느려지는지(Windows도 메모리 압박 시 체감 저하가 큼).
- 디스크 I/O: “buffered to a temporary file”가 잦은지, 로그가 과도하게 쌓여 쓰기 병목이 생기는지.
- 네트워크: 외부 API 호출이 느려져 전체 응답이 밀리는지(타임아웃/재시도 정책 확인).
- 로그 폭주: warning/notice가 초당 수천 줄이면, 그 자체로 성능 이슈가 됩니다(로그 레벨/필터/원인 코드 수정).
- 캐시: opcache(가능한 환경이면), 애플리케이션 캐시, 정적 캐시가 꺼져 있지 않은지.
then 한 번에 다 바꾸지 말고, 한 가지씩 바꾸고 동일 요청으로 재측정하는 방식이 가장 안전합니다.
마무리
Windows에서 Nginx + PHP 성능 이슈는 “튜닝 값”보다도, 로그에 찍힌 문장 하나를 제대로 해석해서 갈래를 타는 게 해결 속도를 좌우합니다.
위 if/then 트리로도 애매하면, 에러 로그의 해당 줄(앞뒤 몇 줄 포함)과 느린 URI, 재현 시간대를 묶어서 보면 다음 액션이 훨씬 명확해집니다.