TLS termination: stunnel, nginx & stud, round 2

Vincent Bernat

A month ago, I pub­lished an ar­ti­cle on the com­pared per­for­mance of stun­nel, ng­inx and stud as TLS ter­mi­na­tors. The con­clu­sion was to use stud on a 64-bit sys­tem, with ses­sion caching and AES. stun­nel was un­able to scale prop­erly and ng­inx ex­hib­ited im­por­tant la­tency is­sues. I got con­struc­tive com­ments on many as­pects. There­fore, here is the sec­ond round. The pro­tag­o­nists are the same but both the con­di­tions and the con­clu­sions have changed a bit.

Up­date (2016-01)

stud is now un­main­tained. It has been forked by the Var­nish team and is now called hitch. How­ever, I would rather con­sider HAProxy. It now fea­tures TLS sup­port, sup­plied by one of the ma­jor con­trib­u­tors of stud.

Benchmarks#

The hard­ware plat­form re­mains un­changed:

We fo­cus on the fol­low­ing en­vi­ronment:

  • Ubuntu Lu­cid, fea­tur­ing a patched ver­sion of OpenSSL 0.9.8k with AES-NI builtin and en­abled by default
  • 2.6.39 ker­nel (250 HZ)
  • 64-bit
  • 1 core for HAProxy, 2 cores for net­work cards,1 4 cores for TLS, 1 core for the system
  • sys­tem limit of 100,000 file de­scrip­tors per process

On the TLS side, the main com­po­nents are:

  • 2048-bit cer­tificates
  • AES128-SHA ci­pher suite
  • 8 HTTP/1.0 re­quests per client
  • HTTP re­sponse pay­load of 1024 bytes

If you are in­ter­ested in dif­fer­ent con­di­tions (32-bit, num­ber of cores, 1024-bit cer­tifi­cates, pro­por­tion of ses­sion reuses), have a look at the pre­vi­ous ar­ti­cle whose re­sults on the mat­ter are still valid. I only con­sider 2048-bit cer­tifi­cates be­cause most cer­tifi­cates is­sued to­day are 2048-bit. In SP800-57, NIST ad­vises that 1024-bit RSA keys will no longer be vi­able af­ter 2010 and ad­vises mov­ing to 2048-bit RSA keys:

If in­for­ma­tion is ini­tially signed in 2009 and needs to re­main se­cure for a max­i­mum of ten years (i.e., from 2009 to 2019), a 1024-bit RSA key would not pro­vide suf­fi­cient pro­tec­tion be­tween 2011 and 2019 and, there­fore, it is not rec­om­mended that 1024-bit RSA be used in this case.

And the pro­tag­o­nists are:

Up­date (2011-10)

Con­fig­u­ra­tion of ng­inx has been up­dated to switch to off both multi_accept and accept_mutex thanks to the feed­back of Maxim Dounin on la­tency is­sues that were present in the pre­vi­ous ar­ti­cle (and still present in the early ver­sion of this article).

HAProxy (con­fig­u­ra­tion) is used for stun­nel and stud to add load-balancing.

For each test, we want to max­i­mize the num­ber of HTTP trans­ac­tions per sec­ond (TPS) while min­i­miz­ing la­tency and un­suc­cess­ful trans­ac­tions. The max­i­mum num­ber of al­lowed failed trans­ac­tions is set at 1 trans­ac­tion out of 1000. The max­i­mum al­lowed av­er­age la­tency is 100 ms.2

Control case#

Be­low is the first com­par­i­son be­tween stud, ng­inx and stun­nel on plain Ubuntu Lu­cid. Like in pre­vi­ous bench­marks, stun­nel is not able to scale, with a max­i­mum of 400 TPS. How­ever, early la­tency is­sues of ng­inx have been solved, thanks to the change in con­fig­u­ra­tion. stud and ng­inx get sim­i­lar per­for­mance but, as you can see on the bot­tom plot (whose scale is log­a­rith­mic) stud starts with an ini­tial la­tency of 40 ms.

This is pretty an­noy­ing. I have tried some mi­cro op­ti­miza­tion to solve this is­sue but with­out luck. If la­tency mat­ters for you, stick with stun­nel or ng­inx (or find where the prob­lem lies in stud).

Up­date (2011-10)

Af­ter re­port­ing this is­sue, a fix has been com­mited. The cul­prit was Na­gle’s al­go­rithm which im­proves band­width at the ex­pense of la­tency. With the fix, per­for­mance is mostly the same but there is no la­tency penalty any more.

stud, nginx, stunnel on Ubuntu Lucid
stud, nginx and stunnel running on Ubuntu Lucid

OpenSSL 1.0.0e#

When I pub­lished the first round of bench­marks, Michał Tro­j­nara was du­bi­ous about the bad re­sults of stun­nel. He told me on Twitter:

I solved it! You com­piled stun­nel against a 4 years old OpenSSL with MT-un­safe SSL_accept. Up­grade OpenSSL and re­build stun­nel. This bug was fixed in OpenSSL 1.0.0b. I im­ple­mented a (slow) work­around, so stun­nel doesn’t crash with ear­lier ver­sions of OpenSSL.

There­fore, I back­ported Ubuntu Oneiric OpenSSL 1.0.0e to Ubuntu Lu­cid3 and, as you can see your­self on the plot be­low, there is a huge per­for­mance boost for stun­nel: about 400%! stud fea­tures a slight im­prove­ment too (about 8%) but still starts with a la­tency of 40 ms while ng­inx does not improve.

stud, nginx, stunnel with OpenSSL 1.0.0e
stud, nginx and stunnel performances with OpenSSL 1.0.0e from Ubuntu Oneiric

Intel Accelerator Engine#

Most Linux dis­tri­bu­tions are still us­ing OpenSSL 0.9.8. For ex­am­ple, De­bian Squeeze ships with OpenSSL 0.9.8o, Ubuntu Lu­cid with 0.9.8k and RHEL 5.7 with 0.9.8e. There­fore, they are not el­i­gi­ble for re­cent per­for­mance im­prove­ments. To solve this, In­tel re­leased In­tel Ac­cel­er­a­tor En­gine, which also brings, among other things, AES-NI support:

This is es­sen­tially a col­lec­tion of as­sem­bler mod­ules from de­vel­op­ment OpenSSL branch, packed to­gether as a stand­alone en­gine. “Stand­alone” means that it can be com­piled out­side OpenSSL source tree with­out patch­ing the lat­ter. Idea is to pro­vide a way to uti­lize new code in al­ready re­leased OpenSSL ver­sions con­strained by sup­port and re­lease poli­cies lim­it­ing their evolve­ment to gen­uine bug fixes.

Alas, in my tests, there was no im­prove­ment com­pared to OpenSSL 1.0.0e (al­ready patched with AES-NI support).

stun­nel comes with out-of-the-box sup­port for ad­di­tional en­gines. Just add some­thing like this to your stunnel.conf:

engine=dynamic
engineCtrl=SO_PATH:/WOO/intel-accel-1.4/libintel-accel.so
engineCtrl=ID:intel-accel
engineCtrl=LIST_ADD:1
engineCtrl=LOAD
engineCtrl=INIT

For stud, you should grab sup­port for en­gine se­lec­tion from GitHub and for ng­inx, spec­ify the en­gine with ssl_engine.

Intel Performance Primitives#

In his great ar­ti­cle “Ac­cel­er­ated SSL,” Si­mon Kuhn shows huge per­for­mance im­prove­ments by us­ing In­tel’s IPP li­brary, a li­brary aimed at lever­ag­ing power of multi-core pro­ces­sors and in­clud­ing some cryp­to­graphic prim­i­tives and a patch to make use of them in OpenSSL. Un­for­tu­nately, this li­brary is not free, not even as in “free beer.” The patch pro­vided by In­tel only ap­plies on OpenSSL 0.9.8j. Si­mon up­dated it for OpenSSL 1.0.0d. It also ap­plies with a slight mod­i­fi­ca­tion to OpenSSL 1.0.0e. Look at Si­mon’s ar­ti­cle for in­struc­tions on how to com­pile OpenSSL with IPP.

Si­mon mea­sured 220% im­prove­ment on RSA 1024-bit pri­vate key op­er­a­tions by us­ing this li­brary. Un­for­tu­nately, we do not get such a per­for­mance burst with stud which ex­hibits a 15% im­prove­ment (but re­mem­ber that pri­vate key op­er­a­tions only hap­pen once ev­ery 8 trans­ac­tions thanks to ses­sion re­use). Worse, with stun­nel, per­for­mance is re­duced by one third and ng­inx just seg­faults. I think such a li­brary is too risky to use in pro­duction.

stud and stunnel with IPP
stud and stunnel performances with IPP library

Hyperthreading#

Dur­ing the pre­vi­ous tests, only half the ca­pac­ity of each core was used. For stud, adding more work­ers per core only slightly im­proves this sit­u­a­tion (less than 10% with 4 work­ers per core). There­fore, I thought us­ing hy­per­thread­ing could rec­tify the sit­u­a­tion here. In this case, stud, stun­nel and ng­inx are al­lo­cated 8 “vir­tual” cores in­stead of 4. stud only got a small im­prove­ment of 5%, ng­inx a mod­er­ate im­prove­ment of 11% while stun­nel got a sig­nif­i­cant im­prove­ment of 26%.

stud, stunnel and nginx with hyperthreading
stud, stunnel and nginx performances with hyperthreading enabled

Conclusion#

Here is a quick re­cap of the max­i­mum TPS reached on each test (while keep­ing av­er­age re­sponse time be­low 100 ms). All tests were run on 4 phys­i­cal cores with 2048-bit cer­tifi­cates, AES128-SHA ci­pher suite. Each client makes 8 HTTP re­quests and re­use the first TLS session.

Context nginx 1.1.4 stunnel 4.44 stud @e207dbc5a3
OpenSSL 0.9.8k 1489 TPS 400 TPS 1492 TPS
OpenSSL 1.0.0e 1501 TPS 1561 TPS 1609 TPS
OpenSSL 1.0.0e + IPP - 1101 TPS 1857 TPS
OpenSSL 1.0.0e + HT 1718 TPS 1973 TPS 1694 TPS

I also would like to stress that be­low 1500 TPS, stun­nel adds an over­head of a few mil­lisec­onds while stud adds 40 mil­lisec­onds. With OpenSSL 1.0.0e and hy­per­thread­ing en­abled, stun­nel has both good per­for­mance and low la­tency on low loads to mod­er­ate loads. You may just want to limit the num­ber of al­lowed con­nec­tions to avoid col­laps­ing on high loads. While in the pre­vi­ous round, stud was the cham­pion and stun­nel lagged be­hind, the sit­u­a­tion is now re­versed: stun­nel is on top!

Up­date (2011-12)

As said ear­lier, the la­tency is­sue with stud has been solved. Per­for­mance re­mains mostly the same. Here is what the lat­est plot looks like af­ter the fix:

stud, stunnel and nginx with hyperthreading with stud fixed
stud, stunnel and nginx performances with hyperthreading enabled and with Nagle algorithm disabled for stud