Windows에서 PHP 세션이 “가끔” 끊기는 문제는, 실제로는 재현 조건(브라우저/도메인/HTTPS/저장 경로)이 매번 달라서 원인 파악이 늦어지는 경우가 많습니다. 이 글은 재현 → 원인 좁히기 → 해결 순서로, 안전한 체크리스트와 흔한 실수를 한 번에 정리합니다.

Broken chain between two servers representing session drop

먼저 “세션이 끊긴다”를 한 가지 케이스로 고정한 다음, 설정과 환경을 좁혀가면 생각보다 빨리 끝납니다.

1) 재현을 고정하기: 가장 작은 테스트 스크립트로 확인

애플리케이션 코드부터 보지 말고, 세션 자체가 유지되는지부터 확인합니다. 아래처럼 한 파일에서 session write/read가 되는지 확인해 보세요.

Flow diagram of cookie set and session read steps

  • 같은 브라우저(시크릿 모드 권장), 같은 URL(도메인/포트 포함), 같은 프로토콜(HTTP/HTTPS)로 반복 테스트
  • 첫 요청에서 session 값을 넣고, 새로고침/다른 페이지에서 읽기
  • 테스트 중에는 로드밸런서/프록시/개발용 터널(ngrok 등) 사용 여부를 고정

관찰 포인트: 응답 헤더에 Set-Cookie(PHPSESSID 등)가 내려오는지, 다음 요청에 Cookie가 다시 올라가는지입니다.

2) 원인 좁히기 A: 쿠키가 저장/전달되지 않는 경우(가장 흔함)

세션은 결국 “세션ID 쿠키”가 유지돼야 합니다. Windows라고 특별하진 않지만, 로컬 개발 환경에서 쿠키 정책 때문에 실패하는 패턴이 많습니다.

  • HTTP/HTTPS 혼용: 한 번은 HTTP로, 한 번은 HTTPS로 접속하면 cookie secure 설정에 따라 세션이 새로 생길 수 있습니다.
  • 도메인 불일치: localhost, 127.0.0.1, 머신명(hostname)이 섞이면 서로 다른 사이트로 취급됩니다.
  • SameSite/iframe/리다이렉트: 외부 로그인/결제처럼 cross-site 이동이 있으면 SameSite 정책에 걸릴 수 있습니다.
  • 프록시 뒤 HTTPS 종단: 서버는 HTTP로 보지만, 브라우저는 HTTPS로 보고 있어 secure 쿠키가 꼬일 수 있습니다(특히 리버스 프록시).

확인 방법: 브라우저 개발자도구(Application/Storage → Cookies)에서 PHPSESSID가 생성되는지, Path/Domain/Secure/SameSite 값이 기대와 같은지 확인합니다.

3) 원인 좁히기 B: session.save_path/권한/핸들러 문제

쿠키는 정상인데 세션 값이 유지되지 않으면, 서버 쪽 저장소가 의심됩니다. Windows에서는 특히 session.save_path 경로와 프로세스 계정 권한이 자주 문제입니다.

Session storage options diagram highlighting permission issues

  • session.save_path가 비어 있거나, 존재하지 않는 폴더를 가리킴
  • 권한 문제: IIS AppPool 계정(예: IIS APPPOOL\YourPool), Apache/Nginx 서비스 계정이 해당 폴더에 쓰기 불가
  • 핸들러 혼선: session.save_handler가 files인데 Redis/Memcached로 기대하고 있었다거나, 반대로 확장 모듈이 없어 fallback 되는 경우
  • 임시 폴더 청소: session.save_path를 Temp 계열로 뒀고, 주기적으로 청소되어 파일이 사라짐

빠른 체크: phpinfo()에서 session.save_handler, session.save_path 값을 확인하고, 실제로 그 경로에 세션 파일이 생성되는지(요청 직후)만 봐도 방향이 잡힙니다.

4) 해결 체크리스트(안전한 순서로 적용)

아래는 영향 범위가 작은 것부터 적용하는 순서입니다. 운영이라면 한 번에 여러 개를 바꾸지 말고, 한 항목 적용 → 재현 테스트로 진행하세요.

  • 접속 URL 고정: 개발 중이면 localhost/127.0.0.1 중 하나로 통일(리다이렉트 포함)
  • HTTPS 정책 정리: HTTPS를 쓸 거면 항상 HTTPS로(서버/프록시까지 포함)
  • cookie params 점검: Secure/SameSite/Path/Domain을 실제 서비스 구조에 맞게 조정(특히 cross-site 흐름이 있으면 SameSite)
  • session.save_path 명시: 존재하는 고정 폴더로 지정하고, 서비스 계정에 읽기/쓰기 권한 부여
  • GC/수명 설정 확인: session.gc_maxlifetime, session.cookie_lifetime이 너무 짧지 않은지 확인(테스트 시에는 넉넉히)
  • 시간/타임존: 서버 시간이 크게 틀어지면 만료 판단이 꼬일 수 있어 NTP/시간 동기화 확인
  • 멀티 프로세스/멀티 서버: 로컬은 괜찮은데 배포 후 끊기면 sticky session 또는 공용 세션 저장소(예: Redis) 고려

5) 흔한 실수 목록(증상별로 바로 의심할 것)

  • 증상: 로그인 직후 바로 로그아웃 → 리다이렉트 과정에서 도메인/프로토콜이 바뀌는지, SameSite가 흐름을 막는지 확인
  • 증상: 새로고침할 때마다 세션ID가 바뀜 → 쿠키가 저장되지 않음(브라우저 정책/응답 헤더/Set-Cookie 누락), 혹은 경로(Path)/도메인(Domain) 불일치
  • 증상: 어떤 페이지에서만 세션이 비어 있음 → 그 페이지가 다른 서브도메인/다른 포트/다른 프로토콜, 또는 출력(공백) 때문에 헤더가 깨져 쿠키가 설정되지 않는지 확인
  • 증상: 일정 시간 후 갑자기 다 같이 풀림 → session.gc_maxlifetime이 짧거나, session 파일 저장 위치가 청소됨(Temp), 또는 앱풀 재활용/서비스 재시작
  • 증상: 개발 PC에서는 되는데 특정 서버에서만 안 됨 → session.save_path 권한, 서비스 계정 차이, 보안 정책(AV/EDR로 임시 폴더 차단) 확인

6) 로그/관측 포인트: “세션이 사라진 시점”을 잡는 방법

추측을 줄이려면, 아래 3가지만 짧게 남겨도 원인이 빨리 좁혀집니다(민감정보는 기록하지 말고, 세션ID도 전체 저장은 피하세요).

  • 응답 Set-Cookie 존재 여부: 특정 요청에서 Set-Cookie가 빠지는지
  • 요청 Cookie 존재 여부: 다음 요청에 PHPSESSID가 올라오는지
  • 서버 저장소 상태: session.save_path에 파일이 생겼는지/바로 사라지는지(권한/청소/GC 의심)

여기서 “Set-Cookie는 정상인데 요청 Cookie가 없다”면 브라우저/도메인/정책, “요청 Cookie는 있는데 서버 값이 없다”면 저장소/권한/핸들러 쪽으로 거의 결정됩니다.

마무리

Windows에서 PHP session 문제가 생기면, 먼저 재현 조건을 고정하고(도메인/HTTPS/브라우저), 다음으로 쿠키 전달 → 서버 저장소 순서로 좁히는 게 가장 빠릅니다.

위 체크리스트대로 한 항목씩만 바꿔가며 확인하면, “가끔 끊김”도 대부분 원인(쿠키 정책/경로 권한/HTTPS 혼용)까지 무리 없이 도달합니다.