HTTP 421 Misdirected Request: 재사용한 HTTP/2 연결이 엉뚱한 서버에 닿을 때
HTTP/2는 연결 하나로 여러 오리진의 요청을 보낼 수 있지만, 서버는 그래도 421로 거절할 수 있다. 권한, 연결 재사용, 새 연결로의 재시도에 관해 RFC 9110과 RFC 9113이 정한 내용을 정리했다.
인증서도 유효했고 TLS 핸드셰이크도 성공했는데 응답이 421 Misdirected Request로 온다. 서버가 하는 말은 리소스가 없다는 것도, 권한이 없다는 것도 아니다. 이 요청이 잘못된 연결로 들어왔다는 뜻이다. 왜 이런 일이 생기는지 이해하려면 HTTP/2 클라이언트가 언제 여러 호스트 이름의 요청을 연결 하나로 보낼 수 있는지부터 알아야 한다.
이 글은 RFC 9110(HTTP Semantics, 2022년 6월)의 4.3절, 7.2절, 7.3절, 7.4절, 9.2.2절, 15.5.20절, 17.1절, 부록 B.3과 RFC 9113(HTTP/2, 2022년 6월)의 8.3.1절, 9.1절, 9.1.1절, 10.1절, 부록 B를 기준으로 한다. 브라우저, HTTP 라이브러리, CDN, 로드 밸런서가 실제로 언제 연결을 재사용하고 언제 421을 보내는지는 제품과 버전, 설정에 따라 다르므로 다루지 않는다. RFC 규칙이 아니라 이 글의 제안인 부분은 따로 밝혀 둔다.
421이 뜻하는 것
RFC 9110 15.5.20절의 정의는 이렇다. 421은 요청이 대상 URI에 대해 권한 있는(authoritative) 응답을 만들 수 없거나 만들 의사가 없는 서버로 향했다는 뜻이다. 오리진 서버, 또는 오리진 서버를 대신하는 게이트웨이가 다음 두 경우에 보낸다.
- 대상 URI가 서버에 설정된 어떤 오리진과도 맞지 않을 때(4.3.1절)
- 대상 URI가 요청을 받은 연결의 맥락(connection context)과 맞지 않을 때(7.4절)
같은 절에는 규칙이 두 개 있다.
- 421을 받은 클라이언트는 메서드가 멱등이든 아니든 다른 연결로 요청을 다시 보낼 수 있다(MAY). 예로 든 것은 대상 리소스의 오리진 전용으로 새로 연 연결, 또는 대체 서비스(alternative service)다.
- 프록시는 421 응답을 생성해서는 안 된다(MUST NOT).
즉 421은 리소스에 대한 판정이 아니라 요청과 연결의 조합에 대한 판정이다. RFC 9110 부록 B.3도 다른 쪽에서 같은 말을 한다. 421은 처음에 RFC 7540(최초의 HTTP/2)에서 정의됐고, 더 넓게 적용되는 코드라서 핵심 의미론 문서로 옮겨졌다. 이때 휴리스틱 캐시 대상에서도 빠졌는데, 이 응답은 대상 리소스가 아니라 연결에 특정된 것이기 때문이다.
오리진과 권한
규칙이 기대는 용어는 오리진이다. RFC 9110 4.3.1절은 URI의 오리진을 스킴, 호스트, 포트의 세 값으로 정의한다. 스킴과 호스트는 소문자로, 포트는 앞자리 0을 뺀 형태로 정규화하고, 포트가 생략되면 스킴의 기본 포트를 쓴다. 그래서 https://Example.Com/happy.js의 오리진은 { "https", "example.com", "443" }이다. 스킴, 호스트, 포트 중 하나라도 다르면 서로 다른 오리진이다. 같은 주체가 두 오리진을 모두 관리하더라도, 그 오리진에 권한 있는 서버가 명시적으로 별칭을 두지 않는 한 두 이름공간은 별개다.
권한(authority)은 어떤 서버가 그 오리진을 대신해 응답해도 되는가의 문제다. https에서는 클라이언트가 그 오리진에 대해 신뢰하는 인증서의 개인 키를 서버가 사용할 수 있는지에 근거한다(4.3.3절). 4.3.4절에 따르면 클라이언트는 인증서의 신원이 URI의 오리진 서버와 받아들일 만하게 일치하는지 검증해야 한다(MUST). 이름 비교 자체는 TLS 인증서 이름 불일치에서 다뤘다.
4.3.3절은 이어서 이 글에서 중요한 선을 긋는다.
- HTTP/1.1 이하에서 클라이언트는 그 URI 오리진의 호스트로 직접 맺은 보안 연결에서만 서버에 권한을 인정한다.
- HTTP/2와 HTTP/3에서 클라이언트는 이미 맺어진 보안 연결이라도, URI 오리진의 호스트가 서버 인증서에 있는 호스트 중 하나와 일치하고 그 URI를 위해 그 호스트로 연결을 열 수 있다고 판단하면 서버에 권한을 인정한다.
RFC는 실제로는 클라이언트가 DNS 질의를 해서 오리진의 호스트가 이미 맺은 연결과 같은 서버 IP 주소를 포함하는지 확인한다고 덧붙인다. 또 오리진 서버가 그에 상응하는 ORIGIN 프레임(RFC 8336)을 보내면 이 제약을 없앨 수 있다고 적는다. RFC 8336은 이 글의 출처에 포함되지 않으므로 ORIGIN 프레임은 더 다루지 않는다.
HTTP/2가 연결 하나를 여러 오리진에 쓰는 조건
RFC 9113 9.1절은 HTTP/2 연결이 지속형이라는 전제에서 출발한다. 클라이언트는 같은 호스트와 포트 쌍에 HTTP/2 연결을 두 개 이상 열지 않아야 한다(SHOULD NOT). 여기서 호스트는 URI, 선택된 대체 서비스, 또는 설정된 프록시에서 나온다. 같은 IP 주소와 TCP 포트라도 SNI(Server Name Indication) 값이나 TLS 클라이언트 인증서가 다르면 연결을 여러 개 열 수 있지만(MAY), 같은 구성으로 여러 연결을 만드는 일은 피해야 한다(SHOULD).
9.1.1절(Connection Reuse)은 호스트 이름이 다른 요청끼리 연결을 공유하는 조건을 정한다.
- 오리진 서버로 직접, 또는 CONNECT로 만든 터널을 거쳐 맺은 연결은 URI의 authority 구성 요소가 서로 다른 요청에 재사용할 수 있다(MAY). 오리진 서버가 권한을 가지는 한 재사용할 수 있다(10.1절).
- TLS 없는 TCP라면 그 호스트가 같은 IP 주소로 해석됐는지에 달려 있다.
https리소스라면 URI의 호스트에 대해 유효한 인증서가 있어야 한다는 조건이 더 붙는다. 서버가 제시한 인증서는 클라이언트가 그 호스트로 새 TLS 연결을 맺을 때 하는 모든 검사를 통과해야 한다(MUST). 인증서 하나로 여러 오리진에 대한 권한을 세울 수 있다.
RFC 9113 부록 B는 이 개념이 421 상태 코드와 함께 이제 RFC 9110에 들어갔다고 적고, 이를 connection coalescing(연결 병합)이라고 부른다.
인증서가 유효해도 부족한 이유
9.1.1절은 문제를 직접 적는다. 어떤 배포 환경에서는 연결 하나를 여러 오리진에 재사용하면 요청이 엉뚱한 오리진 서버로 갈 수 있다. 예로 드는 것은 TLS 종단을 맡은 미들박스가 SNI 확장으로 오리진 서버를 고르는 경우다. 그러면 서버가 다른 면에서는 권한을 가졌더라도, 클라이언트는 그 요청의 의도된 대상이 아닐 수 있는 서버로 요청을 보낼 수 있다.
이 상황을 요청 흐름으로 그리면 다음과 같다. 이름과 주소는 설명을 위한 예다.
Certificate SAN: a.example.com, b.example.com
DNS: a.example.com -> 192.0.2.10
b.example.com -> 192.0.2.10
client TLS terminator (192.0.2.10) backends
|-- TLS, SNI=a.example.com -->|-- route by SNI ------------> pool A
|-- :authority=a.example.com |---------------------------> pool A 200
|
| b.example.com: same cert, same IP -> client reuses the connection
|-- :authority=b.example.com |---------------------------> pool A
|<---------------------------- 421 Misdirected Request ---- pool A (not configured for b)
|
|-- new TLS, SNI=b.example.com -->|-- route by SNI --------> pool B
|-- :authority=b.example.com |-----------------------> pool B 200
클라이언트 쪽에서 보면 모든 조건이 맞는다. 인증서가 b.example.com을 포함하고 주소도 같으니 RFC 9113 9.1.1절의 재사용 조건을 충족한다. 그런데 종단 장비는 이 연결의 목적지를 핸드셰이크 때 SNI를 보고 한 번 정했으므로, b에 대한 요청도 풀 A로 보낸다. 풀 A가 b를 처리하도록 설정되어 있지 않다면, 15.5.20절이 말하는 바로 그 경우, 즉 대상 URI에 대해 권한 있는 응답을 만들 수 없는 상태다. 9.1.1절이 그 출구를 정해 둔다. 클라이언트가 연결을 재사용하기를 원하지 않는 서버는 그 요청에 421로 응답해 자신이 권한을 갖지 않는다고 알릴 수 있다.
RFC 9110 4.3.3절은 이 책임을 오리진 쪽에 둔다. 인증서 개인 키에 대한 통제권을 받은 모든 서비스는 대응하는 https 이름공간을 똑같이 책임져야 하고, 적어도 잘못 전달된 것으로 보이는 요청을 거절할 준비는 되어 있어야 한다(7.4절).
서버가 확인하는 것: 대상 URI와 연결의 맥락
RFC 9110 7.4절은 요청의 대상 URI를 알 수 있을 만큼 파싱한 서버가 무엇을 결정하는지 설명한다. 직접 처리할 수도, 다른 서버로 전달할 수도, 다른 리소스로 리다이렉트할 수도, 오류로 응답할 수도, 연결을 끊을 수도 있다. 판단에는 요청이나 연결에 관한 무엇이든 쓸 수 있지만, 핵심은 서버가 그 대상 URI를 처리하도록 설정되어 있는지, 그리고 연결의 맥락이 그 요청에 적합한지다.
- 잘못 전달된 요청의 예로 Host 헤더 필드의 정보가 연결의 호스트나 포트와 다른 경우를 든다. 신뢰하는 게이트웨이에서 온 연결이라면 예상할 수 있는 일이지만, 그렇지 않다면 보안 필터 우회, 비공개 콘텐츠 유출, 캐시 오염을 노린 시도일 수 있다.
- 신뢰하는 게이트웨이에서 온 연결이 아니라면, 오리진 서버는 대상 URI의 스킴별 요건을 충족하지 않는 요청을 거절해야 한다(MUST). 특히
https리소스에 대한 요청은 그 대상 URI의 오리진에 대해 유효한 인증서로 보호된 연결로 들어온 것이 아니면 거절해야 한다(MUST). - 421 응답은 요청이 잘못 전달된 것으로 보여 오리진 서버가 거절했다는 뜻이다.
4.3.3절은 포트에 관한 예도 든다. 한 호스트가 443과 8000에서 서로 다른 서비스를 운영한다면, 연결이 보호된 뒤에도 오리진 서버는 대상 URI를 확인해야 한다. 네트워크 공격자가 한 포트로 향한 연결을 다른 포트에서 받게 만들 수 있기 때문이다. 이 확인이 없으면 https://example.com/foo에 대한 응답이 https://example.com:8000/foo의, 겉보기에는 권한 있는 응답으로 바뀔 수 있다.
HTTP/2에서 요청의 authority는 :authority 의사 헤더 필드로 전달된다(RFC 9113 8.3.1절, RFC 9110 7.2절). :authority가 있으면 수신자는 대상 URI를 정할 때 Host 헤더 필드를 쓰면 안 된다(MUST NOT). 클라이언트는 :authority와 다른 Host 헤더 필드를 생성하면 안 되고(MUST NOT), 서버는 두 값이 서로 다른 대상을 가리키면 그 요청을 형식 오류로 취급해야 한다(SHOULD). 따라서 서버가 연결과 비교하는 대상 URI는 :authority에서 온다.
클라이언트의 재시도: 허용되는 것과 아닌 것
핵심 규칙은 15.5.20절의 MAY다. 421을 받은 클라이언트는 메서드의 멱등성과 관계없이 다른 연결로 다시 시도할 수 있다. 잘못 읽기 쉬운 지점이 몇 가지 있다.
- MUST가 아니라 MAY다. RFC는 재시도를 허용할 뿐 요구하지 않는다.
- 재시도는 다른 연결로 한다. 방금 421을 돌려준 그 연결에 같은 요청을 다시 보내는 것은 이 규칙이 허용하는 내용이 아니다. RFC가 든 예는 대상 리소스의 오리진 전용으로 새로 연 연결, 또는 대체 서비스다.
- 15.5.20절은 비멱등 메서드까지 허용하는 이유를 밝히지 않는다. 421을 오리진 서버가 잘못 전달된 요청을 거절한 것으로 설명하는 7.4절과는 맞아떨어지지만, 요청이 아무 효과도 내지 않았다고 RFC가 명시하지는 않는다. 421이 아닌 경우에는 RFC 9110 9.2.2절의 일반 규칙이 적용된다. 클라이언트는 요청의 의미가 실제로 멱등임을 알거나 원래 요청이 적용되지 않았음을 감지할 수단이 없는 한, 비멱등 요청을 자동으로 재시도하지 않아야 한다(SHOULD NOT). 메서드 전반의 규칙은 HTTP 메서드의 안전성과 멱등성에서 다뤘다.
- 15.5.20절의 허용은 “클라이언트”를 주어로 쓰였다. 프록시는 비멱등 요청을 자동으로 재시도해서는 안 된다는 9.2.2절의 규칙(MUST NOT)과 어떻게 맞물리는지는 RFC 9110이 말하지 않는다. 이 글의 보수적인 해석은, 중개자가 421을 그 MUST NOT을 넘어설 근거로 삼지 않는 것이다.
- 새 연결로 다시 보낸 요청에도 421이 오면 어떻게 할지는 15.5.20절에 없다. 9.2.2절은 따로, 실패한 자동 재시도를 다시 자동으로 재시도하지 않아야 한다고 말한다(SHOULD NOT).
아래 함수는 이 판단을 정리한 것이다. 규칙은 위 절들에서 가져왔고, 함수의 형태와 시도 횟수 계산은 이 글이 정한 것이다.
interface RetryPlan {
retry: boolean;
newConnection: boolean; // 재사용하지 말고 이 오리진용 연결을 새로 연다
}
function onMisdirected(
status: number,
attempt: number, // 첫 전송은 0, 첫 자동 재시도는 1
bodyReplayable: boolean, // 요청 본문을 처음부터 다시 보낼 수 있는가
): RetryPlan {
if (status !== 421) return { retry: false, newConnection: false };
// 15.5.20: 멱등 여부와 관계없이 다른 연결로 재시도할 수 있다(MAY)
// 9.2.2: 실패한 자동 재시도를 다시 자동으로 재시도하지 않는다(SHOULD NOT)
if (attempt >= 1 || !bodyReplayable) return { retry: false, newConnection: false };
return { retry: true, newConnection: true };
}
bodyReplayable은 RFC 규칙이 아니라 구현상의 전제다. 스트리밍으로 내보내고 버린 본문은 새 연결에 실을 수 없다. 이 함수는 새 연결을 어디로 열지도 다루지 않는다. 같은 주소로 새로 연 연결이 같은 종단 장비에 닿을 수 있다면, 클라이언트는 SNI가 대상 오리진의 호스트로 설정되는지 확인해야 한다. RFC 9113 9.1절은 같은 IP와 포트라도 SNI 값이 다르면 연결을 여러 개 여는 것을 허용한다.
이웃한 응답들과 다른 점
- 3xx 리다이렉트는 대상 URI를 바꾼다. 421은 URI는 그대로 두고 요청을 실어 보낼 연결만 바꾼다. HTTP 리다이렉트 참고.
- GOAWAY는 연결 전체를 종료하는 프레임이고, 끝나지 않은 요청을 다시 보낼 수 있는지는 그 안의 스트림 번호로 정해진다. 421은 요청 하나에 대한 일반 응답이고, 연결 자체는 다른 오리진의 요청을 계속 처리할 수 있다. HTTP/2 GOAWAY 뒤의 재전송 참고.
- 425 Too Early는 TLS 1.3 early data의 재전송(replay) 위험에 관한 코드로, 여러 오리진이 연결을 공유하는 문제와는 관계가 없다. TLS 1.3 0-RTT와 HTTP 425 참고.
- RFC 9110 15.5.20절에 따라 프록시는 421을 생성해서는 안 된다. RFC 9113 9.1.1절은 HTTP/2로 프록시를 쓰도록 설정된 클라이언트가 모든 요청을 그 프록시로 가는 연결 하나로 보낸다고 적는다. 즉 프록시를 거치는 모든 요청은 프록시로 가는 연결을 재사용하고, 이런 재사용은 421이 다루는 문제가 아니다.
운영에서 확인할 것
아래는 위 조건에서 끌어낸 이 글의 제안이다. RFC가 정한 절차가 아니며, 측정 데이터에 근거한 것도 아니다.
서버나 게이트웨이 쪽
- 라우팅을 정하는 계층이 요청의
:authority(또는 Host)를 핸드셰이크 때 정한 맥락과 비교하게 한다. 백엔드가 처리하도록 설정되지 않은 이름의 요청이 오면 다른 사이트의 콘텐츠를 내주거나 404를 돌려주지 말고 421로 응답한다. 404나 200으로는 클라이언트가 “새 연결로 다시 시도하라”는 신호를 받지 못한다. - 421은 오리진 서버나 오리진 서버를 대신하는 게이트웨이에서 보낸다. 프록시는 생성해서는 안 된다(RFC 9110 15.5.20절).
- 캐시가 421을 저장하지 않게 한다. 421은 리소스가 아니라 연결에 관한 응답이다(RFC 9110 부록 B.3).
- 애초에 여러 오리진이 연결을 공유하는 일을 줄이고 싶다면 재사용 조건을 본다.
https에서 재사용하려면 다른 호스트에 대해서도 유효한 인증서가 필요하다(RFC 9113 9.1.1절). 서로 다른 곳으로 라우팅되는 이름을 인증서 하나에 묶으면 재사용의 전제 조건이 생긴다. 그 조건이 갖춰졌을 때 특정 클라이언트가 실제로 연결을 재사용하는지는 구현에 달려 있다.
421을 추적할 때
- 요청의 대상 호스트와 연결을 열 때 쓴 호스트 이름(SNI)이 다른지 확인한다. TLS 종단 장비가 SNI를 기록할 수 있다면
:authority와 같은 줄에 남긴다. - 그 연결에서 제시된 인증서가 요청의 호스트도 포함하는지 확인한다.
- 두 이름이 겹치는 주소로 해석되는지 확인한다. RFC 9110 4.3.3절은 이것을 클라이언트가 실제로 하는 확인으로 설명한다.
- 421 뒤에 클라이언트가 새 연결로 재시도하는지, 그 재시도에도 421이 오는지 확인한다. 두 번째 시도도 실패한다면 새 연결이 같은 백엔드에 닿고 있을 가능성이 높다.
정리
RFC에서 가져온 것
- 421은 서버가 이 연결에서 대상 URI에 대해 권한 있는 응답을 줄 수 없거나 주지 않겠다는 뜻이다. 리소스가 아니라 요청과 연결의 조합에 관한 것이다.
- HTTP/2 연결은 서버가 권한을 가지는 한 여러 오리진에 재사용할 수 있다.
https라면 다른 호스트에 대해서도 유효한 인증서가 필요하다. - TLS 종단 장비가 SNI로 라우팅하면, 재사용한 연결이 의도된 대상이 아닌 서버에 닿을 수 있다. 서버는 421로 거절할 수 있다.
- 클라이언트는 421 뒤에 다른 연결로 다시 시도할 수 있고, 비멱등 메서드라도 된다. 프록시는 421을 생성해서는 안 되고, 421은 휴리스틱 캐시 대상이 아니다.
이 글의 제안
- 라우팅 계층이
:authority가 연결과 맞지 않을 때 421을 돌려주게 한다. - 새 연결로의 재시도는 한 번만, 본문을 다시 보낼 수 있을 때만 한다.
- 421을 추적할 때는 SNI,
:authority, 인증서의 이름, DNS 응답을 함께 본다.
출처와 확인 날짜
모두 2026-10-27에 확인했다.
- RFC 9110: HTTP Semantics, 4.3절(4.3.1–4.3.4), 7.2절, 7.3절, 7.4절, 9.2.2절, 15.5.20절, 17.1절, 부록 B.3
- RFC 9113: HTTP/2, 8.3.1절, 9.1절, 9.1.1절, 10.1절, 부록 B