Cloudflare 앞단에 Nginx를 두고 운영하다 보면 “Chrome은 괜찮은데 Safari에서만 로그인 유지가 자꾸 풀린다”, “결제/마이페이지에서 갑자기 비로그인으로 튕긴다” 같은 세션 이슈가 종종 터집니다. 이 글은 운영 관점에서, 쿠키가 실제로 어디서 깨지는지(브라우저/Cloudflare/Nginx/앱)를 징후→원인→해결→재검증 순서로 정리합니다.
특히 Safari는 ITP(Intelligent Tracking Prevention)와 쿠키 정책이 까다로워, “원인은 쿠키인데 증상은 캐시처럼 보이는” 상황이 자주 생깁니다.
문제 징후: Safari에서만 세션이 사라지는 전형적인 패턴
아래 중 하나라도 해당하면 “쿠키 저장/전송” 또는 “캐시가 쿠키 응답을 덮어씀” 이슈를 먼저 의심하는 게 빠릅니다.
- Safari에서만 로그인 직후엔 되는데, 몇 분/몇 페이지 후 비로그인으로 바뀐다
- 특정 경로(/account, /checkout 등)에서만 갑자기 세션이 끊긴다
- 로그인 요청(POST /login)은 200인데 다음 요청에서 세션 쿠키가 없다
- 서브도메인(www ↔ api, app ↔ www) 이동할 때만 풀린다
- Cloudflare 캐시를 켠 뒤부터 재현 빈도가 늘었다
원인 1) SameSite/Secure 조합 불일치: Safari에서 특히 민감
요즘 브라우저는 기본적으로 쿠키를 더 엄격하게 다룹니다. 운영에서 가장 흔한 실수는 다음 두 가지입니다.
- SameSite=None을 쓰면서 Secure를 빼먹음(HTTPS에서만 허용)
- 서드파티/크로스사이트 흐름(예: 외부 결제, SSO)인데 SameSite=Lax/Strict로 막힘
Safari는 상황에 따라 쿠키를 더 보수적으로 처리해, “다른 브라우저에서는 운 좋게 되던 것”이 Safari에서 먼저 깨지는 경우가 있습니다.
원인 2) Domain/Path 설정 실수: www, apex, api 분리에서 자주 발생
Cloudflare를 쓰면 리다이렉트(www→apex, http→https)가 정리되면서 도메인 경계가 더 선명해집니다. 이때 쿠키 Domain/Path가 의도와 다르면 Safari에서 더 쉽게 “없는 쿠키”가 됩니다.
- 로그인은 www.example.com에서 했는데 쿠키 Domain이 example.com이 아니라 www.example.com로 박혀, apex로 이동하면 사라짐
- 쿠키 Path가 /auth로 제한되어 다른 페이지 요청에 동봉되지 않음
- API가 api.example.com인데 쿠키가 메인 도메인에만 설정되어 API 호출에 안 붙음(반대도 가능)
운영 팁으로는 “로그인/세션 쿠키는 가능한 한 도메인 전략을 단순화”하고, www/apex를 한쪽으로 강제하는 게 재발을 줄입니다.
원인 3) Cloudflare 캐시가 Set-Cookie 응답을 덮어쓰는 경우
Cloudflare 캐시 자체는 기본적으로 Set-Cookie가 있는 응답을 조심스럽게 다루지만, Page Rules / Cache Rules / Workers 등으로 커스터마이징하면 “로그인/세션 관련 응답이 캐시됨” 같은 사고가 생깁니다.
대표 증상은 이렇습니다.
- 첫 로그인 때는 되는데, 이후 다른 사용자의 캐시된 응답을 받는 것처럼 동작
- Safari에서만 재현(캐시 히트 타이밍, 프리로드/프리패치 차이로 더 잘 드러남)
- 응답 헤더에 cf-cache-status: HIT가 찍히는 페이지가 로그인/마이페이지
쿠키를 세팅하는 응답(로그인, 토큰 갱신, 세션 연장)이나 사용자별 콘텐츠는 캐시 계층에서 “절대 캐시하지 않는다”를 강하게 보장해야 합니다.
해결: Nginx 헤더/캐시 정책을 세션 중심으로 재정렬
아래는 실전에서 효과가 컸던 조합입니다. (애플리케이션 프레임워크별 쿠키 세팅은 다르지만, 운영 레이어에서 막을 수 있는 실수들이 꽤 많습니다.)
- 로그인/세션 관련 경로는 캐시 금지: Nginx에서 Cache-Control을 명확히 하고, Cloudflare에서도 Bypass 규칙을 둔다
- Set-Cookie가 나오는 응답은 공유 캐시에서 배제: private, no-store 권장
- HTTPS 강제 후 Secure 일관성: http 접근이 남아 있으면 Secure 쿠키가 저장/전송되지 않아 “간헐적”으로 보일 수 있음
- 도메인 정규화: www↔apex를 한 곳으로 고정하고, 쿠키 Domain 정책을 그에 맞춘다
예시로, 로그인/세션 경로에 대해 Nginx에서 다음처럼 “캐시 불가”를 명시해 두면 Cloudflare 앞에서도 사고 확률이 확 줄어듭니다(환경에 맞게 location은 조정하세요).
- /login, /logout, /session, /account 등은 Cache-Control: no-store, private
- HTML 응답이라도 사용자별이면 캐시하지 않기
또한 프록시 환경에서는 앱이 실제로 HTTPS로 접속됐는지 판단을 잘못해 Secure 쿠키를 누락하는 경우가 있습니다. 이때는 Nginx와 앱 사이에서 X-Forwarded-Proto 전달, 앱의 “trust proxy” 설정(프레임워크별)을 재확인하세요.
재검증: Safari 기준으로 “쿠키가 저장되고 전송되는지”만 확인
재검증은 복잡하게 하지 말고, 아래 체크리스트만 통과시키면 대부분 끝납니다.
- Safari에서 로그인 응답에 Set-Cookie가 기대값대로 내려오는지 확인
- 그 다음 페이지 요청에 해당 쿠키가 Cookie 헤더로 실제로 붙는지 확인
- 문제 페이지 응답의 cf-cache-status가 BYPASS/DYNAMIC인지 확인(로그인/계정 페이지는 HIT가 나오면 위험 신호)
- www↔apex, api 서브도메인 이동 시에도 쿠키가 유지되는지 확인
- HTTP로 한번이라도 접속될 여지가 없는지(리다이렉트/서브도메인 포함) 확인
운영 중이라면 “재현되는 단말 Safari + 문제가 나는 경로”를 고정해 두고, 변경 전/후에 동일 조건으로만 비교하세요. 캐시/쿠키는 조건이 조금만 바뀌어도 증상이 흔들려서, 테스트가 헛도는 경우가 많습니다.
마무리
Safari에서만 세션이 풀릴 때는 앱 버그로 단정하기 전에, 쿠키의 SameSite/Secure/Domain/Path와 캐시 정책(Cloudflare 포함)을 먼저 “세션 중심”으로 재정렬하는 게 가장 빠릅니다.
징후가 애매해 보여도, 결국은 “Set-Cookie가 내려오나 → Safari가 저장하나 → 다음 요청에 붙나 → 중간 캐시가 덮지 않나” 네 단계로 좁히면 대부분 수습됩니다.