
이 글에서는 Nginx 리버스 프록시에서 SSE(Server-Sent Events) 응답이 브라우저까지 실시간으로 오지 않고 중간에 뭉쳐서 도착하는 문제를 어떤 설정으로 풀 수 있는지 확인하실 수 있습니다. 결론만 먼저 말씀드리면, 해당 location 블록에 proxy_buffering off, proxy_cache off, chunked_transfer_encoding off 이 세 줄을 넣는 것으로 대부분의 지연이 해결됩니다. 다만 SSE가 버퍼링될 때의 원인이 이 세 줄만으로 설명되지 않는 경우도 있어서, 아래에서 타임아웃·gzip·CDN 같은 예외 상황도 함께 짚어드리겠습니다.
Nginx 리버스 프록시에서 SSE 응답이 버퍼링되는 이유
Nginx는 proxy_buffering 값이 기본적으로 on으로 설정되어 있습니다. 이 값이 켜져 있으면 Nginx는 백엔드(WAS)가 보낸 응답을 곧바로 클라이언트에 넘기지 않고, proxy_buffer_size와 proxy_buffers로 정해진 메모리 버퍼에 어느 정도 채운 다음에야 클라이언트로 흘려보냅니다.
일반적인 HTML·JSON 응답에서는 이 방식이 오히려 유리합니다. 느린 클라이언트가 응답을 천천히 받아가더라도 백엔드 워커는 버퍼에 데이터를 넘긴 즉시 다음 요청을 처리할 수 있기 때문입니다. 문제는 SSE처럼 백엔드가 연결을 계속 열어두고 이벤트를 몇 초 간격으로 조금씩 흘려보내는 응답입니다. 이런 응답은 버퍼가 다 채워질 때까지 Nginx 안에 갇혀 있다가, 한참 뒤 한꺼번에 클라이언트로 넘어가는 현상이 나타납니다.
버퍼링을 끄는 설정 세 줄부터 적용해보세요
SSE를 서빙하는 location 블록에만 아래 세 줄을 추가합니다. 서버 전체가 아니라 해당 엔드포인트에만 좁게 적용하는 게 중요한데, 이유는 뒤에서 설명하겠습니다.
location /events/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_buffering off;
proxy_cache off;
chunked_transfer_encoding off;
}

각 줄의 역할은 다음과 같습니다.
proxy_buffering off— Nginx가 백엔드 응답을 버퍼에 모으지 않고, 받는 즉시 클라이언트로 전달합니다. SSE가 버퍼링될 때 가장 먼저 효과를 보는 지점입니다.proxy_cache off—proxy_cache_path로 캐시 존을 설정해둔 서버라면, SSE 같은 무한 스트림 응답이 캐시 로직에 걸려 완료를 기다리는 상황을 막아줍니다.chunked_transfer_encoding off— 백엔드가Content-Length를 주지 않는 스트리밍 응답에서 Nginx가 자체적으로 청크 인코딩을 다시 씌우는 동작을 끕니다. 프레임워크에 따라 이 줄 없이도 문제없는 경우가 있어, 앞의 두 줄로 해결되지 않을 때 추가로 시도해볼 항목으로 보시면 됩니다.
설정을 바꾼 뒤에는 nginx -t로 문법을 확인하고 nginx -s reload로 반영합니다. 별도 재시작 없이 무중단으로 적용됩니다.
연결이 끊기는 건 버퍼링과 다른 문제입니다
세 줄을 넣은 뒤 이벤트는 실시간으로 오는데, 일정 시간이 지나면 연결 자체가 끊어지는 경우가 있습니다. 이건 버퍼링이 아니라 proxy_read_timeout 문제입니다. 기본값이 60초라서, 백엔드가 60초 넘게 아무 바이트도 보내지 않으면 Nginx는 백엔드가 죽었다고 판단하고 연결을 닫아버립니다.
해결 방법은 두 가지입니다. 하나는 해당 location에 proxy_read_timeout 3600s;처럼 넉넉한 값을 주는 것이고, 다른 하나는 백엔드에서 주기적으로 하트비트를 보내는 것입니다. SSE 스펙은 콜론(:)으로 시작하는 줄을 주석으로 정의하고 있어서, 예를 들어 15~30초마다 : keep-alive\n\n 같은 줄을 흘려보내면 클라이언트는 무시하지만 Nginx 입장에서는 계속 데이터가 오는 것으로 인식해 타임아웃에 걸리지 않습니다. 실무에서는 타임아웃을 늘리는 방식보다 하트비트 쪽이 더 안정적인데, 타임아웃 값을 무작정 키우면 죽은 연결을 오래 붙잡고 있는 부작용이 생기기 때문입니다.
gzip·CDN·로드밸런서 같은 중간 계층의 예외
세 줄을 넣었는데도 지연이 남아 있다면 Nginx 앞뒤로 다른 계층이 끼어 있는지부터 확인해야 합니다. 아래 표는 흔히 놓치는 지점을 정리한 것입니다.

| 계층 | 버퍼링이 생기는 이유 | 조치 |
|---|---|---|
| Nginx gzip 모듈 | 압축 처리를 위해 데이터를 일부 모아두었다가 내보낼 수 있음 | 해당 location에 gzip off; 적용 |
| 클라우드 로드밸런서(ALB 등), CDN | Nginx와 별개로 자체 버퍼링·타임아웃 정책을 가짐 | 해당 서비스의 스트리밍/버퍼링 옵션을 별도로 확인 |
| 백엔드 프레임워크·WAS | 동기식 워커가 응답을 전부 모은 뒤에야 전송하는 구조일 수 있음 | 스트리밍 응답을 지원하는 워커·핸들러 방식으로 전환 |
특히 Nginx 앞에 또 다른 리버스 프록시나 CDN이 있는 구성이라면, Nginx의 proxy_buffering off는 Nginx 계층에서만 효과가 있을 뿐 그 앞단의 버퍼링까지 없애주지는 않습니다. 이 경우는 이 글에서 다루는 세 줄짜리 설정만으로는 해결되지 않는 대표적인 사례입니다.
proxy_buffering을 껐는데도 지연이 남아있다면?
이럴 때는 문제가 Nginx 구간인지, 백엔드 구간인지부터 나눠서 확인하는 게 빠릅니다. curl -N 옵션은 curl 자체의 출력 버퍼링을 끄기 때문에, 브라우저 개발자 도구보다 원인을 구분하기에 유용합니다.
# 1) 백엔드에 직접 요청 — 여기서도 늦게 오면 백엔드 문제
curl -N http://localhost:8000/events/
# 2) Nginx를 거쳐 요청 — 1번은 빠른데 2번이 느리면 Nginx 설정 문제
curl -N http://your-domain.com/events/
1번, 2번 모두 실시간으로 오는데 실제 브라우저에서만 지연이 느껴진다면, 그건 Nginx가 아니라 브라우저나 프런트엔드 코드에서 이벤트를 모아 렌더링하는 로직을 의심해볼 차례입니다. 참고로 proxy_buffering off를 http나 server 블록처럼 넓은 범위에 걸어두면, 느린 클라이언트가 백엔드 워커를 응답이 끝날 때까지 계속 붙잡고 있게 되어 동시 접속이 많은 환경에서는 오히려 백엔드 부하가 늘어날 수 있습니다. 그래서 SSE를 서빙하는 location 블록에만 좁게 적용하시는 걸 권해드립니다.
지금 적용해볼 수 있는 확인 순서
정리하면, SSE 엔드포인트 location에 proxy_buffering off; proxy_cache off; chunked_transfer_encoding off; 세 줄을 넣고 reload하는 것이 첫 조치입니다. 그다음 연결이 도중에 끊긴다면 proxy_read_timeout을 늘리거나 백엔드에서 하트비트를 보내도록 하고, 그래도 지연이 남으면 gzip과 앞단 CDN·로드밸런서의 버퍼링 옵션을 순서대로 점검해보시면 됩니다. 지금 서버의 Nginx 설정 파일을 열어 해당 location 블록을 찾은 뒤, 위 세 줄부터 먼저 넣고 curl -N으로 결과를 확인해보시길 권해드립니다.
