특정 기기에서만 503 에러 나던 CORS 문제 해결

Hannana·2025년 10월 2일

문제 상황

어느 날 CS가 들어왔다. 잘 돌아가는 서비스에서 503 에러가 난다고 하시는 것이었다.

503 에러 ?? 서버는 멀쩡했고 팀원들 모두 문제 없이 잘 돌아갔는데
로그인부터 막히신다고 하니 당황스러웠다.
로그를 체크해봐도 별 다른 흔적이 없어 의문이 들었다.

원인

최근 애플리케이션 레벨에서 이루어지던 CORS 설정을 서버의 nginx 설정으로 모두 이관했다.

서버에 문제 없고, 애플리케이션 단의 문제가 아님을 확인했으니 추측이 가는 건 라우팅 쪽의 문제 밖에 없었다. Nginx 설정을 다시 뜯어보기로 했다.

Preflight 요청의 함정

브라우저는 본 요청 전에 OPTIONS 요청을 먼저 보낸다. 서버와 통신이 제대로 이루어지는지 체크하기 위함이다.

OPTIONS /api/users
Access-Control-Request-Headers: authorization, content-type, x-requested-with, x-client-version

문제는 이 헤더 목록이 기기/브라우저/앱 버전마다 다르다는 것이다.

기존 Nginx 설정은 아래처럼 허용 헤더를 고정으로 박아뒀다.

  add_header 'Access-Control-Allow-Headers'
    'Authorization, Content-Type, Cache-Control, Pragma, Expires, 
     X-Authenticated-User-Id, X-Authenticated-User, X-Branch-Ids, X-Invite-Token' always;

특정 기기가 여기 없는 헤더(예: x-requested-with)를 요구하면 프리플라이트 단계에서 차단돼서 본 요청이 아예 안 가는 거였다.

이게 원인이었다. 고정된 헤더 목록으로는 모든 기기/브라우저를 대응할 수 없었던 것이다.

해결

기존 설정

  if ($request_method = OPTIONS) {
    add_header 'Access-Control-Allow-Origin'  $cors_origin always;
    add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
    add_header 'Access-Control-Allow-Headers'
      'Authorization, Content-Type, Cache-Control, Pragma, Expires, 
       X-Authenticated-User-Id, X-Authenticated-User, X-Branch-Ids, X-Invite-Token' always;
    return 204;
  }

수정한 설정

  if ($request_method = OPTIONS) {
    add_header Access-Control-Allow-Origin   $cors_origin always;
    add_header Access-Control-Allow-Methods  "GET,POST,PUT,DELETE,OPTIONS" always;
    //여기, 클라이언트가 요청한 헤더를 그대로 허용
    add_header Access-Control-Allow-Headers  "$http_access_control_request_headers, Authorization, Content-Type" always;
    //여기
    add_header Vary "Origin, Access-Control-Request-Headers" always;
    return 204;
  }
//본 요청
  add_header Access-Control-Allow-Origin  $cors_origin always;
  add_header Access-Control-Allow-Methods "GET,POST,PUT,DELETE,OPTIONS" always;
  add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;
  //여기
  add_header Vary "Origin" always;

핵심은 $http_access_control_request_headers를 쓰는 것이다.
이 옵션은 클라이언트가 요구하는 헤더를 동적으로 허용해주고,
추가로 Vary 헤더는 캐시로 인한 간헐적 오류를 방지한다.

캐시로 인한 간헐적 오류?!

프록시나 브라우저, 중간 캐시가 Origin이 다른 응답을 재사용하면 A 오리진용 응답이 B 오리진에도 쓰여서 간헐적인 CORS 에러가 발생한다. Nginx에서 헤더를 항상 붙이면 이런 캐시 동작을 통제할 수 있다.

배운 점

Nginx로 CORS를 제어하는 건 나쁜 선택이 아니다. 오히려 중앙 집중화된 설정으로 일관성을 높일 수 있고, 캐시 문제도 통제할 수 있다.

다만 고정 목록으로 허용 헤더를 관리하면 기기마다 요구하는 헤더가 다르기 때문에 이처럼 잘 돌아가는 것처럼 보이는 서비스도 난데없이 접속이 안되는 상황이 발생하게 된다. 그러니 동적 허용은 꼭 신경 쓰자..

참고

profile
성장하는 하루를 쌓아가는 블로그

0개의 댓글