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' 64: listen 443 ssl http2; 71: ssl_protocols TLSv1.2 TLSv1.3; 72: ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384; ##### 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' 189:# 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' 72: 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'; 310: ssl_ciphers 'HIGH:!aNULL:!MD5'; ##### snipped #####
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 (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled) Active: active (running) since Sun 2025-12-14 10:02:11 UTC; 8s ago ##### snipped ##### - Verify a TLSv1.2 handshake negotiates an allowed cipher.
$ openssl s_client -connect your.domain.tld:443 -servername your.domain.tld -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 your.domain.tld:443 -servername your.domain.tld -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 your.domain.tld:443 -servername your.domain.tld -tls1_1 </dev/null CONNECTED(00000003) ##### snipped ##### no peer certificate available ##### snipped #####
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.
Comment anonymously. Login not required.
