Tightening the allowed TLS cipher set in Nginx reduces the chance of clients negotiating weak encryption and makes security posture consistent across virtual hosts. A curated cipher list also helps meet compliance requirements by removing legacy algorithms and key exchange modes from the negotiation path.
During the TLS handshake, the client and server agree on a protocol version (for example, TLSv1.2 or TLSv1.3) and a cipher suite that both sides support. Nginx passes cipher configuration to its underlying OpenSSL (or LibreSSL) library via directives like ssl_protocols, ssl_ciphers, and ssl_prefer_server_ciphers inside the http or server context.
Cipher tuning can break connectivity when legacy clients require older protocols or non-AEAD ciphers, and certificate type influences which cipher families can actually be used (RSA vs ECDSA). ssl_ciphers primarily affects TLSv1.2 and older, while TLSv1.3 uses ciphersuites handled differently by the crypto library. Keep console or out-of-band access available for recovery, and validate configuration with nginx -t before reloading.
Related: How to secure Nginx web server
Related: How to enable OCSP stapling in Nginx
Steps to configure TLS ciphers in Nginx:
- Locate existing ssl_protocols and ssl_ciphers directives in the active Nginx configuration.
$ sudo nginx -T 2>/dev/null | grep -nE '^\s*(ssl_protocols|ssl_ciphers|ssl_prefer_server_ciphers|ssl_conf_command)\b|listen\s+443' 243: listen 443 ssl http2; 314:ssl_protocols TLSv1.2 TLSv1.3; 316:ssl_prefer_server_ciphers on; 318:ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384'; 353: # listen 443 ssl default_server; ##### snipped #####
- Select a cipher policy baseline that matches client requirements.
https://ssl-config.mozilla.org/
Disabling TLSv1.0 and TLSv1.1 blocks legacy clients, including some older Java and embedded stacks.
- Create a global TLS policy file at /etc/nginx/conf.d/tls-ciphers.conf.
$ sudo tee /etc/nginx/conf.d/tls-ciphers.conf >/dev/null <<'EOF' ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384'; ssl_ecdh_curve X25519:secp256r1:secp384r1; EOF
ssl_ciphers is an OpenSSL cipher string and mainly applies to TLSv1.2 and older, while TLSv1.3 ciphersuites are typically controlled by the crypto library defaults (or optional ssl_conf_command Ciphersuites support on newer builds).
An overly strict cipher list can lock out required clients and cause immediate handshake failures on /443.
- Confirm the TLS policy file is parsed by Nginx in the http context.
$ sudo nginx -T 2>/dev/null | grep -n 'configuration file /etc/nginx/conf.d/tls-ciphers.conf' 313:# configuration file /etc/nginx/conf.d/tls-ciphers.conf:
When the file is missing from nginx -T output, verify /etc/nginx/nginx.conf includes /etc/nginx/conf.d/*.conf inside the http block.
- Identify per-server overrides that conflict with the global cipher policy.
$ sudo nginx -T 2>/dev/null | grep -n '^\s*ssl_ciphers\b' 318:ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
server context directives override http context directives, so remove or align the older overrides to avoid unexpected negotiation.
- Validate the Nginx configuration before applying changes.
$ sudo nginx -t nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful
- Reload nginx to apply the updated cipher policy.
$ sudo systemctl reload nginx
- Confirm the nginx service remains active (running) after reload.
$ sudo systemctl status nginx --no-pager ● nginx.service - A high performance web server and a reverse proxy server Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled) Drop-In: /etc/systemd/system/nginx.service.d └─limits.conf Active: active (running) since Mon 2025-12-29 22:09:39 UTC; 30min ago ##### snipped ##### - Verify a TLSv1.2 handshake negotiates an allowed cipher.
$ openssl s_client -connect example.com:443 -servername example.com -tls1_2 -cipher 'ECDHE-RSA-AES128-GCM-SHA256' </dev/null CONNECTED(00000003) ##### snipped ##### Protocol : TLSv1.2 Cipher : ECDHE-RSA-AES128-GCM-SHA256 Verify return code: 0 (ok)
When the certificate is ECDSA-only, switch the test cipher to an ECDHE-ECDSA-* variant.
- Verify a TLSv1.3 handshake succeeds when enabled.
$ openssl s_client -connect example.com:443 -servername example.com -tls1_3 -ciphersuites 'TLS_AES_128_GCM_SHA256' </dev/null CONNECTED(00000003) ##### snipped ##### Protocol : TLSv1.3 Cipher : TLS_AES_128_GCM_SHA256 Verify return code: 0 (ok)
- Verify deprecated protocol versions fail the handshake when disabled.
$ openssl s_client -connect example.com:443 -servername example.com -tls1_1 </dev/null CONNECTED(00000003) ##### snipped ##### no peer certificate available ##### snipped ##### New, (NONE), Cipher is (NONE)
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.
