Let’s tune our cipher suites

tested with OpenSSL 1.1.1i, 3.0.0-dev and LibreSSL 3.3.1



Here, we’re setting-up a white-list of ciphers to be used, which is a better policy than a black-list. We are trying to get a scalable policy, so we are using blacklists by enabling any new ciphers that would get into library’s update and avoid the need to update NGINX’s ssl_ciphers. Besides, if you were to use whitelists, beware the cipher suites are not the same whether you have an ECDSA vs. RSA certificate(s).

Note it is possible to have dual/hybrid ECDSA+RSA key pairs available, so you got more available cipher suites the client can choose from. NGINX allows to serve multiple certificates, by simply iterating the configuration lines pointing to the cert and privkey.


To get an A+ grade on ssllabs we need:

To get 100/100 on key exchange we need:

To get 100/100 on key strength we need:

note some CBC modes can remain, you can have WEAK tagged ciphers there while still getting 100/100 for cipher strength.


Check out LibreSSL’s filters (w/o -s). The default is

openssl ciphers -V
openssl ciphers -V 'ALL:!aNULL:!eNULL'

To see it all

openssl ciphers -V ALL
openssl ciphers -V ALL:COMPLEMENTOFALL

To get some cathegorizations

openssl ciphers -V LOW
openssl ciphers -V MEDIUM
openssl ciphers -V HIGH

We could start with ALL and negate non-PFS ciphers one by one

openssl ciphers -V 'ALL:!aNULL:!eNULL:!RC4:!MD5:!3DES' | grep -v Kx=RSA
openssl ciphers -V 'ALL:!aNULL:!eNULL:!RC4:!MD5:!3DES' | grep Kx=RSA | awk '{print $3}'

but it is much more adequate to filter-in PFS-capable exchanges anyhow

openssl ciphers -V 'ECDHE:DHE:kGOST:!aNULL:!eNULL:!RC4:!MD5:!3DES'

let’s also filter out 128-bit ciphers to get 100% key strength


Also Chrome 49 / XP SP3 and Firefox 47 / Win 7 R we get

Server negotiated HTTP/2 with blacklisted suite


Ready to go

As a result we got


which gives, with LibreSSL 3.3.1

      0x13,0x02 - AEAD-AES256-GCM-SHA384  TLSv1.3 Kx=TLSv1.3  Au=TLSv1.3 Enc=AESGCM(256) Mac=AEAD
      0x13,0x03 - AEAD-CHACHA20-POLY1305-SHA256 TLSv1.3 Kx=TLSv1.3  Au=TLSv1.3 Enc=ChaCha20-Poly1305 Mac=AEAD
      0xC0,0x30 - ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH     Au=RSA  Enc=AESGCM(256) Mac=AEAD
      0xC0,0x2C - ECDHE-ECDSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AESGCM(256) Mac=AEAD
      0xC0,0x28 - ECDHE-RSA-AES256-SHA384 TLSv1.2 Kx=ECDH     Au=RSA  Enc=AES(256)  Mac=SHA384
      0xC0,0x24 - ECDHE-ECDSA-AES256-SHA384 TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AES(256)  Mac=SHA384
      0xCC,0xA9 - ECDHE-ECDSA-CHACHA20-POLY1305 TLSv1.2 Kx=ECDH     Au=ECDSA Enc=ChaCha20-Poly1305 Mac=AEAD
      0xCC,0xA8 - ECDHE-RSA-CHACHA20-POLY1305 TLSv1.2 Kx=ECDH     Au=RSA  Enc=ChaCha20-Poly1305 Mac=AEAD
      0x00,0x9F - DHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=DH       Au=RSA  Enc=AESGCM(256) Mac=AEAD
      0x00,0x6B - DHE-RSA-AES256-SHA256   TLSv1.2 Kx=DH       Au=RSA  Enc=AES(256)  Mac=SHA256
      0x00,0x39 - DHE-RSA-AES256-SHA      SSLv3 Kx=DH       Au=RSA  Enc=AES(256)  Mac=SHA1
      0xCC,0xAA - DHE-RSA-CHACHA20-POLY1305 TLSv1.2 Kx=DH       Au=RSA  Enc=ChaCha20-Poly1305 Mac=AEAD
      0x00,0xC4 - DHE-RSA-CAMELLIA256-SHA256 TLSv1.2 Kx=DH       Au=RSA  Enc=Camellia(256) Mac=SHA256
      0x00,0x88 - DHE-RSA-CAMELLIA256-SHA SSLv3 Kx=DH       Au=RSA  Enc=Camellia(256) Mac=SHA1
      0xFF,0x85 - GOST2012256-GOST89-GOST89 SSLv3 Kx=GOST     Au=GOST01 Enc=GOST-28178-89-CNT Mac=GOST89IMIT
      0x00,0x81 - GOST2001-GOST89-GOST89  SSLv3 Kx=GOST     Au=GOST01 Enc=GOST-28178-89-CNT Mac=GOST89IMIT

Lax defaults

Define sane defaults for SMTP (TLS v1.0 but only 256-bit ciphers), even though we define them yet again in Postfix

cp -pi /etc/ssl/openssl.cnf /etc/ssl/openssl.cnf.dist
vi /etc/ssl/openssl.cnf

ssl_conf = ssl_sect

system_default = system_default_sect

MinProtocol = TLSv1.0
MinProtocol = DTLSv1.0
CipherString = ECDHE:DHE:kGOST:!aNULL:!eNULL:!RC4:!MD5:!3DES:!AES128:!CAMELLIA128
    Ciphersuites = TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256

and further tune protocols and cipher strings per daemon and overriding the defaults

acceptance tests should return

Key exchange 100/100

And if you really want 100/100 for key exchange

openssl dhparam 4096 > /etc/ssl/dhparam2.pem
cat /etc/ssl/dhparam2.pem
#-rand /dev/urandom

vi nginx.conf

    ssl_dhparam /etc/ssl/dhparam2.pem;
    ssl_ecdh_curve secp384r1;
    #ssl_ecdh_curve secp384r1:secp521r1;
    # prime256v1, secp384r1, secp521r1, X25519, X448

Acceptance test results

We can affort to have a few WEAK tagged ciphers – we still get 100/100 cipher strength anyway.

# TLS 1.2 (server has no preference)
TLS_DHE_RSA_WITH_AES_256_CBC_SHA (0x39)   DH 4096 bits   FS   WEAK      256
TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA (0x88)   DH 4096 bits   FS   WEAK 256
TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 (0x6b)   DH 4096 bits   FS   WEAK   256
TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 (0x9f)   DH 4096 bits   FS  256
TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256 (0xc4)   DH 4096 bits   FS   WEAK      256
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 (0xc024)   ECDH secp384r1 (eq. 7680 bits RSA)   FS   WEAK       256
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 (0xc028)   ECDH secp384r1 (eq. 7680 bits RSA)   FS   WEAK 256
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 (0xc02c)   ECDH secp384r1 (eq. 7680 bits RSA)   FS      256
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)   ECDH secp384r1 (eq. 7680 bits RSA)   FS        256
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (0xcca8)   ECDH secp384r1 (eq. 7680 bits RSA)   FS  256
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 (0xcca9)   ECDH secp384r1 (eq. 7680 bits RSA)   FS        256
TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (0xccaa)   DH 4096 bits   FS  256

You can also check yourself remotely

nmap -sV --script ssl-enum-ciphers -p 443 nethence.com

443/tcp open  ssl/http nginx 1.19.6
|_http-server-header: nginx/1.19.6
| ssl-enum-ciphers:
|   TLSv1.2:
|     ciphers:
|       TLS_DHE_RSA_WITH_AES_256_CBC_SHA (dh 4096) - A
|       TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 (dh 4096) - A
|       TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 (dh 4096) - A
|       TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA (dh 4096) - A
|       TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256 (dh 4096) - A
|       TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (dh 4096) - A
|       TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 (secp384r1) - A
|       TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 (secp384r1) - A
|       TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 (secp384r1) - A
|       TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 (secp384r1) - A
|       TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (secp384r1) - A
|       TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (secp384r1) - A
|     compressors:
|       NULL
|     cipher preference: client
|_  least strength: A

TLS v1.3

tested with OpenSSL 1.1.1i

in case you absolutely want to disregard the 128-bit cipher with tls13, you need to tune it there for now

vi /etc/ssl/openssl.cnf

ssl_conf = ssl_sect

system_default = system_default_sect

MinProtocol = TLSv1.2
    MinProtocol = DTLSv1.2
    CipherString = ECDHE:DHE:kGOST:!aNULL:!eNULL:!RC4:!MD5:!3DES:!AES128:!CAMELLIA128
Ciphersuites = TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256

# Options = ServerPreference,NoRenegotiation
# CipherString = ALL:!aNULL:!eNULL

and you need to FULLY RESTART NGINX for this to take action

ps auxfww | grep nginx | grep -v grep
pkill nginx && nginx
ps auxfww | grep nginx | grep -v grep



Cipher suite https://en.wikipedia.org/wiki/Cipher_suite

SSL Server Rating Guide https://github.com/ssllabs/research/wiki/SSL-Server-Rating-Guide

SSL Labs: Stricter Security Requirements for 2014 https://blog.qualys.com/product-tech/2014/01/21/ssl-labs-stricter-security-requirements-for-2014

dual cert

RSA and ECDSA hybrid Nginx setup with LetsEncrypt certificates generated through Docker image https://medium.com/hackernoon/rsa-and-ecdsa-hybrid-nginx-setup-with-letsencrypt-certificates-ee422695d7d3


Official portable version of LibreSSL https://github.com/libressl-portable/portable

tls13 API not available yet https://github.com/libressl-portable/portable/blob/master/ChangeLog


SSL Server Test https://www.ssllabs.com/ssltest/

CryptCheck https://tls.imirhil.fr/

testssl.sh https://github.com/drwetter/testssl.sh

SSLyze https://github.com/nabla-c0d3/sslyze


HTTPS Cheat Sheet https://scotthelme.co.uk/https-cheat-sheet/

Cipherli.st Strong Ciphers for Apache, nginx and Lighttpd https://cipherli.st/

Applied Crypto Hardening https://bettercrypto.org/

SSL Configuration Generator https://ssl-config.mozilla.org/

File ssl-enum-ciphers https://nmap.org/nsedoc/scripts/ssl-enum-ciphers.html


openssl-ciphers, ciphers - SSL cipher display and cipher list tool https://www.openssl.org/docs/man1.1.1/man1/ciphers.html

SSL and TLS Deployment Best Practices https://github.com/ssllabs/research/wiki/SSL-and-TLS-Deployment-Best-Practices#23-use-secure-cipher-suites

TLS Cipher String ยท OWASP Cheat Sheet Series https://cheatsheetseries.owasp.org/cheatsheets/TLS_Cipher_String_Cheat_Sheet.html

Mapping OpenSSL Cipher Suite Names to Official Names and RFCs https://www.woolie.co.uk/article/mapping-openssl-ciphers-to-iana-cipher-suite-registy/

A list of recommended cipher suites for TLS 1.2 https://www.peerlyst.com/posts/a-list-of-recommended-cipher-suites-for-tls-1-2-guurhart


SSL Labs Grading Update: Forward Secrecy, Authenticated Encryption and ROBOT https://blog.qualys.com/ssllabs/2018/02/02/forward-secrecy-authenticated-encryption-and-robot-grading-update

[stunnel-users] STUNNEL — How to chose the AES cipher with TLS v1.2 https://www.stunnel.org/pipermail/stunnel-users/2013-February/004112.html

Cipher Security: How to harden TLS and SSH https://www.linuxjournal.com/content/cipher-security-how-harden-tls-and-ssh

ciphers https://www.openssl.org/docs/manmaster/man1/ciphers.html

Security/Server Side TLS https://wiki.mozilla.org/Security/Server_Side_TLS

Recommendations for TLS/SSL Cipher Hardening https://securityboulevard.com/2018/04/recommendations-for-tls-ssl-cipher-hardening/

Recommendations: SSL/TLS Protocols and Cipher Suites https://grok.lsu.edu/Article.aspx?articleid=17596

Recommendations for TLS/SSL Cipher Hardening https://www.acunetix.com/blog/articles/tls-ssl-cipher-hardening/ SSL!


cloudflare’s nginx ssl setup https://github.com/cloudflare/sslconfig/blob/master/conf

ciphers - SSL cipher display and cipher list tool. https://github.com/libressl/libressl/blob/master/src/doc/apps/ciphers.pod

Cipher String Syntax in nginx https://serverfault.com/questions/635189/cipher-string-syntax-in-nginx

CIPHER LIST FORMAT https://www.openssl.org/docs/manmaster/man1/openssl-ciphers.html#CIPHER-LIST-FORMAT

CIPHER LIST FORMAT https://www.openssl.org/docs/man1.1.1/man1/ciphers.html#CIPHER-LIST-FORMAT

SSL_CONF_cmd_value_type, SSL_CONF_cmd - send configuration command https://www.openssl.org/docs/man1.1.1/man3/SSL_CONF_cmd.html

config - OpenSSL CONF library configuration files https://www.openssl.org/docs/man1.1.1/man5/config.html

x509v3_config - X509 V3 certificate extension configuration format https://www.openssl.org/docs/man1.1.1/man5/x509v3_config.html

we’re better than that https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/TLS_Cipher_String_Cheat_Sheet.md

naming ciphers

Transport Layer Security (TLS) Parameters https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-4

Mapping OpenSSL cipher suite names to IANA names https://testssl.sh/openssl-iana.mapping.html

Security/Cipher Suites https://wiki.mozilla.org/Security/Cipher_Suites

Authenticated encryption https://en.wikipedia.org/wiki/Authenticated_encryption








tls13 order

Undocumented openssl.cnf options and PrioritizeChaCha https://blog.germancoding.com/2020/05/30/undocumented-openssl-cnf-options-and-prioritizechacha/

Using TLS1.3 With OpenSSL https://www.openssl.org/blog/blog/2018/02/08/tlsv1.3/

Prefer ChaCha20-Poly1305 in TLS 1.3 with nginx https://blog.pinterjann.is/chacha20-tls13-openssl.html

Unable to configure or disable TLS 1.3 via openssl.cnf https://bugs.launchpad.net/ubuntu/+source/openssl/+bug/1832370

Could not configure TLS1.3 ciphers in OpenSSL 1.1.1 pre4 https://trac.nginx.org/nginx/ticket/1529

Set ciphersuite list order for TLS 1.3 https://trac.nginx.org/nginx/ticket/1878


Guide to Deploying Diffie-Hellman for TLS https://weakdh.org/sysadmin.html

Copyright © 2024 Pierre-Philipp Braun