nginx에서 upstream sent too big header while reading response header from upstream 오류가 발생할 때 cover image

nginx에서 upstream sent too big header while reading response header from upstream 오류가 발생할 때

김재동 • July 21, 2019

laravel infrastructure xpressengine

최근 XE의 스토리지 병목현상을 해소하기 위해 스토리지에서 파일을 직접 불러와서 XE에서 다운로드를 처리하는 대신 XE에 첨부파일이 추가/수정/삭제되면 Laravel의 Queue를 통해 S3로 파일을 동기화하고 다운로드는 Laravel쪽에서 AWS CloudFront의 Signed Url로 리다이렉트 해서 처리하도록 하였다. 그런데 프로덕션 서버에 이 방식을 도입한 첫 날, 다운로드가 되지 않는다는 제보가 갑자기 많이 들어왔다. 그래서 로그를 살펴보니 일부 요청이 502 상태를 반환하고 있었고 nginx 에러 로그에는 아래와 같은 내용이 있었다.


2019/07/08 00:00:00 [error] nnnn#0: *nnnnn upstream sent too big header while reading response header from upstream, client: x.x.x.x, server: hapjeong.indischool.com, request: "GET /files?xxxx HTTP/1.1", upstream: "fastcgi://127.0.0.1:9000", host: "hapjeong.indischool.com", referrer: "https://www.indischool.com/index.php?xxxx"

오류 내용으로 구글링을 해보니 Response의 Header 크기가 nginx의 설정값보다 크면 502를 반환하게 되기 때문에 fastcgi_buffers와 fastcgi_buffer_size를 조절하면 된다는 내용이었다. 그런데 신기한 것은 이 답글의 내용보다 이 답변에 달린 댓글의 점수가 굉장히 높다는 것이었다.

그래서 댓글의 링크를 타고 들어갔더니 해당 nginx 옵션에 대해 상세히 설명하고 있었다.

막연히 설정 값을 늘리기보다는 access.log를 분석해서 response 크기의 최대/평균 값을 보고 판단할 수 있도록 아래와 같은 명렁어를 제시했다.

awk '($9 ~ /200/) { i++;sum+=$10;max=$10>max?$10:max; } END { printf("Maximum: %d\nAverage: %d\n",max,i?sum/i:0); }' access.log

fastcgi_buffers에서 앞의 숫자는 세그먼트의 갯수이고 뒤는 메모리 크기인데 기본값은 다음과 같다.

fastcgi_buffers 8 4k|8k;

4Kb 또는 8Kb 사이즈의 버퍼 8개를 설정한다는 의미인데 뒤쪽의 버퍼 메모리 크기는 기본적으로 운영체제의 메모리 페이지 크기를 따르게 된다. Debian이나 Ubuntu 계열 리눅스의 경우 4k(4096 바이트)가 된다. 이 글을 쓴 사람은 버퍼의 사이즈는 가능하면 운영체제의 기본 페이지 값으로 그대로 두고 앞의 세그먼트 숫자를 조절한다고 했다. 앞서 access.log를 분석해서 얻은 response size의 최대/평균값을 바탕으로 적절하게 설정하면 된다.

그런데 오류가 발생한 원인은 fastcgi_buffers 때문이 아니라 fastcgi_buffer_size 때문이었다. fastcgi_buffer_size는 FastCGI response의 첫 번째 chunk를 저장하는 특별한 버퍼라고 되어있는데 바로 HTTP response header가 저장되는 공간이다. 보통의 경우에는 이 값 또한 운영체제의 메모리 페이지 크기(4/8K)로 설정해도 큰 문제는 없지만 사용자 인증을 위해 쿠키를 사용하는 일부 프레임워크는 이 크기로 해결이 안 되는 경우도 있다는 것이었다. 이런 경우에는 8k/16k/32k 등으로 상황에 맞게 fastcgi_buffer_size를 조절할 필요가 있다.

인디스쿨의 경우 이 버퍼 사이즈의 값이 2k로 되어 있었기 때문에 오류가 더 쉽게 발생했다. 그런데 어째서 response header가 2Kb를 넘는 걸까 싶어서 오류가 발생한 요청의 헤더를 한 번 살펴봤다. 이 헤더의 값은 실제로 2048 byte를 초과했는데 이렇게 헤더가 커지게 된 가장 큰 원인은 파일 다운로드를 위해 CloudFront의 Signed Url을 이용해서 URL이 엄청 길어졌기 때문이다. 그 외에 Laravel의 CSRF Token, 세션 쿠키 값도 한 몫을 했다.

fastcgi_buffer_size의 값을 16k로 변경하고 이 문제는 간단히 해결되었다.