Quick Flow
requestTimeout은 handler 실행 시간이 아니라 클라이언트가 요청 전체를 보내는 제한입니다. graceful shutdown은 새 연결을 먼저 막고 유휴 연결을 정리한 뒤, active 요청에 deadline을 주고 마지막에만 강제로 끊습니다.
import { createServer } from "node:http";
const server = createServer((req, res) => res.end("ok"));
server.requestTimeout = 30_000; // 요청 전체 수신 제한
server.headersTimeout = 10_000; // 헤더 전체 수신 제한
server.keepAliveTimeout = 5_000; // 응답 뒤 유휴 연결 대기
server.listen(3000);
let shuttingDown = false;
process.on("SIGTERM", () => {
if (shuttingDown) return;
shuttingDown = true;
let finished = false;
const forceClose = setTimeout(() => {
if (!finished) server.closeAllConnections();
}, 10_000);
forceClose.unref();
server.close((error) => {
finished = true;
clearTimeout(forceClose);
process.exitCode = error ? 1 : 0;
});
server.closeIdleConnections(); // Node 18.2+; Node 19+에서는 close()가 처리
});연결 관리
먼저 구분해 둘 기본형은 아래입니다.
- 요청 전체 제한:
requestTimeout - 헤더 수신 제한:
headersTimeout - 유휴 keep-alive 제한:
keepAliveTimeout - 새 연결 차단:
server.close() - 남은 연결 정리:
closeAllConnections(), 필요 시closeIdleConnections()
requestTimeout
클라이언트가 요청 전체(헤더와 본문)를 보내기까지 허용할 최대 시간입니다. handler 실행, 데이터베이스 호출, 응답 전송 시간은 이 값이 다루지 않으므로 느린 업로드·슬로우 클라이언트 방어 기준으로 봅니다.
headersTimeout
헤더만 질질 보내는 연결을 빨리 잘라내는 값입니다. 보통 requestTimeout보다 짧게 둡니다.
keepAliveTimeout
응답을 보낸 뒤 다음 요청을 기다리는 유휴 시간입니다. 짧으면 재사용 이점이 줄고, 길면 종료 시 프로세스가 오래 남습니다.
즉:
- 요청 자체가 너무 오래 간다:
requestTimeout - 헤더만 늦다:
headersTimeout - 응답 뒤 연결이 너무 오래 남는다:
keepAliveTimeout
종료 시 가장 먼저 보는 문제는 keep-alive다
server.close()는 새 연결만 막고 기존 연결은 기다립니다. 그런데 keep-alive 유휴 연결은 요청이 없어도 살아 있으니, 종료 콜백이 생각보다 늦게 옵니다. 배포 중 "서버가 안 죽는다"는 문제는 이 지점이 많습니다.
Node 18.2+의 server.closeIdleConnections()는 유휴 keep-alive 연결만 정리합니다. Node 19+에서는 server.close()가 유휴 연결도 정리하므로 보통 별도 호출이 필요하지 않습니다. server.closeAllConnections()는 active 요청과 응답도 끊는 강제 수단이므로 deadline 뒤에만 씁니다.
process.on("SIGTERM", () => {
server.close(() => process.exit(0));
server.closeIdleConnections();
});반대로 server.close()만 호출하고 keep-alive 연결을 그대로 두면, "종료 중인데 왜 프로세스가 계속 살아 있지?"라는 상황이 생기기 쉽습니다.
종료는 신호 처리보다 순서 고정이 중요하다
SIGTERM을 받았다고 바로 process.exit() 해 버리면 응답 중인 요청, 로그 flush, DB 정리가 중간에 잘릴 수 있습니다.
그래서 shutdown 카드는 "무슨 시그널을 받는가"보다 "새 연결 차단 -> 연결 정리 -> 리소스 정리 -> 종료" 순서를 고정하는 카드에 가깝습니다.
종료 순서를 코드로 고정한다
실전에서는 아래 순서를 기억하면 됩니다.
- SIGTERM 수신
- 새 연결 차단
- keep-alive 연결 정리
- DB, 큐, 로그 플러시 같은 리소스 정리
- 안전망 타이머 안에 종료
종료 로직이 여러 파일에 흩어져 있으면 누락되기 쉬우니 한 함수로 모으는 편이 낫습니다.
어디서 끊을까
체크포인트
- 느린 요청 제한:
requestTimeout - 헤더만 느린 연결 제한:
headersTimeout - 유휴 연결 재사용 시간:
keepAliveTimeout - 새 연결 차단:
server.close() - 유휴 keep-alive 정리:
server.closeIdleConnections() - idle 연결만 먼저 정리한다:
server.closeIdleConnections() - 종료 안전망:
setTimeout(...).unref()
주의할 점
server.closeAllConnections()는 active 응답까지 즉시 끊습니다. 정상 종료에서는 close()로 새 연결을 막고 active 요청을 drain하며, deadline이 지난 경우에만 강제 종료 안전망으로 호출합니다.
참고 링크
2 sources