HTTP 426 Upgrade Required

http

API 서버 도메인이 변경되어 변경된 도메인에 맞게 수정 작업 중 HTTP 426 Upgrade Required 에러를 만나게 되었다. 해당 에러를 해결하는 과정에서 HTTP 426에 대해 잘 접하지 못한 에러라 생각되었고 간단히 정리해 두고자 한다.


MDN에 설명된 HTTP 426 Upgrade Required는 "서버가 현재 프로토콜을 사용하여 요청을 처리하는 것은 거부하지만 클라이언트가 다른 프로토콜로 업그레이드한 후에는 요청을 수행할 의향이 있음"으로 정의되어 있다.

그리고 서버에서는 보통 필요한 프로토콜을 응답 헤더 Upgrade에 함께 실어 보낸다고 한다.


상황이 발생한 서비스의 API 통신 과정은 아래와 같다.

  1. 클라이언트에서 웹 서버 도메인으로 API 호출
  2. nginx로 구성된 웹 서버에서 API 서버 도메인으로 프록시 처리 (proxy_pass)
  3. 클라이언트에서 HTTP 426 Upgrade Required 응답 받음

문제는 API 서버에서도 Nginx에서도 어떠한 로그도 확인되지 않는다는 점과 로컬 Vite Dev Server로의 프록시 처리로는 정상 동작한다는 점이였다.

HTTP-426-Upgrade-Required



Nginx proxy_pass

Vite Dev Server에서는 정상 동작하는 것을 확인한 후에는 클라이언트 요청을 받아 Nginx에서 API 서버로 리버스 프록시 처리 과정에서 문제가 있지 않을까 하여 host, resolver 등 여러 설정을 바꿔가며 테스트를 진행했다. 그리고 http protocol 설정 과정에서 1.1 이상의 설정에서 정상 동작 처리를 확인하였다.

이후 Nginx proxy_pass 공식 문서에서 기본 동작이 1.0으로 동작한다는 것을 확인하였고 proxy_http_version을 설정하는 것으로 문제를 해결 할 수 있었다.


why between nginx/nginx upstream use http/1.0?

Nginx는 처음 설계될 때부터 경량 웹 서버 + 프록시 서버로 쓰이도록 설계되었으며 설계 당시 2000년대 초중반 많은 백엔드 서버나 CGI 프레임워크는 HTTP/1.1을 완전히 지원되지 않는 상황이었다고 한다. 그래서 Nginx는 백엔드와의 연결에서 HTTP/1.0을 기본값으로 정해서, 최대한 많은 시스템과 잘 작동하도록 처리한다고 한다.



proxy_pass

위 문제를 해결하며 proxy_pass 설정 부분을 잘 몰라 이런저런 설정을 해보며 테스트해 보았는데 이참에 proxy_pass에 대한 정리도 간단히 해두고자 한다.


리버스 프록시 구현 명령으로 proxy_pass 는 nginx가 받은 요청을 다른 서버로 다시 보내주는 설정이다. 실제 동작은 클라이언트에서 받은 요청을 다른 서버로 요청을 전달하고 응답을 받아 다시 클라이언트에게 보내준다. 이때 클라이언트는 nginx를 요청 응답 서버로 바라보지만, 실제는 다른 서버에서 요청에 대한 응답 처리이다.


설정은 프록시 처리할 경로를 설정 후, proxy_pass로 요청을 전달할 서버 URL를 설정한다.

json
location /api { **proxy_pass** https://api-server; }

서버 URL 설정 시 유의해야 할 점은 마지막의 / 유무에 따라 API 프록시 경로 처리가 달라진다.

/ 가 없는 경우 location에 설정된 path가 그대로 붙어 전달되고 / 가 있는 경우에는 생략되어 서버로 전달된다.


GET /api/user → https://api-server/api/user

json
location /api { **proxy_pass** https://api-server; }

GET /api/user → https://api-server/user

json
location /api { **proxy_pass** https://api-server/; }


options

프록시 과정에서 API 요청 응답에 영향을 미칠 수 있는 헤더 설정도 간단히 정리하고 HTTP 426 Upgrade Required 문제 해결을 위해 설정한 HTTP 버전 설정도 간단히 정리해 본다.


설정설명
proxy_set_header Host $host;Host 헤더를 원본 요청대로 전달
proxy_set_header X-Real-IP $remote_addr;클라이언트 IP를 백엔드에 전달
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;프록시 경유 이력을 전달
proxy_http_version 1.1;HTTP 버전 강제 (웹소켓 등 지원할 때 필수)
proxy_read_timeout 60s;백엔드 응답 대기 시간 설정


In conclusion

HTTP 426 Upgrade Required을 해결하는 과정에서 서비스의 API 요청에서부터 응답 동선과 Nginx 리버스 프록시를 다시 살펴보는 계기가 되어 좋았다. 그리고 HTTP/1.1이 표준이 된 년도는 97년도이지만 Nginx에서 HTTP/1.0을 기본값을 정한 부분에서 그 당시 상황이 있다고 하지만 굉장히 보수적인 설계라는 생각도 들었다.

또 자료를 찾으며 정리 중 Nginx 설계 철학은 explicit is better than implicit라고 알려주는 GPT의 이야기를 듣고 다른 언어들의 철학은 어떠한지도 궁금하였다.

그리고 아래는 Python과 Rust가 본인들의 명시성을 장점으로 내세우는 것을 비판하는 글이다. 아랫글을 보고 함께 일하는 친구들은 명시성에 대해 어떤 생각을 하고 있는지 토론해 보아야겠다.

HTTP-426-Upgrade-Required