최근 몇 년간 SSL/TLS 라이브러리 생태계가 크게 변하며 성능 병목과 호환성 문제가 부각됐다. OpenSSL 3.0 전환이 가져온 영향과 대안 라이브러리들(BoringSSL, LibreSSL, WolfSSL, AWS-LC), QUIC과의 관계, 그리고 HAProxy 관점의 성능 측정 결과 및 권고 사항을 살펴본다.
URL: https://www.haproxy.com/blog/state-of-ssl-stacks
Title: The State of SSL Stacks
이 주제에 대한 문서는 작년에 HAProxy 내부용으로 준비되었고, 이번 버전은 이제 공개적으로 공유됩니다. 인터넷 통신을 보호하는 데 SSL이 맡는 결정적인 역할과 진화하는 SSL 기술이 제기하는 과제를 고려할 때, HAProxy 같은 리버스 프록시는 성능과 호환성을 유지하기 위해 SSL 전략을 지속적으로 적응시켜야 하며, 사용자가 안전하고 효율적인 경험을 얻도록 해야 합니다. 우리는 이러한 발전에 대해 지속적으로 업데이트를 제공할 것을 약속드립니다.
최근 몇 년 사이 SSL 환경은 극적으로 변화했으며, 개발자들에게 성능 병목과 호환성 과제를 가져왔습니다. 한때 신뢰할 수 있는 기반이었던 OpenSSL의 진화는 업계 전반에서 SSL 전략을 근본적으로 재평가하게 만들었습니다.
수년 동안 OpenSSL은 사실상의 표준 SSL 라이브러리로서 장기적 안정성과 일관된 성능을 제공해 왔습니다. 그러나 2021년 9월 3.0 버전이 등장하면서 상황이 완전히 달라졌습니다. 보안과 모듈성을 강화하기 위해 설계되었지만, 새로운 아키텍처는 멀티스레드 환경에서 눈에 띄는 성능 저하를 일으켰고, 많은 외부 프로젝트가 의존하던 필수 API를 폐기(deprecate)했습니다. 게다가 기대되던 QUIC API가 부재하면서, 이미 구현에 투자했던 개발자들의 상황은 더욱 복잡해졌습니다.
이 전환은 전체 생태계에 도전 과제가 되었습니다. OpenSSL 3.0은 장기 지원(LTS) 버전으로 지정된 반면, 널리 사용되던 1.1.1 브랜치에 대한 유지보수는 중단되었습니다. 그 결과, 다수의 리눅스 배포판은 한계가 있음에도 새 버전을 채택할 수밖에 없었습니다. 성능이 중요한 애플리케이션을 사용하는 사용자들은 갈림길에 섰습니다. 점점 지원이 사라지는 이전 버전을 계속 쓰거나, 성능과 기능 측면의 큰 페널티를 감수해야 했습니다.
성능 테스트는 냉혹한 현실을 보여줍니다. 일부 멀티스레드 구성에서 OpenSSL 3.0은 다른 SSL 라이브러리보다 현저히 느려져, 기존 처리량을 유지하기 위해서만 더 많은 하드웨어를 투입해야 하는 상황이 발생합니다. 이는 성능, 에너지 효율, 운영 비용에 대해 중요한 질문을 던집니다.
대안—BoringSSL, LibreSSL, WolfSSL, AWS-LC—을 살펴보면, 각기 다른 트레이드오프가 존재하는 지형이 드러납니다. 각 라이브러리는 API 호환성, 성능 최적화, QUIC 지원에 대해 서로 다른 접근을 취합니다. 현대 SSL 생태계를 탐색하는 개발자에게 이러한 트레이드오프를 이해하는 것은 성능 최적화, 호환성 유지, 인프라의 미래 대비에 필수입니다.
SSL 라이브러리의 기능적 측면은 다양한 소프트웨어 제품에서의 범용성과 적용 가능성을 결정합니다. HAProxy의 SSL 기능 세트는 OpenSSL API를 중심으로 설계되었기 때문에, 호환성 또는 기능 동등성(parity)이 핵심 요구사항입니다.
현대 구현체는 다양한 클라이언트 요구를 수용하면서 더 안전한 프로토콜로의 마이그레이션을 장려하기 위해, (레거시 TLS 1.0부터 최신 TLS 1.3까지) 여러 TLS 프로토콜 버전을 지원해야 합니다.
QUIC 같은 혁신적이고 새롭게 부상하는 프로토콜 지원은 광범위한 채택과 기술적 돌파구를 견인하는 데 중요한 역할을 합니다.
인증서 체인 검증, OCSP 및 CRL을 통한 폐지(Revocation) 확인, SNI(Server Name Indication) 지원을 포함한 인증서 관리 기능은 올바른 배포에 필수입니다.
SSL 라이브러리는 PCI-DSS, HIPAA, FIPS 등 다양한 보안 정책 및 컴플라이언스 요구사항을 충족하기 위해 포괄적인 암호군(cipher suite) 선택지를 제공해야 합니다.
HTTP/2 지원을 위한 ALPN(Application-Layer Protocol Negotiation), 인증서 투명성(certificate transparency) 검증, 스테이플링(stapling) 기능 같은 표준 기능도 기능 요구사항을 확장합니다.
이 라이브러리에 의존하는 소프트웨어 제품은, 특정 사용 사례에 어떤 기능 구성요소가 중요한지 신중히 평가하는 동시에, 이러한 기능이 유발할 수 있는 오버헤드도 고려해야 합니다.
SSL/TLS 연산은 계산 비용이 높아, 이러한 라이브러리에 의존하는 소프트웨어 제품에 중대한 성능 과제를 야기합니다. 안전한 연결을 수립하는 핸드셰이크 연산은 비대칭 암호를 필요로 하며, 특히 트래픽이 많은 환경에서는 상당한 CPU 자원을 소모할 수 있습니다. 또한 계산 비용과 함께 환경적·운영상의 도전 과제도 동반합니다.
암호 연산의 에너지 소비는 이러한 보안 프로토콜에 의존하는 디지털 인프라의 탄소 발자국에 직접적인 영향을 미칩니다. 대량의 SSL 핸드셰이크와 암호화 작업은 데이터센터의 전력 수요를 증가시켜, 전기 소비와 그에 따른 탄소 배출을 늘립니다.
조직들이 지속가능성 목표와 그린 컴퓨팅 이니셔티브를 추구함에 따라 SSL 라이브러리의 성능 중요성은 더욱 커졌습니다. 현대 소프트웨어 제품은 모든 가용 CPU 코어에 암호 워크로드를 분산시켜 단일 노드 효율을 극대화하는 정교한 코어 인지(core-awareness) 전략을 구현합니다. 이러한 프로세서 포화(saturation) 접근은, 수평 확장 전에 기존 하드웨어를 최대한 활용하게 하여, 추가 서버에 필요한 자본 지출과 에너지 소비를 크게 줄입니다.
SSL/TLS 연산에 모든 가용 코어를 효율적으로 활용하면, 적절히 구성된 단일 노드가 최적화가 부족한 여러 서버와 동일한 암호화 트래픽을 처리할 수 있는 경우도 흔하며, 데이터센터 면적, 냉각 요구, 전력 소비를 크게 줄일 수 있습니다.
이러한 아키텍처 개선이 SSL 라이브러리에 의해 제대로 활용될 경우, 환경에 미치는 영향은 최소화하면서도 큰 성능 향상을 제공할 수 있습니다. 전 세계 네트워크에서 암호화 트래픽이 기하급수적으로 증가하는 상황에서 이는 중요한 고려사항입니다.
SSL 구현체의 유지보수 부담은 소프트웨어 제품에 상당한 도전 과제를 제시합니다. SSL 라이브러리의 보안 취약점은 즉각적인 대응을 요구하므로, 개발 팀은 견고한 패치 프로세스를 갖춰야 합니다.
소프트웨어 제품은 안정성이 검증된 SSL 라이브러리와 신규 버전의 보안 개선 사이에서 균형을 잡아야 합니다. 운영체제 벤더가 일관되고 시기적절한 업데이트를 제공할 때 이 과정은 더 수월해집니다. 문서화와 전문성 요구사항도 복잡성을 더하는데, SSL을 올바르게 구성하려면 개발 팀 내에 부족할 수 있는 특수 지식이 필요하기 때문입니다. 하위 호환성 문제 역시 유지보수를 어렵게 만들며, 업데이트는 기존 기능을 보호하면서 필요한 보안 개선 또는 수정도 구현해야 합니다.
새 SSL 라이브러리 버전으로 마이그레이션하는 복잡성과 위험 때문에, 제품 벤더들은 가능한 한 오랫동안 동일한 유지보수 브랜치(가능하다면 OS 벤더가 제공하는 LTS 버전)에 머무르려는 경향이 있습니다.

OpenSSL은 오랜 기간 대부분의 운영체제에 포함된 업계 표준 SSL 라이브러리 역할을 해왔습니다. 중요한 장점은 여러 버전을 장기간에 걸쳐 동시에 지원해 왔다는 점으로, 사용자는 업그레이드를 신중히 계획하고, 새 버전을 수용하도록 코드를 적응시키며, 적용 전에 충분히 테스트할 수 있었습니다.
하지만 2021년 9월 OpenSSL 3.0의 도입은 SSL 생태계의 안정성에 중대한 도전을 제기하며, 지속적인 신뢰성과 지속가능성을 위협했습니다.
이 버전은 일정보다 거의 1년 늦게 출시되어, 애플리케이션을 새 버전으로 마이그레이션할 수 있는 시간적 여유가 줄었습니다.
OpenSSL의 API 변경으로 마이그레이션이 어려웠습니다. 많은 흔히 쓰이는 함수들과 외부 프로젝트가 의존하던 ENGINE API가 폐기되었기 때문입니다. 이는 HSM(Hardware Security Module)을 위한 pkcs11 엔진, 하드웨어 암호 가속을 위한 Intel QAT 엔진 같은 솔루션에 영향을 주었고, 새로운 providers API로 엔진을 다시 작성해야 했습니다.
멀티스레드 환경에서 성능이 측정 가능할 정도로 낮아져, 성능 의존적인 다수의 사용 사례에서 OpenSSL 3.0은 사용할 수 없는 수준이었습니다.
OpenSSL은 오랫동안 기다려온 QUIC API를 결국 병합하지 않기로 결정하여, 이 기술의 혁신가와 조기 채택자에게 큰 타격을 주었습니다. 개발자와 조직은 프로젝트에 필요하다고 믿어왔던 핵심 QUIC 기능 없이 남겨졌습니다.
OpenSSL은 3.0을 LTS 브랜치로 지정한 뒤 얼마 지나지 않아 이전 1.1.1 LTS 브랜치의 유지보수를 중단했습니다. 이 결정은 많은 리눅스 배포판에 실질적인 대안을 남기지 않아, 새 버전 채택을 강제했습니다.
성능이 중요한 요구사항을 가진 사용자는 선택지가 제한되었습니다. 1.1.1을 자체 유지보수하는 구 배포판에 머무르거나, 성능 손실을 보전하기 위해 서버를 더 배치하거나, 비싼 유료 장기 지원 계약을 구매해 자체 패키지를 유지해야 했습니다.
BoringSSL은 heartbleed CVE 이후 2014년에 발표된 OpenSSL의 포크입니다. 이 라이브러리는 원래 Google을 위한 것이었고, 이를 사용하는 프로젝트는 "HEAD를 따라간다(live at HEAD)" 모델을 따라야 합니다. API가 자주 깨지고 유지보수 브랜치를 제공하지 않기 때문에 유지보수 측면의 어려움이 생길 수 있습니다.
하지만 최첨단 기능을 구현하려는 의지가 강하다는 점에서 SSL 생태계에서 두드러집니다. 예를 들어, QUIC API를 최초로 구현한 OpenSSL 기반 라이브러리였으며, 다른 라이브러리들이 이후 이를 채택했습니다.
이 라이브러리는 HAProxy 커뮤니티에서 한동안 지원되었고 QUIC 관련 진전을 이룰 기회를 제공했습니다. HAProxy의 LTS 모델과 호환되지 않아 이후에는 중단되었지만, 유의미한 혁신을 자주 만들어내기 때문에 계속 주시하고 있습니다.
LibreSSL은 heartbleed 취약점 이후 OpenSSL의 더 안전한 대안을 목표로 등장한 OpenSSL 1.0.1 포크입니다. OpenSSL 코드 대청소로 시작해, 레거시이거나 드물게 사용되는 많은 코드와 OpenSSL API 일부를 제거했습니다.
이후 LibreSSL은 libssl API의 더 단순하고 안전한 대안으로 설계된 완전히 새로운 API인 libtls API를 제공했습니다. 하지만 API가 완전히 다르기 때문에, 애플리케이션이 이를 채택하려면 상당한 수정이 필요합니다.
LibreSSL은 더 안전한 SSL을 지향하며 다른 라이브러리보다 성능이 낮은 편입니다. 따라서 잠재적으로 안전하지 않다고 간주되는 기능은 구현하지 않는데, 예를 들어 0-RTT가 그렇습니다. 현재 프로젝트는 BoringSSL의 영향을 일부 받으며 libssl API를 진화시키는 데 집중하고 있습니다(예: EVP_AEAD, QUIC API).
LibreSSL은 libressl-portable 프로젝트 형태로 다른 운영체제에도 포팅되었습니다. 하지만 리눅스 배포판에서는 패키징되는 경우가 드물고, 주로 BSD 환경에서 사용됩니다.
HAProxy는 LibreSSL을 지원합니다(현재 CI 파이프라인에서 빌드 및 테스트됨). 다만 모든 기능이 지원되지는 않습니다. LibreSSL은 2022년에 BoringSSL QUIC API를 구현했고, HAProxy 팀은 libressl 3.6.0으로 HAProxy를 성공적으로 포팅했습니다. 그러나 LibreSSL은 HAProxy를 최대한 활용하기 위해 필요한 API 기능을 모두 구현하지는 않습니다.
WolfSSL은 원래 임베디드 영역을 목표로 한 TLS 라이브러리입니다. OpenSSL의 포크는 아니지만 호환성 레이어를 제공해 애플리케이션 포팅을 더 쉽게 합니다.
2012년에 우리는 그 전신인 cyaSSL을 테스트했습니다. 성능은 비교적 괜찮았지만 기능이 너무 부족해 사용 대상으로 고려하기 어려웠습니다. 그 이후 라이브러리는 TLS 1.3, QUIC 등 많은 중요한 기능을 추가하며 발전했고, 경량 접근을 유지하는 동시에 FIPS 인증 암호 모듈도 제공하게 되었습니다.
2022년에 우리는 WolfSSL 팀의 도움을 받아 HAProxy를 WolfSSL로 포팅하기 시작했습니다. OpenSSL 호환 레이어에는 버그와 누락 기능이 있었지만, WolfSSL 5.6.6 기준으로 단순한 구성이나 임베디드 시스템에서는 실용적인 선택지가 되었습니다. CI에도 성공적으로 통합되어, 최신 WolfSSL 버전으로 정기적으로 빌드 및 테스트됩니다.
WolfSSL은 OpenSSL 기반이 전혀 아니므로 일부 동작이 달라질 수 있고 모든 기능이 지원되지는 않습니다. HAProxy SSL 기능은 OpenSSL API를 중심으로 설계되었습니다. 이는 OpenSSL API 기반이 아닌 SSL 라이브러리로의 첫 포팅이었기 때문에 기존 기능을 완벽히 매핑하기 어렵습니다. 그 결과 일부 기능은 때때로 소규모 설정 조정이 필요할 수 있습니다.
우리는 WolfSSL이 메인스트림 리눅스 배포판에서 HAProxy와 매끄럽게 통합될 수 있도록 WolfSSL 팀과 협력하고 있으나, 해당 통합은 아직 개발 중입니다(https://github.com/wolfSSL/wolfssl/issues/6834).
WolfSSL은 Ubuntu와 Debian에서 사용할 수 있지만, HAProxy 및 CPU 최적화에 필요한 특정 빌드 옵션이 기본적으로 활성화되어 있지 않습니다. 그 결과 수동으로 설치하고 유지해야 하며 번거로울 수 있습니다.
AWS-LC는 2019년에 시작된 BoringSSL(그리고 그 상위로 OpenSSL) 포크입니다. AWS와 그 고객을 대상으로 하며, 보안과 성능(특히 AWS 하드웨어에서)을 목표로 합니다. BoringSSL과 달리, 후방 호환 API를 지향해 유지보수가 쉽습니다.
최근 AWS 팀이 HAProxy를 AWS-LC와 호환되게 만드는 패치를 제공했고, 이를 통해 CI를 통해 정기적으로 함께 테스트할 수 있게 되었습니다. 과거 HAProxy가 BoringSSL로 포팅된 적이 있기 때문에, 이미 작동하던 많은 기능을 그대로 물려받았습니다.
AWS-LC는 현대 TLS 기능과 QUIC을 지원합니다. HAProxy에서는 OpenSSL 1.1.1과 동일한 기능을 지원하지만, 더 이상 사용되지 않는 일부 오래된 암호(CCM, DHE)는 부족합니다. 또한 BoringSSL에서 이미 제거된 엔진(ENGINE) 지원이 없습니다.
또한 FIPS 인증 암호 모듈을 제공하며, 주기적으로 FIPS 검증을 위해 제출됩니다.
Mbedtls, GnuTLS 등의 라이브러리도 검토했지만, HAProxy의 SSL 코드를 대규모로 다시 작성해야 했습니다. 당시의 기능 세트로는 필요한 선행 작업과 유지보수 노력을 정당화할 수 없어 포팅하지 않았습니다.
또한 Rustls와 rustls-openssl-compat 호환 레이어도 테스트했습니다. Rustls는 미래에 흥미로운 라이브러리가 될 수 있지만, OpenSSL 호환 ABI가 충분히 완성되지 않아 현재 상태로는 HAProxy와 제대로 동작하게 만들기 어려웠습니다. 네이티브 Rustls API를 사용하려면 역시 HAProxy 코드를 대대적으로 다시 작성해야 합니다.
QUIC 개발 중에는 QuicTLS(openssl+quic)도 자주 사용했습니다. 하지만 OpenSSL과 충분히 다르지 않아 별도의 라이브러리로 보기는 어렵습니다. 사실상 OpenSSL 위에 패치셋을 적용한 형태로 배포되기 때문입니다.
QUIC은 주로 HTTP/3를 전송하는 데 사용되는, 암호화되고 멀티플렉싱되는 전송 프로토콜입니다. TCP, TLS, HTTP/2의 일부 장점을 결합하면서도 많은 단점을 피합니다. 2012년 Google의 연구로 시작되어 2014년 Chrome 브라우저와 함께 대규모로 배포되었습니다. 2015년에는 IETF QUIC 워킹그룹이 구성되어 프로토콜 표준화를 시작했고, 2016년 11월 28일 첫 드래프트(draft-ietf-quic-transport-00)를 공개했습니다. 2020년에는 새로운 IETF QUIC 프로토콜이 원래 버전과 꽤 달라지며 브라우저와 일부 대형 호스팅 제공업체에서 널리 채택되기 시작했습니다. 최종적으로 2021년 RFC9000으로 발행되었습니다.
이 프로토콜의 핵심 목표 중 하나는 혼잡 제어를 유저랜드(userland)로 옮겨, 운영체제가 구현·배포할 때까지 기다리지 않고 애플리케이션 개발자들이 새로운 알고리즘을 실험할 수 있게 하는 것입니다. 고전적인 TLS가 TCP 위의 추가 레이어인 것과 달리, QUIC은 암호를 프로토콜의 중심에 통합합니다.

풀스택 웹 애플리케이션은 다음 핵심 구성요소에 의존합니다:
HTTP/1, HTTP/2, HTTP/3 구현(자체 구현 또는 라이브러리)
QUIC 구현(자체 구현 또는 라이브러리)
위 3개 프로토콜 구현이 공유하는 TLS 라이브러리
나머지(아래)는 일반적인 UDP/TCP 커널 소켓

전반적으로 이는 잘 통합되며, 다양한 QUIC 구현이 아주 이른 시기부터 새 프로토콜 개념을 검증하고 발전에 필요한 피드백을 제공하기 위해 시작되었습니다. 일부 구현은 HAProxy의 QUIC 구현처럼 특정 프로젝트에 특화되어 있고, ngtcp2처럼 공통 애플리케이션에서 쉽게 채택할 수 있도록 이식성과 범용성을 목표로 한 구현도 있습니다.
이 작업 과정에서 QUIC 구현이 TLS 레코드에 담긴 필수 요소에 접근할 수 있도록 새로운 TLS API가 필요하다는 점이 확인되었고, 필요한 변경이 BoringSSL(Google의 OpenSSL 포크)에 도입되었습니다. 이는 오랫동안 클라이언트와 서버 모두에 대해 QUIC 구현에서 사용 가능한 유일한 TLS 라이브러리였습니다. BoringSSL을 사용하기 어려운 점 중 하나는 빠르게 진화한다는 것인데, 공개 API 변경으로 새 버전이 정기적으로 빌드를 깨뜨리므로 장기간 유지보수되는 제품에는 적합하지 않을 수 있습니다.
2020년 2월, Todd Short는 OpenSSL GitHub 저장소에 PR을 열어 OpenSSL에 BoringSSL 호환 QUIC API를 제안했습니다. 추가 코드는 몇몇 핵심 지점에 콜백을 더해, MsQuic, ngtcp2, HAProxy 등 기존 QUIC 구현이 BoringSSL뿐 아니라 OpenSSL도 지원할 수 있게 했습니다. 커뮤니티의 반응은 매우 긍정적이었습니다. 그러나 OpenSSL 팀은 OpenSSL 3.0이 출시될 때까지 이 작업을 보류하길 선호했고(관련 글, 보류 유지), 일정이 지연되는 동안에도 이 선택을 재고하지 않았습니다. 이 기간에 Akamai와 Microsoft 개발자들은 QuicTLS를 만들었습니다. 이 프로젝트는 본질적으로 OpenSSL 최신 안정 버전에 패치셋을 적용한 형태였습니다. QuicTLS는 OpenSSL 3.0 출시와 PR 병합을 기다리던 QUIC 구현들에게 사실상의 표준 TLS 라이브러리가 되었습니다.
결국 3년 뒤, OpenSSL 팀은 해당 작업을 통합하지 않고 대신 새로운 QUIC 구현을 처음부터 만들겠다고 발표했습니다. 이는 사용자들이 필요로 했던 것도, 요청했던 것도 아니었으며, QUIC 커뮤니티가 수년간 축적한 검증된 작업을 폐기하는 결과가 되었습니다. 이 충격적인 결정은 커뮤니티의 강한 반발을 불러왔습니다. QuicTLS를 통해 OpenSSL에 많은 노력을 투자한 이들은 다른 해법을 찾아야 했습니다. 빠르게 변하는 BoringSSL을 쓰거나, 더 공식적으로 유지보수되는 QuicTLS 변형을 택해야 했습니다.
한편 WolfSSL, LibreSSL, AWS-LC 등 다른 라이브러리들도 사실상의 표준이 된 BoringSSL QUIC API를 채택했습니다.
마지막으로 OpenSSL은 프로젝트 내에서 QUIC을 계속 언급하지만, 현재 초점은 명령행 도구 "s_client"에 충분한 단일 스트림 최소 기능 제품(MVP)을 제공하는 데 있는 듯합니다. 그러나 이 접근은 지난 4년간 QUIC 구현들이 기다려온 API를 제공하지 못하며, 그 결과 QUIC 구현들은 QuicTLS로 돌아설 수밖에 없습니다.
QUIC 같은 전송 계층 개발은 암호 라이브러리 개발과는 전혀 다른 역량이 필요하며, 이런 개발은 완전한 투명성 하에 이루어져야 합니다. 개발 팀은 프로젝트 품질을 저하시켰고, 지속되는 문제를 해결하지 못했으며, 광범위한 커뮤니티의 요구(사소한 개선조차)를 반복적으로 일축해 왔습니다. 이러한 우려를 확인하듯, Curl 기여자 Stefan Eissing은 최근 Curl에서 OpenSSL의 QUIC 구현을 활용하려 시도했고 그 결과를 공개했습니다. 이는 이 주제에 관심 있는 대부분의 개발자가 기대했을 법한 것과는 거리가 멉니다.
이 상황에 절망감을 느낀 HAProxy 팀은 OpenSSL에 패치를 적용하지 않고도 QUIC 패치셋을 기반으로 우회할 방법이 있을지 고민했고, 우리만 그런 것은 아니었습니다. NGINX 코어 팀의 Roman Arutyunyan이 keylog 콜백을 악용(abuse)해 필요한 요소를 추출/주입하는 기발한 방법으로, 최소한의 서버 모드 QUIC 지원을 가능하게 하는 해법을 최초로 제안했습니다. 우리도 이를 채택해, 사용자들이 QUIC과 인프라에 미치는 영향을 익힐 수 있도록 했습니다. 다만 기술적 한계(예: 0-RTT 미지원)가 있습니다. 또한 이 해법은 서버 전용이며, 클라이언트에는 동작하지 않을 수 있습니다(현재 HAProxy에서는 QUIC이 프런트엔드에만 구현되어 있으므로 HAProxy에는 동작합니다).
이 모든 점을 고려하면, OpenSSL 중심으로 설계된 프로젝트에서 QUIC 구현을 위해 선택할 수 있는 TLS 라이브러리는 현재 매우 제한적입니다:
QuicTLS: OpenSSL과 가장 가깝고 OpenSSL 대체로 잘 동작할 가능성이 높지만, OpenSSL 위에 리베이스되기 때문에 OpenSSL 3+의 해결되지 않은 기술적 문제(아래에서 더 다룸)의 영향을 받음
AWS-LC: 기능이 꽤 완전하고 유지보수되며 릴리스가 잦고 상당히 빠르지만, 현재로서는 전용 LTS 브랜치가 없음
WolfSSL: 완전성은 떨어지지만 적응성이 높고 매우 빠르며, 지원 계약도 제공하므로 LTS는 협의 가능성이 높음
LibreSSL: OpenBSD에 기본 포함되며 OpenSSL 대비 일부 기능과 최적화가 부족하지만, 소규모 사이트에서는 즉시 사용 가능
NGINX의 해킹: 서버 전용, OpenSSL 그대로(재빌드 없이) 즉시 동작하지만 몇 가지 제한이 있고 OpenSSL 3+의 해결되지 않은 기술적 문제도 함께 겪게 됨
BoringSSL: 모든 것의 출발점이지만, 많은 프로젝트에겐 변화 속도가 너무 빠름
이 불행한 상황은 QUIC 프로토콜의 채택을 상당히 저해하고, QUIC 서버를 모니터링할 테스트 도구를 개발·빌드하는 것조차 어렵게 만듭니다. 산업 관점에서 보면 WolfSSL 또는 AWS-LC가 LTS 버전을 제공해야 시장 선도 위치로 이동할 수 있을 것처럼 보입니다. 그렇게 되면 OpenSSL은 구식이 되고 QuicTLS 노력도 불필요해질 수 있습니다.

SSL에서 성능은 가장 중요한 요소입니다. 실제로 통신이 가능해지기 전에 연결 초기에 매우 비싼 연산이 수행됩니다. 연결이 빠르게 닫히는 경우(서비스 리로드, 스케일 업/다운, 스위치오버, 피크 시간대, 공격 등) 서버는 쉽게 과부하로 응답을 멈출 수 있고, 이는 방문자 재시도를 유발해 트래픽을 더 늘릴 수 있습니다. 이런 이유로 SSL 프런트엔드 게이트웨이는 서비스 품질 저하 없이 트래픽 급증을 처리할 수 있도록 많은 CPU 코어를 갖춘 강력한 시스템이 되는 경향이 있습니다.
Intel과 협업해 수행한 성능 테스트(그 결과 최적화가 이 문서에 반영됨) 중 예상치 못한 병목을 발견했습니다. 48코어 머신에서 "h1load" 생성기가 초당 400개 이상의 연결을 만들어내지 못하는 상황이었습니다. 광범위한 트러블슈팅 끝에, 트레이스는 스레드들이 libcrypto(OpenSSL 라이브러리의 일부) 내부에서 서로를 기다리고 있음을 보여주었습니다. 로드 생성기는 OpenSSL 3.0.2가 포함된 Ubuntu 22.04에서 구성되어 있었습니다. OpenSSL 1.1.1을 다시 빌드해 링크하자 문제가 즉시 해결되어 초당 14만 연결이 가능해졌습니다. 테스트에 참여한 여러 팀원은 도구들을 OpenSSL 3.0에 링크한 탓에 비슷한 함정에 빠졌고, 결국 이 버전이 클라이언트 기반 성능 테스트 목적에 근본적으로 부적합함을 깨달았습니다.
우리가 겪은 성능 문제는 더 광범위한 패턴의 일부였습니다. 많은 사용자가 OpenSSL 3에서 성능 저하를 보고했고, 라이브러리 전반에 걸친 대규모 성능 퇴행을 모으기 위한 메타 이슈도 존재합니다(https://github.com/OpenSSL/OpenSSL/issues/17627). 여기에는 클라이언트로서 사용될 때 nodejs 성능이 7분의 1로 감소했다는 보고, 다른 도구에서 처리 시간이 20배 증가했다는 보고, 로드 생성기 문제와 유사한 스레드 애플리케이션 CPU 사용이 30배 증가했다는 보고 등 다수의 사례가 포함됩니다.
QUIC API 거부로 인한 큰 좌절감에도 불구하고, 우리는 OpenSSL이 이 막대한 성능 퇴행을 파악하고 해결하도록 돕고 싶었습니다. 다른 참여자들과 함께 문제의 근본 원인을 OpenSSL 팀에 설명하려 했고, 여기처럼 상세 측정치, 그래프, 락 카운트를 제공했습니다. OpenSSL은 (32GB RAM의 Intel Xeon에 대해 이야기하면서) "임베디드 시스템은 더 이상 타깃이 아니므로 락 콜백을 재구현하지 않겠다"고 답했으며, 심지어 문제를 고치는 PR을 환영한다고 제안했습니다. 마치 제3자가 성능 저하를 유발한 문제를 쉽게 고칠 수 있는 것처럼 말입니다.
사용자 경험과 개발자 관점의 괴리는 최근 논의에서 더 두드러졌고, 특히 성능 테스트 문화의 부재로 더욱 명확해졌습니다. 패치를 테스트해 달라고 사용자에게 요청한 한 개발자가 하드웨어 부족을 이유로 자신은 테스트를 수행하지 못한다고 인정한 것은 이 부재를 명백히 보여줍니다. 이후 프로젝트가 하드웨어 접근을 공개적으로 요청하면 된다는 제안이 나왔고(이는 1~2주 내 해결된 것으로 보임), 그때서야 제안된 패치들의 성능 테스트가 프로젝트 외부 참여자들(Akamai, HAProxy, Microsoft)에 의해 수행되었습니다.
프로젝트 구성원 일부가 32% 성능 퇴행을 "원래 성능에 꽤 가깝다"고 간주했을 때, 우리 개발 팀은 유의미한 개선 가능성이 낮다고 판단했습니다. 테스트용 하드웨어 부족은 프로젝트가 문제 해결에 충분한 자원을 투입할 의지가 없거나 능력이 없음을 의미하며, 유의미한 지표는 아마 열려 있는 이슈의 수뿐일 것입니다. 최근에는 OpenSSL을 사용하는 프로젝트들이 신뢰를 잃기 시작해 대체 라이브러리에 링크할 수 있는 옵션을 추가하고 있습니다. 이는 지난 3년간 상황이 정체되었다는 점에서, 우리의 경험과 관찰과도 일치합니다.
OpenSSL 1.1.0 이전에는 간단하고 효율적인 락킹 API에 의존했습니다. 스레드를 사용하는 애플리케이션은 OpenSSL API를 초기화하고, 락/언락에 사용할 함수 포인터 몇 개를 전달하기만 하면 됐습니다. 이는 애플리케이션이 사용하는 어떤 스레딩 모델과도 호환되는 장점이 있었습니다. OpenSSL 1.1.0부터는 이 함수가 무시되고, OpenSSL은 표준 Pthread 라이브러리가 제공하는 락에만 의존합니다. 이는 애플리케이션이 사용하던 락보다 훨씬 무거울 수 있습니다.
당시 락은 많은 곳에 구현되어 있었지만, 배타적(exclusive) 모드로 사용되는 경우는 드물었고 가장 흔한 코드 경로에서도 그렇지 않았습니다. 예를 들어, 우리는 크립토 엔진 사용 시 락 사용이 매우 많아 주요 병목이 되곤 했고, 세션 재개와 캐시 접근에서도 꽤 사용됐지만, 나머지 코드 경로에서는 비교적 적게 사용되었습니다.
2년 전 Intel QAT 엔진 테스트 중, OpenSSL 1.1.1이 엔진 API에서 과도한 락을 사용해 16개 스레드 이후 극심한 경합을 유발할 수 있음을 이미 확인했습니다. 엔진은 엣지 케이스이고 테스트 및 최적화가 어려울 수 있으므로 이는 감내할 만했습니다. 이를 pthread_rwlocks로 구현되어 있음을 보고, 우리가 이미 더 가벼운 읽기-쓰기 락 구현을 갖고 있었기 때문에, OpenSSL이 레거시 pthread_rwlocks 대신 우리의 저오버헤드 락("lorw")을 사용하도록 자체 pthread_rwlock 함수를 제공하는 아이디어를 떠올렸습니다. 이는 경합 지점을 훨씬 높이는 데 매우 효과적이었습니다. 이 개선은 결국 병합되었고, 대체 락 메커니즘을 활성화하는 빌드 옵션 USE_PTHREAD_EMULATION도 추가되었습니다. 아래에서 이 옵션이 락에 기인한 영향을 측정하는 데 다시 활용될 것입니다.
OpenSSL 3.0에서는 라이브러리를 훨씬 더 동적으로 만들려는 것이 중요한 목표였던 것으로 보입니다. 이전에는 상수였던 요소들(예: 알고리즘 식별자 등)이 동적으로 바뀌었고, 컴파일 타임에 고정되는 대신 리스트에서 조회해야 했습니다. 새 설계는 누구나 런타임에 그 리스트를 업데이트할 수 있게 하므로, 일관성을 위해 리스트 접근 시 곳곳에 락이 추가되었습니다. 이 리스트는 매우 기본적인 설정 요소를 찾기 위해 스캔되는 것으로 보이며, 따라서 이 연산은 매우 자주 수행됩니다. 위에서 링크된 측정 중 하나에서는, 서버 모드(영향이 가장 적은 모드)에서만 읽기 락(비배타)이 OpenSSL 1.1.1 대비 5배 증가했음을 보여주었습니다. 클라이언트 모드에서는 아예 동작하지 않아 측정을 수행할 수 없었습니다. 타임아웃과 워치독이 몇 초마다 발생했습니다.
아래에서 보듯이 락 메커니즘만 바꿔도 눈에 띄는 성능 향상이 나타나므로, 락 남용이 OpenSSL 3.0에 영향을 주는 성능 저하의 주된 원인임이 입증됩니다.
OpenSSL 3.1은 가능해 보이는 곳에서 락 대신 일부 원자(atomic) 연산을 배치해 문제를 부분적으로 해결하려 했습니다. 그러나 아키텍처 자체가 필요 이상으로 훨씬 동적이게 설계되었을 가능성이 있고, 이는 성능이 중요한 워크로드에는 부적합하며, 위에서 언급한 이슈들의 성능 보고에서도 분명히 드러납니다.
현재 남아 있는 문제는 두 가지입니다:
가능한 모든 조치를 취한 뒤에도 OpenSSL 3.x의 성능은 OpenSSL 1.1.1보다 훨씬 뒤떨어집니다. 비율은 워크로드에 크게 좌우되어 예측하기 어렵지만, 10%에서 99%까지의 손실이 보고되었습니다.
OpenSSL 1.1.1을 서둘러 없애려는 과정에서, OpenSSL 팀은 3.0이 출시되기 전에 1.1.1의 EOL을 선언했고, 이후 3.0 출시를 1년 이상 미루면서도 1.1.1 EOL 날짜는 조정하지 않았습니다. 3.0이 마침내 출시되었을 때 1.1.1의 수명은 거의 남지 않았고, 그 결과 3.0을 LTS로 선언해야 했습니다. 이는 충분히 테스트되지 않은 완전히 새로운 아키텍처의 버전이, OS들이 수년간 지원해야 하는 요구 때문에 여러 운영체제에서 수년간 제공될 것임을 의미했습니다. 결과적으로 이 버전은 성능과 안정성 면에서 지금까지 출시된 어떤 버전보다도 극적으로 나쁜 것으로 드러났습니다.
최종 사용자들은 막다른 길에 처했습니다:
운영체제는 이제 3.0을 제공하는데, 이는 특정 사용자에겐 사실상 사용할 수 없습니다.
1.1.1을 제공하던 배포판은 점차 지원 종료에 이르고 있습니다(연장 지원을 제공하는 배포판은 예외지만, 사용자가 적고 종종 유료입니다).
OpenSSL 1.1.1은 OpenSSL 팀이 무료로 지원하지 않으므로, 많은 사용자가 이를 안전하게 사용할 수 없습니다.
이 문제는 HAProxy 커뮤니티에서 큰우려를 촉발했고 우선순위를 근본적으로 바꿔놓았습니다. 원래는 "QUIC을 구현하기 위해 어떤 라이브러리를 써야 하는가?" 같은 미래지향적 질문에 집중하고 있었지만, 이제는 더 기본적인 생존 문제—"웹사이트를 그냥 운영 가능하게 유지해줄 SSL 라이브러리는 무엇인가?"—와 맞닥뜨리게 되었습니다. 성능 문제가 너무 심각해, 새로운 기능 지원보다 기본 기능 자체가 최우선 고려사항이 되었습니다.
HAProxy는 이미 대체 라이브러리를 지원했지만, API 차이로 인해 대부분의 지원이 불완전했습니다. 위에서 설명한 새 성능 문제는 대안의 완전한 채택을 가속하도록 만들었습니다. 현재 HAProxy는 OpenSSL 외에도 QuicTLS, LibreSSL, WolfSSL, AWS-LC 등 여러 SSL 라이브러리를 지원합니다. QuicTLS는 단순히 OpenSSL에 QUIC 패치를 더한 것이며 성능에 영향이 없기 때문에 테스트에 포함하지 않았습니다. LibreSSL은 코드 정확성과 감사 가능성에 초점을 맞추며, 우리는 이미 상당한 성능 손실(특정 알고리즘의 어셈블리 구현 제거와 일부 기능 단순화와 관련된 것으로 보임)을 관찰했기 때문에 테스트에서 제외했습니다.
우리는 OpenSSL 1.1.1부터 당시 최신 3.4-dev까지 다양한 버전을 포함해, 3.x가 1.1.1 대비 얼마나 성능을 잃었는지, 그리고 OpenSSL 팀이 퇴행을 수정하기 위해 어떤 진전을 이뤘는지 측정했습니다. OpenSSL 3.0.2는 특히 Ubuntu 22.04에 포함되어 있어 언급했습니다. 많은 사용자는 OpenSSL 1.1.1이 포함된 Ubuntu 20.04에서 업그레이드하면서 문제를 겪고 있기 때문입니다. 테스트에 사용한 HAProxy 버전은 다음과 같습니다: HAProxy version 3.1-dev1-ad946a-33 2024/06/26
테스트 시나리오:
서버 단독 모드 + 전체 TLS 핸드셰이크: 인터넷에 노출된 웹 장비(서버 및 로드 밸런서)에서 가장 중요하고 흔한 사용 사례입니다. 매우 비싼 비대칭 암호 연산이 필요합니다. 절대 최악의 경우이며 클라이언트가 언제든 새 핸드셰이크를 강제할 수 있으므로 성능 영향은 특히 우려스럽습니다. 또한 서비스 거부 공격의 쉬운 표적이 될 수 있습니다.
TLS 재개를 사용하는 종단 간 암호화: 재개(resumption)는 오리진 서버로 가는 백엔드에서 가장 흔한 방식입니다. 오늘날의 가상화 환경에서는 네트워크 경로가 불명확하므로 보안이 특히 중요합니다. 서버에 높은 부하를 주지 않기 위해 새 TCP 연결에서 TLS 세션을 재개합니다. 프런트엔드에서도 대부분 사이트의 일반적인 케이스와 맞추기 위해 동일하게 수행합니다.
테스트 변형:
락 옵션 2종(표준 Pthread 락, HAProxy의 저오버헤드 락)
다수의 SSL 라이브러리 및 버전
테스트 환경:

이 테스트에서 클라이언트는:
서버(이 경우 HAProxy)에 연결
단일 HTTP 요청 수행
연결 종료
이 단순화된 시나리오에서는 가장 이상적인 조건을 시뮬레이션하기 위해 백엔드 서버를 포함하지 않습니다(영향이 무시할 만하기 때문). HAProxy는 클라이언트 요청에 직접 응답할 수 있습니다. 재연결 시 기존 세션 재개를 시도하지 않고 항상 새 연결을 수행합니다. RSA를 사용하는 경우 이 사용 사례는 클라이언트에게는 매우 저렴하고 서버에는 매우 비쌉니다. 이는 (키 교환을 유발하는) 신규 방문자 급증을 나타냅니다. 예를 들어 어떤 이벤트 후 갑자기 인기가 상승한 사이트(예: 뉴스 사이트) 같은 경우입니다. 이런 테스트에서 클라이언트와 서버 간 성능 비율이 1:10~1:15 정도면 서버를 포화시키기에 충분합니다. 여기서 서버는 64코어이지만 클라이언트는 32코어로 유지할 것이며, 이는 충분합니다.
서로 다른 라이브러리를 사용했을 때 머신의 성능은 초당 신규 연결 수로 측정됩니다. 항상 CPU가 포화되는 것을 확인했습니다. 첫 테스트는 라이브러리에 대해 HAProxy를 일반적으로 빌드한 경우입니다(즉, HAProxy가 pthread 락을 에뮬레이트하지 않고 라이브러리가 pthread 락을 사용하게 둠):

두 라이브러리가 상단과 하단에서 두드러집니다. 상단(초당 63,000 연결 이상)의 연한 파란색은 AWS-LC 최신 버전(v1.32.0 이후 30개 커밋)으로, RSA 계산에 대한 중요한 CPU 수준 최적화가 포함됩니다. 이전 버전에서는 프로세서를 제대로 감지하지 못해 적절한 최적화를 활성화하지 못하는 코드 오류 때문에 이런 결과가 나오지 않았습니다. 두 번째로 빠른 라이브러리(주황색)는 WolfSSL 5.7.0입니다. 이 라이브러리는 제한된 하드웨어에서도 빠르게 동작하도록 고도로 최적화되어 있기로 유명했기 때문에, 이렇게 강력한 머신에서도 상위에 있는 것이 놀랍지 않으며 오히려 반갑습니다.
중간(초당 48,000 연결 전후, 약 25% 낮음)에는 OpenSSL 1.1.1과 이전 버전 AWS-LC(약 45k, 1.29.0)가 있습니다. 그 아래(초당 42,500 연결 전후)에는 OpenSSL 최신 버전들(3.1, 3.2, 3.3, 3.4-dev)이 있고, 최하단(초당 21,000 연결 전후)에는 테스트 시점 최신 3.0 버전인 OpenSSL 3.0.2와 3.0.14가 있습니다.
이 그래프에서 특히 눈에 띄는 점은, 이 프로세서에 특화된 최적화를 가진 두 버전을 제외하면, 다른 모든 라이브러리는 12~16 스레드 정도까지는 비슷하게 묶여 있다는 것입니다. 그 이후부터 라이브러리들이 갈라지기 시작하며, OpenSSL 3.0 계열 두 버전은 바닥에 머물고 32스레드 즈음에서 최대 성능에 도달하며 평탄해집니다. 즉, 이는 암호 최적화 문제가 아니라 확장성 문제입니다.
이 테스트에서 OpenSSL 1.1.1과 3.0.14의 프로파일링 결과를 비교하면 차이가 명확합니다.
OpenSSL 1.1.1w:
(아래 내용에는 숨겨진 또는 양방향 Unicode 텍스트가 포함될 수 있습니다. 숨겨진 문자를 확인하려면 해당 기능을 지원하는 편집기에서 여십시오.)
46.29% libcrypto.so.1.1 [.] __bn_sqr8x_mont 14.73% libcrypto.so.1.1 [.] __bn_mul4x_mont 13.01% libcrypto.so.1.1 [.] MOD_EXP_CTIME_COPY_FROM_PREBUF 2.05% libcrypto.so.1.1 [.] __ecp_nistz256_mul_mont 1.06% libcrypto.so.1.1 [.] sha512_block_armv8 0.95% libcrypto.so.1.1 [.] __ecp_nistz256_sqr_mont 0.61% libcrypto.so.1.1 [.] ecp_nistz256_point_double 0.54% libcrypto.so.1.1 [.] bn_mul_mont_fixed_top 0.52% [kernel] [k] default_idle_call 0.51% libc.so.6 [.] malloc 0.51% libc.so.6 [.] _int_free 0.50% libcrypto.so.1.1 [.] BN_mod_exp_mont_consttime 0.49% libcrypto.so.1.1 [.] ecp_nistz256_sqr_mont 0.46% libc.so.6 [.] _int_malloc 0.43% libcrypto.so.1.1 [.] OPENSSL_cleanse
OpenSSL 3.0.14:
(아래 내용에는 숨겨진 또는 양방향 Unicode 텍스트가 포함될 수 있습니다. 숨겨진 문자를 확인하려면 해당 기능을 지원하는 편집기에서 여십시오.)
19.12% libcrypto.so.3 [.] __bn_sqr8x_mont 17.33% libc.so.6 [.] __aarch64_ldadd4_acq 15.14% libc.so.6 [.] pthread_rwlock_unlock@@GLIBC_2.34 12.48% libc.so.6 [.] pthread_rwlock_rdlock@@GLIBC_2.34 8.55% libc.so.6 [.] __aarch64_cas4_rel 6.04% libcrypto.so.3 [.] __bn_mul4x_mont 5.39% libcrypto.so.3 [.] MOD_EXP_CTIME_COPY_FROM_PREBUF 1.59% libcrypto.so.3 [.] __ecp_nistz256_mul_mont 0.80% libcrypto.so.3 [.] __aarch64_ldadd4_relax 0.74% libcrypto.so.3 [.] __ecp_nistz256_sqr_mont 0.53% libcrypto.so.3 [.] __aarch64_ldadd8_relax 0.50% libcrypto.so.3 [.] ecp_nistz256_point_double 0.43% libcrypto.so.3 [.] sha512_block_armv8 0.30% libcrypto.so.3 [.] ecp_nistz256_sqr_mont 0.24% libc.so.6 [.] malloc 0.23% libcrypto.so.3 [.] bn_mul_mont_fixed_top 0.23% libc.so.6 [.] _int_free
OpenSSL 3.0.14는 읽기 락을 획득/해제하는 데 시간의 27%를 사용하고 있는데, 이는 키 교환 연산 중에는 분명 필요하지 않아야 하는 것입니다. 여기에 원자 연산 26%까지 더하면, CPU의 53%가 비생산적인 작업에 사용됩니다.
USE_PTHREAD_EMULATION=1로 빌드했을 때(라이브러리가 Pthread 락 대신 HAProxy의 저오버헤드 락을 사용) 얼마나 성능을 회복할 수 있는지 살펴보겠습니다.

결과는 OpenSSL 3.0을 제외한 모든 라이브러리에서 성능이 정확히 동일하게 유지됨을 보여줍니다. OpenSSL 3.0은 성능이 크게 증가해 초당 약 36,000 연결에 도달했습니다. 이제 프로파일은 다음과 같습니다:
OpenSSL 3.0.14:
33.03% libcrypto.so.3 [.] __bn_sqr8x_mont 10.63% haproxy-openssl-3.0.14-emu [.] pthread_rwlock_wrlock 10.34% libcrypto.so.3 [.] __bn_mul4x_mont 9.27% libcrypto.so.3 [.] MOD_EXP_CTIME_COPY_FROM_PREBUF 5.63% haproxy-openssl-3.0.14-emu [.] pthread_rwlock_rdlock 3.15% haproxy-openssl-3.0.14-emu [.] pthread_rwlock_unlock 2.75% libcrypto.so.3 [.] __ecp_nistz256_mul_mont 2.19% libcrypto.so.3 [.] __aarch64_ldadd4_relax 1.26% libcrypto.so.3 [.] __ecp_nistz256_sqr_mont 1.10% libcrypto.so.3 [.] __aarch64_ldadd8_relax 0.87% libcrypto.so.3 [.] ecp_nistz256_point_double 0.72% libcrypto.so.3 [.] sha512_block_armv8 0.50% libcrypto.so.3 [.] ecp_nistz256_sqr_mont 0.42% libc.so.6 [.] malloc 0.41% libc.so.6 [.] _int_free
두 테스트의 유일한 차이는 사용된 락이었습니다. 락에 소비되는 시간이 눈에 띄게 줄었지만, 이 정도의 큰 차이를 설명하기에는 충분하지 않습니다. 다만 pthread_rwlock_wrlock가 새로 나타났는데 이전 프로파일에서는 보이지 않았습니다. 경합 시 원래 함수가 곧바로 커널에서 슬립 상태로 들어가, 대기 시간이 CPU 시간(perf top이 측정하는 값)에 포함되지 않았을 가능성이 큽니다.

다음 테스트는 가장 최적의 경우를 다룹니다. 즉, 프록시가 클라이언트 티켓으로부터 TLS 세션을 재개할 수 있고, 백엔드 서버로 연결할 때도 세션 재개를 사용하는 경우입니다. 이 모드에서는 세션 티켓을 받는 데 필요한 시간 동안만 클라이언트와 서버 각각에 대해 비대칭 암호를 한 번 사용하고, 나머지는 더 가벼운 암호를 사용합니다.
이 시나리오는 퍼블릭 클라우드 인프라에 호스팅된 애플리케이션의 가장 흔한 사용 사례를 나타냅니다. 애플리케이션에 하루 종일 접속하는 클라이언트는 동일한 TCP 연결을 유지하지 않습니다. 사용되지 않으면 연결은 투명하게 종료되고, 활동 시 다시 열리며 TLS 세션이 재개됩니다. 결과적으로 초기 비대칭 암호 비용은 수많은 요청과 연결에 걸쳐 분산되면서 무시할 만해집니다. 또한 퍼블릭 클라우드에서는 프록시와 백엔드 서버 간 암호화가 필수이므로, 양쪽 모두 SSL이 존재합니다.
성능이 훨씬 높아질 것이므로 단일 클라이언트와 단일 서버만으로는 벤치마크가 부족합니다. 따라서 프록시당 10 클라이언트와 10 서버가 필요하며, 각자가 총 부하의 10%를 담당합니다. 이론적 구성은 다음과 같습니다:

구성을 단순화하기 위해, 동일 프로세스 내에서 프록시 인스턴스 10개를 두는 방식(즉 포트 10개, 각 클라이언트→서버 연결에 1개씩)을 사용할 수 있습니다:

클라이언트 및 서버와의 연결은 정확히 같은 프로토콜과 동작(http/1.1, close, resume)을 사용하므로, 각 인스턴스를 다음 인스턴스에 데이지 체인으로 연결하고 클라이언트 1과 서버 10만 남길 수 있습니다:

이 구성에서는 단일 클라이언트와 단일 서버만 필요하며, 각각 부하의 10%를 보지만, 프록시는 10번에 걸쳐 그 10%를 처리해야 하므로 총 100% 부하를 보게 됩니다.
첫 테스트는 기본 락을 유지한 일반 HAProxy 버전으로 수행했습니다. 성능은 종단 간(end-to-end) 초당 연결 수로 측정되며, 클라이언트에서 하나 수락한 연결과 서버로 하나 발신한 연결을 합쳐 종단 간 연결 1개로 계산합니다.

우선 상단 두 곡선은 잠시 무시하겠습니다. 주황색 곡선은 다시 WolfSSL로, 64코어까지 뛰어난 선형 확장성을 보여주며 초당 150,000 종단 간 연결에 도달합니다. 이 성능은 이용 가능한 CPU 코어 수에 의해 제한되었을 뿐입니다. 이는 HAProxy의 현대적 확장성을 입증하며, 코어 수 증가에 따라 단일 프로세스 내에서 선형 성능 확장이 가능함을 보여줍니다.
그 아래 갈색 곡선은 OpenSSL 1.1.1w입니다. 재키잉(rekeying)에서는 꽤 잘 확장되곤 했지만, 재개(resume)와 서버 연결이 포함되면 확장성이 사라지고 40 스레드에서 성능이 저하됩니다. 64스레드에서는 8스레드 수준으로 붕괴하여 초당 17,800 연결에 그칩니다. 프로파일링은 원인을 명확히 보여주는데, 락과 원자 연산만으로 CPU 사이클의 약 80%가 낭비됩니다.
OpenSSL 1.1.1w:
72.83% libc.so.6 [.] pthread_rwlock_wrlock@@GLIBC_2.34 1.99% libc.so.6 [.] __aarch64_cas4_acq 1.47% libcrypto.so.1.1 [.] fe51_mul 1.30% libc.so.6 [.] __aarch64_cas4_relax 1.24% libcrypto.so.1.1 [.] fe_mul 1.00% libc.so.6 [.] __aarch64_ldset4_acq 0.86% libcrypto.so.1.1 [.] sha512_block_armv8 0.77% [kernel] [k] futex_q_lock 0.70% [kernel] [k] queued_spin_lock_slowpath 0.70% libcrypto.so.1.1 [.] fe51_sq 0.68% libcrypto.so.1.1 [.] x25519_scalar_mult 0.56% libc.so.6 [.] pthread_rwlock_unlock@@GLIBC_2.34
가장 성능이 나쁜 라이브러리(하단의 평탄한 곡선)는 다시 OpenSSL 3.0.2와 3.0.14입니다. 둘 다 2스레드 이상 확장되지 못합니다. 3.0.2는 16스레드에서 붕괴해 X축과 구분이 어려울 정도로 낮아지고, 16스레드 이후 초당 1500~1600 연결을 보이며 WolfSSL의 1%에 불과합니다. OpenSSL 3.0.14는 약간 나아 3700 연결/초로 끝나지만 여전히 WolfSSL의 2.5%입니다. 직설적으로 말하면: Ubuntu 22.04에 포함된 OpenSSL 3.0.2를 그대로 사용하면 동일 하드웨어에서 WolfSSL의 성능 1/100이 됩니다! 이를 다른 방식으로 말하면, 기본 SSL 라이브러리 때문에 동일 트래픽을 처리하려면 100배의 머신을 배치해야 하는 셈입니다.
또한 OpenSSL 1.1.1에서 32코어 시스템이 최적으로 초당 63,000 연결을 처리하던 것이, OpenSSL 3.0.2에서는 초당 1500 연결로 떨어져 성능이 1/42가 되는 것도 확인됩니다. 예를 들어 Ubuntu 20.04에서 22.04로 업그레이드한 뒤 이런 일이 발생할 수 있습니다. 이는 현재 많은 사용자들이 겪고 있는 정확한 상황입니다. Ubuntu 24.04로 업그레이드해 OpenSSL 3.0.14를 사용해도 성능이 대략 두 배 정도만 개선되어 문제의 극히 일부만 완화되는 것 역시 이해할 수 있습니다.
OpenSSL 3.0.2에서 실행되는 프로세스의 성능 프로파일은 다음과 같습니다:
14.52% [kernel] [k] default_idle_call 14.15% libc.so.6 [.] __aarch64_ldadd4_acq 9.87% libc.so.6 [.] pthread_rwlock_unlock@@GLIBC_2.34 7.32% libcrypto.so.3 [.] ossl_sa_doall_arg 7.28% libc.so.6 [.] pthread_rwlock_rdlock@@GLIBC_2.34 6.23% [kernel] [k] arch_local_irq_enable 3.35% libcrypto.so.3 [.] __aarch64_ldadd8_relax 2.80% libc.so.6 [.] __aarch64_cas4_rel 2.04% [kernel] [k] arch_local_irq_restore 1.32% libcrypto.so.3 [.] OPENSSL_LH_doall_arg 1.11% libcrypto.so.3 [.] __aarch64_ldadd4_relax 0.87% [kernel] [k] futex_q_lock 0.84% libcrypto.so.3 [.] fe51_mul 0.82% [kernel] [k] el0_svc_common.constprop.0 0.74% libcrypto.so.3 [.] fe_mul 0.65% libcrypto.so.3 [.] OPENSSL_LH_flush 0.64% libcrypto.so.3 [.] OPENSSL_LH_doall 0.62% [kernel] [k] futex_wake 0.58% libc.so.6 [.] _int_malloc 0.57% [kernel] [k] wake_q_add_safe 0.53% libcrypto.so.3 [.] sha512_block_armv8
여기서 보이는 것은 CPU가 락과 원자 연산, 그리고 깨우기/수면 사이클에서 낭비되고 있다는 점이며, 이것이 CPU 사용률이 350~400% 이상으로 올라가지 못하는 이유입니다. 락이 슬립 상태로 들어가면서 머신이 무엇인가를 기다리는 듯 보이며, 전체 작업이 심하게 직렬화됩니다.
또 하나 우려스러운 곡선은 하단 근처의 AWS-LC(파란색)입니다. 적은 스레드에서는 다른 라이브러리보다 훨씬 높은 성능을 보이다가, 코어 수가 늘어나면 갑자기 붕괴합니다. 프로파일은 이것이 락 문제임을 보여주며 perf top으로도 확인됩니다:
AWS-LC 1.29.0:
86.01% libc.so.6 [.] pthread_rwlock_wrlock@@GLIBC_2.34 2.43% libc.so.6 [.] __aarch64_cas4_relax 1.78% libc.so.6 [.] __aarch64_cas4_acq 1.13% [kernel] [k] futex_q_lock 1.09% libc.so.6 [.] __aarch64_ldset4_acq 0.82% libc.so.6 [.] __aarch64_swp4_relax 0.76% [kernel] [k] queued_spin_lock_slowpath 0.65% haproxy-aws-lc-v1.29.0-std [.] curve25519_x25519_byte_scalarloop 0.25% [kernel] [k] futex_get_value_locked 0.23% haproxy-aws-lc-v1.29.0-std [.] curve25519_x25519base_byte_scalarloop 0.15% libc.so.6 [.] __aarch64_cas4_rel 0.13% libc.so.6 [.] _int_malloc
락이 CPU의 대부분을 차지하고, 원자 연산도 꽤 큰 비중을 차지합니다(특히 경합에 취약한 CAS(compare-and-swap) 연산). 또한 커널 내 락(futex 등)도 일부 보입니다. 약 1년 전 초기 x86 테스트(라이브러리 1.19)에서 이 동작을 관찰했지만 당시에는 깊이 조사하지 않았습니다.
플레임 그래프를 파고들어보면, 참조 카운팅(reference counting) 연산이 많은 락 비용을 유발한다는 점이 드러납니다:

락 비용으로 큰 영향을 받는 라이브러리가 두 개나 있었기 때문에, HAProxy의 락을 사용하는 새로운 테스트 시리즈를 수행했습니다(HAProxy를 USE_PTHREAD_EMULATION=1로 재빌드).

결과는 훨씬 나아졌습니다. OpenSSL 1.1.1은 이제 거의 선형이며 초당 124,000 종단 간 연결에 도달합니다. 성능 프로파일도 훨씬 깔끔해졌고, 락에 소비되는 CPU 사이클은 3% 미만입니다.
OpenSSL 1.1.1w:
7.52% libcrypto.so.1.1 [.] fe51_mul 6.47% libcrypto.so.1.1 [.] fe_mul 4.68% libcrypto.so.1.1 [.] sha512_block_armv8 3.64% libcrypto.so.1.1 [.] fe51_sq 3.42% libcrypto.so.1.1 [.] x25519_scalar_mult 2.67% haproxy-openssl-1.1.1w-emu [.] pthread_rwlock_wrlock 2.48% libcrypto.so.1.1 [.] fe_sq 2.33% libc.so.6 [.] _int_malloc 2.04% libc.so.6 [.] _int_free 1.84% [kernel] [k] __wake_up_common_lock 1.83% libc.so.6 [.] cfree@GLIBC_2.17 1.80% libc.so.6 [.] malloc 1.59% libcrypto.so.1.1 [.] OPENSSL_cleanse 1.10% [kernel] [k] el0_svc_common.constprop.0 0.95% libcrypto.so.1.1 [.] cmov 0.91% libcrypto.so.1.1 [.] SHA512_Final 0.77% libc.so.6 [.] __memcpy_generic 0.77% libc.so.6 [.] __aarch64_swp4_rel 0.73% libc.so.6 [.] malloc_consolidate 0.71% [kernel] [k] kmem_cache_free
OpenSSL 3.0.2는 구조적 결함을 그대로 유지하지만, 이제는 32스레드까지는 붕괴하지 않습니다(이전에는 12였음). 이를 통해 락과 원자 연산 사용 방식이 더 명확히 드러납니다(96%가 락).
OpenSSL 3.0.2:
77.58% haproxy-openssl-3.0.2-emu [.] pthread_rwlock_rdlock 18.02% haproxy-openssl-3.0.2-emu [.] pthread_rwlock_wrlock 0.51% libcrypto.so.3 [.] ossl_sa_doall_arg 0.39% haproxy-openssl-3.0.2-emu [.] pthread_rwlock_unlock 0.34% libcrypto.so.3 [.] OPENSSL_LH_doall_arg 0.27% libcrypto.so.3 [.] OPENSSL_LH_flush 0.26% libcrypto.so.3 [.] OPENSSL_LH_doall 0.23% libcrypto.so.3 [.] __aarch64_ldadd8_relax 0.13% libcrypto.so.3 [.] __aarch64_ldadd4_relax
OpenSSL 3.0.14는 64스레드까지 (낮은 수준이긴 하지만) 성능을 유지하는데, 이번에는 초당 약 8000 연결로, Pthread 락을 썼을 때보다 두 배 이상 높습니다. 그러나 여전히 락을 과도하게 사용합니다(CPU 사용의 89%).
OpenSSL 3.0.14:
60.18% haproxy-openssl-3.0.14-emu [.] pthread_rwlock_rdlock 28.69% haproxy-openssl-3.0.14-emu [.] pthread_rwlock_unlock 0.55% libcrypto.so.3 [.] fe51_mul 0.49% libcrypto.so.3 [.] fe_mul 0.46% libcrypto.so.3 [.] __aarch64_ldadd4_relax 0.33% libcrypto.so.3 [.] sha512_block_armv8 0.27% libcrypto.so.3 [.] fe51_sq 0.26% libcrypto.so.3 [.] x25519_scalar_mult 0.26% libc.so.6 [.] _int_malloc 0.22% libc.so.6 [.] _int_free
최신 OpenSSL 버전들은 많은 락을 원자 연산으로 대체했지만, 이 원자 연산이 과도해졌습니다. 아래에서 __aarch64_ldadd4_relax()—참조 카운팅 및 수동 락킹에서 흔히 쓰이는 명령—이 CPU를 많이 쓰는 것으로 확인됩니다.
OpenSSL 3.4.0-dev:
37.24% libcrypto.so.3 [.] __aarch64_ldadd4_relax 8.91% libcrypto.so.3 [.] evp_md_init_internal 8.68% libcrypto.so.3 [.] EVP_MD_CTX_copy_ex 7.18% libcrypto.so.3 [.] EVP_DigestUpdate 2.03% libcrypto.so.3 [.] fe51_mul 1.92% libcrypto.so.3 [.] EVP_DigestFinal_ex 1.78% libcrypto.so.3 [.] fe_mul 1.45% haproxy-openssl-3.4.0-dev-emu [.] pthread_rwlock_rdlock 1.43% haproxy-openssl-3.4.0-dev-emu [.] pthread_rwlock_unlock 1.22% libcrypto.so.3 [.] sha512_block_armv8 1.09% libcrypto.so.3 [.] fe51_sq 0.86% libc.so.6 [.] _int_malloc 0.85% libcrypto.so.3 [.] x25519_scalar_mult 0.77% libc.so.6 [.] _int_free
WolfSSL 곡선은 전혀 변하지 않습니다. 락이 필요 없는 것이 분명합니다.
AWS-LC 곡선은 붕괴하기 전까지 더 높이 올라가지만(32스레드—81,000 연결/초), 여전히 락이 무겁습니다.
AWS-LC 1.29.0:
69.57% haproxy-aws-lc-v1.29.0-emu [.] pthread_rwlock_wrlock 4.80% haproxy-aws-lc-v1.29.0-emu [.] curve25519_x25519_byte_scalarloop 1.65% haproxy-aws-lc-v1.29.0-emu [.] curve25519_x25519base_byte_scalarloop 0.93% haproxy-aws-lc-v1.29.0-emu [.] pthread_rwlock_unlock 0.73% [kernel] [k] __wake_up_common_lock 0.52% libc.so.6 [.] _int_malloc 0.47% libc.so.6 [.] _int_free 0.45% haproxy-aws-lc-v1.29.0-emu [.] sha256_block_armv8 0.41% haproxy-aws-lc-v1.29.0-emu [.] SHA256_Final
AWS-LC의 새로운 플레임그래프를 생성했는데, 스파이크가 훨씬 좁아졌습니다(성능이 대략 두 배로 늘었으니 놀랍지 않음).

참조 카운팅은 일반적으로 락을 사용하지 않아야 하므로, AWS-LC 코드에서 개선할 점이 있는지 검토했습니다. 실제로 참조 카운팅 함수 구현이 두 가지 있다는 점을 발견했습니다. 하나는 Pthread rwlock에 의존하는 일반 구현이고, 다른 하나는 gcc-4.7부터 지원되는 원자 연산을 사용하는 더 현대적인 구현으로 C11 표준을 채택하도록 구성된 컴파일러에서만 선택됩니다. gcc-5부터는 C11이 기본입니다. 테스트는 gcc-11.4로 수행했으므로 원래는 괜찮아야 합니다. 더 깊은 분석 결과, 프로젝트를 구성하는 CMakeFile이 변수를 설정하지 않으면(CMAKE_C_STANDARD) 표준을 오래된 C99로 강제하고 있었습니다.
CMAKE_C_STANDARD=11로 라이브러리를 재빌드하자 성능이 급격히 바뀌었고, 최상단 곡선은 -c11 변형에 해당했습니다. 이제는 라이브러리가 빠른 경로에서 락을 사용하지 않으므로, 일반 빌드와 에뮬레이트 락 간 차이가 없습니다. WolfSSL과 마찬가지로 코어/스레드 수에 따라 선형으로 확장합니다. 이 경우 64스레드에서 초당 183,000 종단 간 연결에 도달하여, WolfSSL보다 약 20% 높고 OpenSSL 1.1.1w보다 50% 높습니다. 프로파일에서도 락이 사라졌습니다.
AWS-LC 1.29.0:
16.61% haproxy-aws-lc-v1.29.0-c11-emu [.] curve25519_x25519_byte_scalarloop 5.69% haproxy-aws-lc-v1.29.0-c11-emu [.] curve25519_x25519base_byte_scalarl 2.65% [kernel] [k] __wake_up_common_lock 1.60% libc.so.6 [.] _int_malloc 1.55% haproxy-aws-lc-v1.29.0-c11-emu [.] sha256_block_armv8 1.53% [kernel] [k] el0_svc_common.constprop.0 1.52% libc.so.6 [.] _int_free 1.36% haproxy-aws-lc-v1.29.0-c11-emu [.] SHA256_Final 1.27% libc.so.6 [.] malloc 1.22% haproxy-aws-lc-v1.29.0-c11-emu [.] OPENSSL_free 0.93% [kernel] [k] __fget_light 0.90% libc.so.6 [.] __memcpy_generic 0.89% haproxy-aws-lc-v1.29.0-c11-emu [.] CBB_flush
이 문제는 AWS-LC 프로젝트에 보고되었고, 프로젝트는 이를 환영했으며 다음 버전에 빠르게 수정해 릴리스했습니다(대체로 cmake 기반 빌드 시스템에서 생기는 "고양이와 쥐" 문제였습니다).
마지막으로 OpenSSL 최신 버전(3.1, 3.2, 3.3, 3.4-dev)은 가벼운 락의 이점을 거의 누리지 못합니다. 네 버전 모두 성능은 동일하며, 가벼운 락을 사용해도 25,000에서 28,000 연결/초로만 증가하고 24~32스레드 사이에서 평탄해집니다. 이는 OpenSSL 1.1.1의 22.5%, AWS-LC 성능의 15.3%에 해당합니다. 이는 경합이 락에만 집중되지 않고 원자 연산 남용으로 코드 전체에 분산되었음을 강하게 시사합니다. 문제는 단순 최적화가 아니라 근본적인 소프트웨어 아키텍처 문제에서 비롯됩니다. 영구적인 해법은 효율적 자원 사용을 우선하고 현실의 애플리케이션 요구에 부합하는 더 가벼운 아키텍처로 되돌리는 것을 필요로 할 것입니다.
아래 그래프는 각 라이브러리의 성능(서버 핸드셰이크/초)을 보여줍니다(수치는 초당 연결 수를 천 단위로 표시).

OpenSSL 3.0.x를 제외하면, 이 단계에서 라이브러리들은 락의 영향을 받지 않습니다. 이는 락을 많이 사용하지 않는다는 것을 의미합니다. 성능은 대체로 비슷하며, CPU 인지형(AWS-LC, WolfSSL)이 최상단, 그 다음이 OpenSSL 1.1.1, 그 다음이 모든 OpenSSL 3.x 버전입니다.
다음 그래프는 TLS 재개에 대한 성능(초당 포워딩된 연결 수, 천 단위)을 보여줍니다.

이 테스트는 종단 간 연결을 포함하며, 클라이언트가 HAProxy에 연결을 수립한 뒤, HAProxy가 서버로 연결을 수립합니다. 사전 핸드셰이크가 이미 수행되었고 티켓을 통해 연결이 재개되므로, 수치가 이전 테스트보다 훨씬 높습니다. OpenSSL 1.1.1w는 기본적으로 중간 정도 락 사용 때문에 성능이 나쁘지만, 가벼운 락을 사용하면 최상위 성능군으로 올라갑니다. OpenSSL 3.0.x는 극도로 낮은 성능을 보이며 락을 대체해도 개선은 제한적이고, 최대로 봐도 두 배 수준입니다.
모든 OpenSSL 3.x 버전은 락이 문제의 일부일 뿐인 채로 낮은 성능을 보입니다. 하지만 이 버전에 갇힌 사용자도 HAProxy 빌드 옵션을 설정해 우리의 가벼운 락을 통해 이득을 볼 수 있습니다. aws-lc1.32의 기본 빌드도 컴파일러를 잘못 감지해 참조 카운팅에 원자 연산 대신 락을 쓰기 때문에 기본 성능이 매우 낮습니다. 하지만 제대로 구성하면 최고 성능을 보입니다. WolfSSL은 기본 설정으로도 매우 좋습니다. 잘못된 컴파일 옵션에도 불구하고 AWS-LC는 OpenSSL 3.x의 어떤 버전보다도 훨씬 낫고, OpenSSL 3.x가 가벼운 락을 써도 마찬가지입니다.
안타깝게도 OpenSSL 사용자에게 미래는 밝지 않습니다. 역사상 가장 큰 성능 퇴행 중 하나 이후, 측정 결과 지난 2년간 이를 극복하기 위한 추가 진전이 전혀 없었고, 팀의 문제 해결 능력은 정체 상태에 도달한 것으로 보입니다.
문제를 해결하려면 그 문제를 만든 사람들보다 더 뛰어난 지성이 필요하다는 말이 종종 있습니다. 강한 신념으로 해결책의 "정당성"을 확신한 팀이 문제를 설계한 경우, 그 문제를 만든 팀이 스스로 해결할 가능성은 극히 낮아 보입니다. 최신 릴리스에서의 진전 부재는 이러한 불행한 가설을 뒷받침합니다. 유일한 전진 경로는 3.x 버전을 괴롭히는 주요 변경 일부를 되돌리는 것처럼 보이지만, 논의에 따르면 이는 그들의 선택지에서 제외된 듯합니다.
기술적 사안이 여전히 위원회와 투표로 결정되는 프로젝트에서 어떤 결과가 나올지 예측하기는 어렵습니다. 이러한 반패턴이 더 나쁨을 낳는다는 점은 잘 알려져 있습니다. 관료주의와 관리자가 상식을 거슬러 결정하는 것은 신뢰할 수 있는 솔루션으로 이어지기 어렵고, 기술 문제에서 다수가 항상 옳은 것도 아닙니다. 또한 프로젝트가 최근 재조직되었지만, 위원회와 투표 기반 의사결정은 유지되어, 가까운 시일 내 추가 변화가 기대되지 않는 듯합니다.
2023년 초 프로젝트 리더 중 한 명인 Rich Salz는 QuicTLS 프로젝트가 Apache Incubator를 통해 아파치 재단으로 이동해 _Apache TLS_가 될 가능성을 언급했습니다. 그러나 이는 이루어지지 않았습니다. 한 가지 가능한 설명은, 이처럼 고된 작업에 장기간 참여할 충분한 유지보수자를 찾기 어려웠기 때문일 수 있습니다. 또한 OpenSSL이 3 이상 버전에서 성능을 완전히 망가뜨렸다는 현실도 작용했을 것입니다. 큰 성능 결함으로 출발하고, 2년 동안 개선하거나 해결하지 못한 팀의 무능이 드러난 프로젝트에 개발자가 참여하고 싶어 하지는 않을 것입니다. IETF 120에서 QuicTLS 프로젝트 리더들은 OpenSSL과의 분기, BoringSSL과 유사한 방식의 작업, 그리고 다른 프로젝트들과의 협력을 목표로 한다고 밝혔습니다.
AWS-LC는 강한 커뮤니티를 가진 매우 활발한 프로젝트로 보입니다. 첫 만남에서 몇 가지 거친 부분이 있었지만 빠르게 해결되었습니다. 최근 보고된 성능 문제도 빠르게 수정되어 다음 버전에 포함되어 릴리스되었습니다. 이 글을 작성하는 동안에도 여러 버전이 출시되었습니다. 이 주제에 관심 있는 누구나 지켜볼 가치가 있는 라이브러리입니다.

최종 사용자를 위한 해법은 무엇일까요?
성능 영향과 무관하게, 운영체제 벤더가 OpenSSL 릴리스 위에 QuicTLS 패치셋을 적용해 제공한다면, 성능에 민감하지 않은 환경에서 QUIC 채택에 큰 도움이 될 것입니다.
QUIC을 테스트하거나 사용하되 성능은 신경 쓰지 않는 사용자(대부분)에게 HAProxy는 OpenSSL 위에서 0-RTT 없이 QUIC을 지원하는 limited-quic 옵션을 제공합니다. 다른 사용자, 그리고 다른 제품의 사용자에게도 QuicTLS를 빌드하는 것은 쉽고, 어떤 코드에도 매끄럽게 통합되는 100% OpenSSL 호환 라이브러리를 제공할 것입니다.
성능 측면에서는 정기적으로 버전을 업그레이드할 수 있는 사용자는 AWS-LC를 채택해야 합니다. AWS-LC는 BoringSSL(그 자체가 OpenSSL 포크)과 같은 계보를 공유하므로 기존 코드와 잘 통합됩니다. 팀은 도움이 되고 반응이 빠르며, HAProxy의 SSL 스택에서 호환되지 않는 의미 있는 기능은 아직 발견하지 못했습니다. 공식 LTS 브랜치는 없지만, FIPS 브랜치는 5년간 유지되므로 적절한 대안이 될 수 있습니다. 최첨단을 추구하는 사용자는 AWS-LC 라이브러리를 주기적으로 업그레이드하고 재빌드하는 것이 권장됩니다.
시스템에 맞춰 라이브러리를 미세 조정하고 싶은 사용자는 WolfSSL을 선택하는 것이 좋습니다. 지원은 꽤 좋지만 OpenSSL과 같은 계보가 없고 API를 에뮬레이션할 뿐이므로, 때때로 작은 차이를 발견합니다. 따라서 제품에 배포하려면 많은 테스트와 기능 검증이 필요합니다. 프로젝트 뒤에 회사가 있으므로, 양측에 맞는 지원 기간을 협상할 수 있을 것입니다.
한편, 고객을 위한 지속 가능한 해법을 아직 결정하지 못했기 때문에, 우리는 OpenSSL 1.1.1(연장 지원 포함)과 QuicTLS 패치셋을 적용한 패키지를 제공하고 있습니다. 이는 SSL 환경을 계속 평가하는 동안, 지원·기능·성능의 최적 조합을 제공합니다.
리눅스 배포판에서의 OpenSSL 3.0의 현 상태는 사용자가 보통 패키징되어 있지 않은 대안 해법을 찾게 만듭니다. 이는 사용자가 OS 벤더로부터 자동 보안 업데이트를 더 이상 받지 못하고, 새로 나타나는 보안 취약점에 대한 대응 책임이 전적으로 사용자에게 넘어간다는 뜻입니다. 따라서 실제 환경의 TLS 구현 전반의 보안 태세가 상당히 약화되었습니다. 위에서 본 것처럼 3.0 자체의 문제도 고려해야 하며, 이는 쉬운 DoS 표적이 됩니다. 우리는 이 주제의 소식을 계속 주시하고, HAProxy 위키에 최신 조사 결과와 제안을 계속 업데이트하고 있습니다. 모두가 주기적으로 확인하길 권장합니다.
시간이 지나면서 상황이 명확해지기를 바랄 뿐입니다.
첫째, OpenSSL은 3.0을 LTS로 태그해서는 안 됐습니다. 이는 “openssl s_client”나 Curl 같은 명령행 도구를 넘어서면 사실상 동작하지 않기 때문입니다. 우리는 OpenSSL이 더 최신 릴리스를 LTS로 태그하길 촉구합니다. 3.1 이후에도 성능은 업그레이드 이전과는 여전히 큰 차이가 있지만, 소규모 사이트에서는 사용할 수 있는 영역으로 돌아왔기 때문입니다. 그렇게 되면 QuicTLS 포크는 QUIC을 지원하는 사용 가능한 LTS 버전(고성능이 필요하지 않은 사이트용)의 이점을 누릴 수 있을 것입니다.
OpenSSL은 마침내 3.5-beta에서 자체 QUIC API를 구현하여 오랜 이슈를 끝냈습니다. 하지만 이 새 API는 다른 라이브러리와 QUIC 구현이 수년간 사용해 온 표준 API와 호환되지 않습니다. 기존 구현을 이 새 QUIC API에 통합하려면 상당한 작업이 필요하며, 가까운 미래에 새 QUIC API를 사용하는 많은 새 구현이 등장할 가능성도 낮습니다. 따라서 이 API의 관련성은 현재로서는 불확실합니다. Curl 저자 Daniel Stenberg는 자신의 블로그에서 발표에 대한 리뷰를 제공했습니다.
둘째, 모두가 에너지 발자국을 줄이려는 세계에서, 전 세대 효율의 4분의 1 수준으로 동작하고 경쟁사보다 6~9배 느린 라이브러리에 집착하는 것은 전 세계적 지속가능성 노력에 역행합니다. 이는 용납될 수 없으며, 커뮤니티가 힘을 모아 문제를 해결해야 합니다.
AWS-LC와 QuicTLS는 QUIC 제공, 고성능, 좋은 전방 호환성이라는 유사한 목표를 추구하는 것으로 보입니다. 이런 프로젝트들이 협력해 훌륭한 성능을 제공하는 AWS-LC의 몇 가지 LTS 버전을 사용자에게 제공하는 것이 합리적일 수도 있습니다. 현재 OS 벤더들은 이런 라이브러리를 배포하기에 충분히 긴 지원 약속이 부족하며, 일단 채택되면 SSL을 사용하는 대부분의 소프트웨어가 큰 이점 때문에 빠르게 이를 채택할 것이 분명합니다.
우리는 OpenSSL 1.1.1의 유료 연장 지원이 끝나기 전에 수용 가능한 해법이 나오길 바랍니다. 비슷한 상황은 약 22년 전 리눅스 배포판에서도 있었습니다. 스레딩 메커니즘과 라이브러리 사이에 분기가 있었고, 몇몇 배포판이 새 NPTL 커널 및 라이브러리 패치를 제공하기 시작하자 점차 모든 배포판에 채택되어 결국 표준 스레딩 라이브러리가 되었습니다. 업계도 업데이트된 TLS 라이브러리를 수용하며 길을 여는 몇몇 배포판이 필요할 것이며, 이는 다른 배포판들이 뒤따르도록 유도할 것입니다.
우리는 발표를 꾸준히 모니터링하고 구현자들과 논의에 참여해 사용자 및 고객 경험을 개선하고 있습니다. 합리적인 시간 안에, 운영체제에서 기본 제공되며 QUIC을 포함한 모든 기능을 지원하는, 효율적이고 잘 유지보수되는 라이브러리가 제공되길 기대합니다. 그런 상황이 결국 등장할 것이라는 자신감이 커지고 있으며, 예를 들어 OpenSSL이 최근 2년마다 새 LTS 버전에 대한 유지보수 사이클과 5년 지원을 발표하는 등 전반적으로 개선을 향한 움직임이 보입니다.
2025년 6월, 우리의 HAProxyConf에서 다음 업데이트를 기대해 주십시오. 그 자리에서 HAProxy의 차세대 TLS 성능 및 호환성 시대를 열어갈 것입니다.
블로그 구독하기. HAProxy 전문가들이 전하는 최신 릴리스 업데이트, 튜토리얼, 심층 분석을 받아보세요.