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

503 에러 ?? 서버는 멀쩡했고 팀원들 모두 문제 없이 잘 돌아갔는데
로그인부터 막히신다고 하니 당황스러웠다.
로그를 체크해봐도 별 다른 흔적이 없어 의문이 들었다.
최근 애플리케이션 레벨에서 이루어지던 CORS 설정을 서버의 nginx 설정으로 모두 이관했다.
서버에 문제 없고, 애플리케이션 단의 문제가 아님을 확인했으니 추측이 가는 건 라우팅 쪽의 문제 밖에 없었다. Nginx 설정을 다시 뜯어보기로 했다.
브라우저는 본 요청 전에 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를 제어하는 건 나쁜 선택이 아니다. 오히려 중앙 집중화된 설정으로 일관성을 높일 수 있고, 캐시 문제도 통제할 수 있다.
다만 고정 목록으로 허용 헤더를 관리하면 기기마다 요구하는 헤더가 다르기 때문에 이처럼 잘 돌아가는 것처럼 보이는 서비스도 난데없이 접속이 안되는 상황이 발생하게 된다. 그러니 동적 허용은 꼭 신경 쓰자..
참고