Configuring the TLS cipher policy in Apache limits which encryption suites clients can negotiate, which helps remove weak legacy choices, align HTTPS endpoints with current scanner baselines, and make certificate and proxy rollouts more predictable.

HTTPS in Apache is handled by mod_ssl on top of OpenSSL. On current Apache HTTP Server 2.4 builds, SSLCipherSuite can set the cipher list for TLS 1.2 and earlier and, when Apache is linked against OpenSSL 1.1.1 or later, a separate SSLCipherSuite TLSv1.3 … line can set the TLS 1.3 suites directly.

Examples below use the Debian and Ubuntu layout with /etc/apache2/, the apache2 systemd unit, and the apache2ctl wrapper. RHEL-family systems usually use /etc/httpd/ plus apachectl or httpd instead. Restricting protocols or ciphers can block older clients and upstream devices, and the negotiated suite still depends on the server certificate type, so syntax tests plus live TLS handshakes are part of the change.

Steps to configure TLS ciphers in Apache:

  1. Show the active HTTPS virtual host file before editing it.
    $ sudo apache2ctl -S
    VirtualHost configuration:
    *:443                  host.example.net (/etc/apache2/sites-enabled/host.example.net.conf:1)
    ServerRoot: "/etc/apache2"
    Main DocumentRoot: "/var/www/html"
    Main ErrorLog: "/var/log/apache2/error.log"
    Mutex ssl-stapling-refresh: using_defaults
    Mutex ssl-stapling: using_defaults
    Mutex ssl-cache: using_defaults
    Mutex default: dir="/var/run/apache2/" mechanism=default
    PidFile: "/var/run/apache2/apache2.pid"
    ##### snipped #####

    Use sudo apachectl -S or sudo httpd -S on platforms that do not ship apache2ctl.

  2. List the TLS 1.2 cipher names supported by the local OpenSSL build so the configured list matches real library names.
    $ openssl ciphers -s -v -tls1_2 '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'
    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
    ECDHE-ECDSA-AES128-GCM-SHA256  TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AESGCM(128)            Mac=AEAD
    ECDHE-RSA-AES128-GCM-SHA256    TLSv1.2 Kx=ECDH     Au=RSA   Enc=AESGCM(128)            Mac=AEAD

    The -tls1_2 filter keeps this check aligned with the SSLCipherSuite SSL … line used later. TLS 1.3 uses separate suite names.

    The negotiated TLS 1.2 suite must match the server certificate type. A host using an RSA certificate will negotiate the RSA entries from the list, not the ECDSA entries.

  3. Open the active HTTPS virtual host file.
    $ sudo vi /etc/apache2/sites-available/host.example.net.conf

    Global policy can live in /etc/apache2/mods-available/ssl.conf or the equivalent httpd SSL config when the same cipher rules must apply to every HTTPS virtual host.

  4. Set the protocol and cipher policy inside the `<VirtualHost *:443>` block.
    <IfModule mod_ssl.c>
    <VirtualHost *:443>
        ServerName host.example.net
    
        SSLEngine On
        SSLProtocol -all +TLSv1.2 +TLSv1.3
        SSLHonorCipherOrder On
    
        SSLCipherSuite SSL 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
        SSLCipherSuite TLSv1.3 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>

    Removing TLS 1.0 or TLS 1.1 support and narrowing the cipher list can break older browsers, older Java runtimes, load balancers, or API clients.

    If apache2ctl configtest reports an unknown protocol or rejects the TLS 1.3 line, remove +TLSv1.3 and the SSLCipherSuite TLSv1.3 … directive and keep the TLS 1.2 policy only.

    Current Apache 2.4 builds can configure TLS 1.3 suites directly with SSLCipherSuite TLSv1.3 …. Older examples often use SSLOpenSSLConfCmd Ciphersuites instead.

    An “Invalid command 'SSLCipherSuite'” error usually means mod_ssl is not loaded.

  5. Validate the updated Apache configuration.
    $ sudo apache2ctl configtest
    Syntax OK

    sudo apache2ctl -t performs the same syntax check. Use sudo apachectl -t or sudo httpd -t on other platforms.

  6. Reload Apache so new connections use the updated cipher policy.
    $ sudo systemctl reload apache2

    When systemd is not managing the service, use sudo apache2ctl graceful or the platform-equivalent reload command.

  7. Confirm a TLS 1.2 handshake negotiates one of the configured TLS 1.2 suites.
    $ echo | openssl s_client -connect host.example.net:443 -servername host.example.net -tls1_2 2>/dev/null | sed -n '/New, /p;/Protocol  :/p;/Cipher    :/p'
    New, TLSv1.2, Cipher is ECDHE-RSA-AES256-GCM-SHA384
        Protocol  : TLSv1.2
        Cipher    : ECDHE-RSA-AES256-GCM-SHA384

    With an RSA certificate, seeing an ECDHE-RSA-… suite here is expected even when ECDSA suites appear earlier in the configured list.

  8. Confirm a TLS 1.3 handshake negotiates one of the configured TLS 1.3 suites.
    $ echo | openssl s_client -connect host.example.net:443 -servername host.example.net -tls1_3 2>/dev/null | sed -n '/New, /p;/Protocol  :/p;/Cipher    :/p'
    New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
        Protocol  : TLSv1.3
        Cipher    : TLS_AES_256_GCM_SHA384

    Available TLS 1.3 suites are limited to the standard names exposed by the linked OpenSSL library.

  9. Scan the public endpoint from another host to confirm the offered suites and server-side order.
    $ nmap --script ssl-enum-ciphers -p 443 host.example.net
    Starting Nmap 7.94SVN ( https://nmap.org ) at 2026-04-08 04:34 UTC
    Nmap scan report for host.example.net (192.0.2.40)
    Host is up (0.00017s latency).
    
    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 the server is choosing the preferred suite order for the negotiated set.

    nmap uses TLS_AKE_WITH_* labels for TLS 1.3 suites, which correspond to the configured TLS_AES_* and TLS_CHACHA20_* names.