Controlling TLS cipher suites in Apache reduces exposure to weak cryptography, helps meet compliance baselines, and makes scanner results consistent across environments.
HTTPS in Apache is provided by mod_ssl, which uses OpenSSL to negotiate a protocol version and cipher suite during the TLS handshake. SSLCipherSuite controls the cipher list for TLS 1.2 and earlier, while TLS 1.3 cipher suites are configured separately through SSLOpenSSLConfCmd Ciphersuites when the OpenSSL build supports it.
Cipher hardening is safest when applied at the TLS virtual host level (inside `<VirtualHost *:443>`) and rolled out with syntax checks plus live negotiation tests. A too-strict policy can block legacy clients and upstream proxies, and a syntax error can prevent the apache2 service from reloading, so a rollback path and console access reduce the impact of accidental lockouts.
Related: How to test your Apache configuration
Related: How to enable or disable Apache modules
Steps to configure TLS ciphers in Apache:
- Show the active virtual host file that serves HTTPS on port 443.
$ sudo apache2ctl -S VirtualHost configuration: *:80 is a NameVirtualHost default server host.example.net (/etc/apache2/sites-enabled/000-default.conf:1) port 80 namevhost host.example.net (/etc/apache2/sites-enabled/000-default.conf:1) port 80 namevhost host.example.net (/etc/apache2/sites-enabled/host.example.net.conf:1) *:443 host.example.net (/etc/apache2/sites-enabled/host.example.net.conf:6) ServerRoot: "/etc/apache2" Main DocumentRoot: "/var/www/html" Main ErrorLog: "/var/log/apache2/error.log" Mutex ssl-stapling: using_defaults Mutex ssl-cache: using_defaults Mutex default: dir="/var/run/apache2/" mechanism=default ##### snipped #####On RHEL-family systems, use apachectl -S and expect paths under /etc/httpd/.
- List the cipher names supported by the local OpenSSL build to avoid typos in SSLCipherSuite.
$ openssl ciphers -v 'ECDHE:!aNULL:!eNULL:!MD5:!3DES' TLS_AES_256_GCM_SHA384 TLSv1.3 Kx=any Au=any Enc=AESGCM(256) Mac=AEAD TLS_CHACHA20_POLY1305_SHA256 TLSv1.3 Kx=any Au=any Enc=CHACHA20/POLY1305(256) Mac=AEAD TLS_AES_128_GCM_SHA256 TLSv1.3 Kx=any Au=any Enc=AESGCM(128) Mac=AEAD ECDHE-ECDSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AESGCM(256) Mac=AEAD ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(256) Mac=AEAD ECDHE-ECDSA-CHACHA20-POLY1305 TLSv1.2 Kx=ECDH Au=ECDSA Enc=CHACHA20/POLY1305(256) Mac=AEAD ECDHE-RSA-CHACHA20-POLY1305 TLSv1.2 Kx=ECDH Au=RSA Enc=CHACHA20/POLY1305(256) Mac=AEAD ##### snipped #####
SSLCipherSuite uses OpenSSL cipher string syntax, including colon-separated lists and selection rules.
- Add the TLS protocol and cipher policy directives inside the `<VirtualHost *:443>` block for the HTTPS site.
<IfModule mod_ssl.c> <VirtualHost *:443> ServerName host.example.net SSLEngine On SSLProtocol -all +TLSv1.2 +TLSv1.3 SSLHonorCipherOrder On SSLCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256 SSLOpenSSLConfCmd Ciphersuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256 SSLCertificateFile /etc/apache2/ssl/apache.crt SSLCertificateKeyFile /etc/apache2/ssl/apache.key </VirtualHost> </IfModule>Disabling TLS 1.0/1.1 or removing older cipher families can break legacy clients, older JVMs, and some upstream devices.
If apache2ctl -t reports an unknown protocol for TLSv1.3, remove +TLSv1.3 and keep only +TLSv1.2 for that host.
SSLOpenSSLConfCmd Ciphersuites is ignored on builds without TLS 1.3 support, and SSLCipherSuite does not affect TLS 1.3.
An “Invalid command 'SSLCipherSuite'” error usually indicates mod_ssl is not loaded.
Global defaults can be set in /etc/apache2/mods-available/ssl.conf when consistent policy across sites is preferred.
- Validate the Apache configuration syntax.
$ sudo apache2ctl -t Syntax OK
No output other than Syntax OK indicates the configuration is parseable.
- Reload apache2 to apply the new cipher policy.
$ sudo systemctl reload apache2
Use a reload for config-only changes, since existing connections typically remain established.
- Confirm a TLS 1.2 handshake negotiates an allowed cipher suite.
$ echo | openssl s_client -connect host.example.net:443 -servername host.example.net -tls1_2 CONNECTED(00000003) ##### snipped ##### New, TLSv1.2, Cipher is ECDHE-RSA-AES256-GCM-SHA384 ##### snipped ##### SSL-Session: Protocol : TLSv1.2 Cipher : ECDHE-RSA-AES256-GCM-SHA384 ##### snipped ##### - Confirm a TLS 1.3 handshake negotiates a cipher suite from Ciphersuites.
$ echo | openssl s_client -connect host.example.net:443 -servername host.example.net -tls1_3 CONNECTED(00000003) ##### snipped ##### New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384 ##### snipped ##### Verify return code: 0 (ok) ##### snipped #####
- Enumerate offered ciphers from another host to verify ordering and protocol coverage.
$ nmap --script ssl-enum-ciphers -p 443 host.example.net Starting Nmap 7.94SVN ( https://nmap.org ) at 2026-01-10 20:19 +08 Nmap scan report for host.example.net (192.0.2.40) Host is up (0.000087s latency). rDNS record for 192.0.2.40: host PORT STATE SERVICE 443/tcp open https | ssl-enum-ciphers: | TLSv1.2: | ciphers: | TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (ecdh_x25519) - A | TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (ecdh_x25519) - A | TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (ecdh_x25519) - A | compressors: | NULL | cipher preference: server | TLSv1.3: | ciphers: | TLS_AKE_WITH_AES_256_GCM_SHA384 (ecdh_x25519) - A | TLS_AKE_WITH_CHACHA20_POLY1305_SHA256 (ecdh_x25519) - A | TLS_AKE_WITH_AES_128_GCM_SHA256 (ecdh_x25519) - A | cipher preference: server |_ least strength: A
The cipher preference: server line confirms SSLHonorCipherOrder On is being honored for TLS 1.2.
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.
