---
title: "30살 된 FastCGI가 아직도 리버스 프록시 백엔드 프로토콜로 더 낫다는 주장"
published: 2026-04-29T16:16:39.000Z
canonical: https://jeff.news/article/2003
---
# 30살 된 FastCGI가 아직도 리버스 프록시 백엔드 프로토콜로 더 낫다는 주장

HTTP를 리버스 프록시와 백엔드 사이 프로토콜로 쓰는 관행이 desync 공격과 신뢰 헤더 문제를 계속 만든다는 글이다. 저자는 FastCGI가 1996년 나온 오래된 프로토콜이지만 명시적 프레이밍과 신뢰 정보 분리 덕분에 이 구간에서는 HTTP보다 안전한 선택일 수 있다고 주장한다.

- 리버스 프록시와 백엔드 사이에 HTTP를 쓰는 게 너무 당연해졌지만, 이 글은 그 기본값이 꽤 위험하다고 찌름
  - 최근 Discord 미디어 프록시에서도 desync 취약점이 공개됐고, 이 취약점은 비공개 첨부파일을 엿볼 수 있는 수준이었다고 함
  - 저자는 이런 문제가 특이한 사고가 아니라 HTTP 리버스 프록시 구조에서 반복적으로 나오는 패턴이라고 봄

- 핵심 원인은 HTTP/1.1의 메시지 경계가 애매하다는 점임
  - HTTP/1.1은 겉으로는 텍스트라 단순해 보이지만, 같은 메시지도 여러 방식으로 표현할 수 있고 파서마다 해석이 달라질 수 있음
  - 메시지가 어디서 끝나고 다음 메시지가 어디서 시작되는지 프록시와 백엔드가 다르게 보면 request smuggling, 즉 HTTP desync 공격이 가능해짐
  - 보안 연구자 James Kettle은 이런 류의 파서 차이를 계속 찾아냈고, 결국 “HTTP/1.1 must die”라고까지 말했다고 함

> [!WARNING]
> HTTP desync는 단순 파서 버그 하나 고치는 문제로 끝나지 않음. 프로토콜 자체가 애매한 경계를 허용하면 구현체 차이가 계속 공격면이 됨.

- FastCGI는 1996년에 나온 30년 된 프로토콜이지만, 이 문제 하나만큼은 처음부터 더 깔끔하게 피하고 있었음
  - FastCGI는 프로세스 실행 모델이 아니라 wire protocol이라, 백엔드 데몬에 TCP나 유닉스 소켓으로 요청을 보내는 식으로 쓸 수 있음
  - Go에서는 `net/http/fcgi`를 import하고 `http.Serve`를 `fcgi.Serve`로 바꾸면 기존 `http.ResponseWriter`, `http.Request` 핸들러를 그대로 쓸 수 있다고 함
  - Apache, Caddy, nginx, HAProxy 모두 FastCGI 백엔드를 지원하고, 설정도 HTTP 프록시 설정과 크게 다르지 않음

- HTTP/2는 메시지 프레이밍을 명확히 해서 desync 문제를 줄이지만, 배포 현실은 그렇게 단순하지 않음
  - 프록시와 백엔드 사이에서 HTTP/2를 일관되게 써야 효과가 있는데, nginx는 첫 릴리스부터 FastCGI 백엔드를 지원한 반면 HTTP/2 백엔드 지원은 2025년 말에야 들어갔다고 함
  - Apache의 HTTP/2 백엔드 지원도 아직 “experimental” 상태라고 언급됨
  - 반대로 FastCGI는 1996년부터 명확한 요청 경계를 가진 더 단순한 프로토콜이었다는 게 저자의 포인트임

- 두 번째 문제는 프록시가 넣은 신뢰 정보를 HTTP 헤더로 전달하는 방식임
  - 실제 클라이언트 IP, 인증된 사용자명, mTLS 클라이언트 인증서 정보 같은 값은 백엔드가 믿어야 하는 데이터임
  - 그런데 HTTP에서는 이 값들을 결국 `X-Real-IP` 같은 헤더에 넣게 되고, 클라이언트가 보낸 헤더와 구조적으로 섞임
  - 프록시가 `X-Real-IP`를 지운다 해도 `True-Client-IP` 같은 다른 헤더를 미들웨어가 먼저 신뢰하면 공격자가 조작한 값이 백엔드까지 들어갈 수 있음

- FastCGI는 이 부분에서 클라이언트 헤더와 프록시 신뢰 정보를 구조적으로 분리함
  - 클라이언트가 보낸 HTTP 헤더는 `HTTP_` 접두사가 붙은 파라미터로 전달됨
  - 반면 실제 클라이언트 IP는 `REMOTE_ADDR` 같은 표준 파라미터로 전달되기 때문에, 클라이언트가 보낸 헤더가 신뢰 정보처럼 위장하기 어려움
  - Go의 `net/http/fcgi`는 `REMOTE_ADDR`를 자동으로 `http.Request.RemoteAddr`에 채워줘서 별도 미들웨어 없이도 동작한다고 함

> [!IMPORTANT]
> 저자가 보는 FastCGI의 진짜 장점은 속도가 아니라 보안 모델임. 요청 경계와 신뢰 데이터의 출처가 프로토콜 레벨에서 분리돼 있다는 게 포인트다.

- 물론 FastCGI가 만능은 아님. 30년 된 기술답게 단점도 선명함
  - WebSocket을 지원하지 않음
  - `curl`로 FastCGI 서버에 바로 요청을 보낼 방법도 없음. 저자는 curl이 FTP, Gopher, SMTP까지 지원하면서 FastCGI는 안 된다고 꼬집음
  - Go FastCGI 서버를 여러 리버스 프록시 뒤에서 벤치마크했을 때 일부 워크로드는 HTTP/1.1이나 HTTP/2보다 처리량이 낮았다고 함
  - 다만 저자는 이게 프로토콜 자체의 한계라기보다 FastCGI 코드 경로가 HTTP만큼 최적화되지 않았기 때문일 수 있다고 봄

- 결론은 꽤 현실적임. WebSocket이 필요 없고 성능 병목이 아니라면 FastCGI는 지금도 충분히 쓸 만하다는 것
  - 저자는 SSLMate에서 FastCGI를 10년 넘게 프로덕션에서 쓰고 있다고 밝힘
  - 병목이 생기면 HTTP 리버스 프록시의 보안 지뢰밭을 감당하기보다 차라리 하드웨어를 더 사겠다는 입장임

---

## 기술 맥락

- 여기서 선택지는 “프록시와 백엔드 사이도 HTTP로 말할 것인가, 아니면 FastCGI 같은 전용 프로토콜을 쓸 것인가”예요. 보통은 관성적으로 HTTP를 고르지만, 저자는 이 구간에서는 범용성보다 메시지 경계와 신뢰 정보 분리가 더 중요하다고 보는 거예요.

- HTTP/1.1이 위험해지는 이유는 요청의 끝을 메시지 내부 값으로 판단하기 때문이에요. 프록시와 백엔드가 `Content-Length`, `Transfer-Encoding` 같은 단서를 다르게 해석하면 같은 바이트 스트림이 서로 다른 요청으로 보일 수 있거든요.

- FastCGI는 요청을 레코드 단위로 나누고, 클라이언트 헤더와 프록시가 추가한 환경 정보를 다른 이름 공간으로 보냅니다. 그래서 `X-Real-IP`를 지웠는지, 대소문자 변형까지 처리했는지 같은 방어 로직에 덜 기대게 돼요.

- 트레이드오프도 분명해요. WebSocket이 필요하거나 HTTP 도구 생태계에 깊게 기대는 서비스라면 FastCGI 전환이 번거로울 수 있어요. 반대로 일반적인 요청-응답 백엔드이고 앞단 보안 경계가 중요하다면, 오래된 프로토콜이라는 이유만으로 제외할 선택지는 아니라는 얘기예요.

## 핵심 포인트

- HTTP/1.1은 메시지 경계가 애매해 리버스 프록시와 백엔드가 서로 다르게 파싱할 수 있고, 이게 request smuggling의 뿌리다.
- FastCGI는 요청 경계를 명확히 나누고, 클라이언트 헤더와 프록시가 넣은 신뢰 정보를 구조적으로 분리한다.
- nginx는 첫 릴리스부터 FastCGI 백엔드를 지원했지만, HTTP/2 백엔드 지원은 2025년 말에야 들어갔다.
- FastCGI는 WebSocket 미지원, 부족한 도구, 덜 최적화된 성능 같은 현실적인 단점도 있다.

## 인사이트

요지는 '오래된 기술이라 낡았다'가 아니라, 특정 문제를 풀기 위해 설계된 프로토콜이 범용 HTTP보다 나을 수 있다는 얘기다. 백엔드 앞단을 HTTP로만 붙이는 게 당연하다고 생각했다면 한 번쯤 아키텍처 기본값을 의심해볼 만하다.
