Server Ciphers

Posted on 7 2026

A TLS connection negotiates two things before any data flows: the protocol version (TLS 1.2, TLS 1.3) and the cipher suite (the specific algorithms used for key exchange, authentication, and bulk encryption). The server publishes a list of what it accepts. The client publishes what it supports. They agree on the best option they share.

The problem is that “best option they share” depends entirely on what you put in the list. An old default might include cipher suites that have known weaknesses, or protocol versions that should have been retired years ago. This article sets the list correctly for February, once, as shared nginx configuration, so every service that runs behind nginx inherits it automatically.

What is not covered here

This article covers the cipher and protocol configuration for nginx specifically. Other services on February that handle their own TLS (Postfix, Mosquitto, MariaDB) are configured separately in their own articles. The principle is the same, but the configuration syntax differs.

The reference: Mozilla Intermediate

Mozilla maintains a set of TLS configuration profiles, updated regularly, for web servers, mail servers, databases, and other software. The profiles are: Modern (TLS 1.3 only), Intermediate (TLS 1.2 and 1.3), and Old (legacy compatibility for ancient clients that nobody should be running anymore).

For February, the Intermediate profile is the correct choice. It supports every modern client while still accepting connections from anything released in roughly the last five years. The Modern profile (TLS 1.3 only) is more restrictive than necessary for a homelab that may have older devices connecting to it.

The Mozilla SSL Configuration Generator at ssl-config.mozilla.org produces ready-to-use configurations for nginx, Apache, HAProxy, and others. What follows is the nginx Intermediate configuration, explained and adapted for this series.

The shared TLS configuration file

Rather than repeating TLS settings in every nginx server block, define them once in a shared include file. nginx’s include mechanism pulls this into any server block that needs it.

Create the directory and file:

sudo mkdir -p /etc/nginx/tls
sudo tee /etc/nginx/tls/tls.conf << 'EOF'
#
# Shared TLS configuration
# Mozilla Intermediate profile
# nginx / OpenSSL
#
# Reference: https://ssl-config.mozilla.org/
#

# Protocols: TLS 1.2 and TLS 1.3 only.
# TLS 1.0 and 1.1 are deprecated and disabled by Ubuntu's OpenSSL
# defaults, but being explicit here makes the policy clear.
ssl_protocols TLSv1.2 TLSv1.3;

# TLS 1.2 cipher suites: ECDHE only, AEAD only.
# TLS 1.3 cipher suites are not configured here; nginx uses the OpenSSL
# defaults for TLS 1.3, which are secure. See ssl_conf_command below for
# explicit TLS 1.3 cipher configuration on nginx >= 1.19.4.
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305;

# Do not prefer server cipher ordering.
# All ciphers in the list above are strong, so the client can choose
# based on what its hardware accelerates best. Mobile devices without
# AES-NI will prefer ChaCha20-Poly1305; servers with AES-NI will prefer
# AES-GCM. Both are fine.
ssl_prefer_server_ciphers off;

# Elliptic curves for ECDHE key exchange.
# X25519 is faster and preferred; prime256v1 and secp384r1 are widely
# supported fallbacks.
ssl_ecdh_curve X25519:prime256v1:secp384r1;

# Session cache: shared across all worker processes.
# 10MB holds approximately 40,000 sessions.
ssl_session_cache shared:MozSSL:10m;
ssl_session_timeout 1d;

# Session tickets: disabled.
# Session tickets reuse encryption keys that do not rotate automatically,
# which undermines forward secrecy. Disabled in favour of the session cache.
ssl_session_tickets off;

# OCSP stapling: improves handshake performance by embedding the
# certificate revocation status in the TLS handshake, so clients do not
# need to make a separate request to the CA's OCSP responder.
# Note: OCSP stapling has no effect on Let's Encrypt certificates because
# Let's Encrypt does not operate a traditional OCSP responder.
ssl_stapling on;
ssl_stapling_verify on;

# Resolver for OCSP stapling lookups.
# Using the local Unbound resolver once it is configured. Fall back to
# a public resolver temporarily during initial setup.
resolver 127.0.0.1 valid=300s;
resolver_timeout 5s;
EOF

sudo chmod 0644 /etc/nginx/tls/tls.conf

Per-server block configuration

Each nginx server block that handles HTTPS includes the shared file and adds its own certificate paths:

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    server_name nextcloud.yourdomain.net;

    # Certificate (Let's Encrypt for public services)
    ssl_certificate     /etc/letsencrypt/live/nextcloud.yourdomain.net/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/nextcloud.yourdomain.net/privkey.pem;

    # Shared TLS settings
    include /etc/nginx/tls/tls.conf;

    # HSTS: instruct clients to always use HTTPS for this domain.
    # 63072000 seconds = 2 years.
    # Only add this header once you are certain the service will remain
    # HTTPS-only. It cannot be easily undone once browsers have cached it.
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;

    # ... rest of server block
}

For internal services using certificates from the internal CA, the pattern is identical, only the certificate paths differ:

ssl_certificate     /etc/ssl/certs/grafana.yourdomain.net.chained.cert.pem;
ssl_certificate_key /etc/ssl/private/grafana.yourdomain.net.key.pem;

What each setting does

ssl_protocols TLSv1.2 TLSv1.3

TLS 1.0 and 1.1 are formally deprecated (RFC 8996) and removed from all modern browsers. They are already disabled in Ubuntu 24.04’s OpenSSL defaults, but being explicit makes the intended policy visible in the config rather than relying on an OS-level default that could change.

ssl_ciphers

All suites in this list share three properties: ECDHE or DHE key exchange (which provides forward secrecy), AEAD encryption (AES-GCM or ChaCha20-Poly1305), and no SHA-1 in the MAC. The source material for this series used cipher strings from 2016 that included SHA-1 and the deprecated kEECDH syntax. That configuration is not appropriate in 2026.

ssl_prefer_server_ciphers off

The old advice was to turn this on so the server controls cipher selection and cannot be tricked into a weaker suite. That advice made sense when cipher lists included weak options. With this list, every cipher is strong, so letting the client choose based on its hardware capabilities is the right call.

ssl_ecdh_curve

X25519 is the preferred curve: it is faster, has a simpler implementation, and is resistant to the timing side-channel issues that affected some earlier elliptic curve implementations. prime256v1 and secp384r1 are included as widely-supported fallbacks.

ssl_session_tickets off

TLS session tickets encrypt session state using a server-side key, allowing resumed connections without a full handshake. The problem is that the ticket encryption key does not rotate automatically by default, meaning past sessions could be decrypted if the key is ever compromised. This undermines forward secrecy. The session cache (ssl_session_cache) provides the same performance benefit without the key management concern.

ssl_stapling on

OCSP stapling bundles the certificate revocation check into the TLS handshake. Without it, connecting clients may make a separate request to the CA’s OCSP endpoint to check whether the certificate has been revoked, adding latency and creating a privacy leak (the CA learns which sites the client visits). With stapling, the server fetches the OCSP response and includes it in the handshake, so the client gets the revocation status without contacting the CA directly.

Verifying the configuration

After adding the include to a server block and reloading nginx, test from another machine:

# Check which TLS version and cipher were negotiated
openssl s_client -connect nextcloud.yourdomain.net:443 \
    -tls1_2 2>/dev/null | grep -E "Protocol|Cipher"

# Confirm TLS 1.1 is rejected
openssl s_client -connect nextcloud.yourdomain.net:443 \
    -tls1_1 2>/dev/null | grep -E "Protocol|alert"

For a more thorough check, the Qualys SSL Labs tester at ssllabs.com/ssltest/ gives a detailed report for any publicly accessible service. The configuration above should produce an A rating. A+ requires enabling HSTS preloading, which is optional and not appropriate for every service.

For internal services not reachable from the public internet, test directly from another machine on the network:

openssl s_client -connect grafana.yourdomain.internal:443 \
    -CAfile /usr/local/share/ca-certificates/yourdomain.net/root-ca.crt \
    2>/dev/null | grep -E "Protocol|Cipher|Verify"

The -CAfile flag tells OpenSSL to trust the internal CA for this test.

A note on DHE cipher suites

The Mozilla Intermediate configuration includes DHE suites (DHE-RSA-*) alongside the preferred ECDHE suites. DHE provides forward secrecy using finite-field Diffie-Hellman rather than elliptic curves. For the DHE suites to work correctly, nginx needs a DH parameters file. Without one, nginx silently falls back to a default 1024-bit parameter that is too weak.

Since this configuration includes DHE suites, generate a parameters file:

sudo openssl dhparam -out /etc/nginx/tls/dhparam.pem 2048

This takes a few minutes. Add it to the shared config:

echo 'ssl_dhparam /etc/nginx/tls/dhparam.pem;' \
    | sudo tee -a /etc/nginx/tls/tls.conf

If you prefer to drop DHE entirely and use only ECDHE suites, remove the three DHE-RSA-* entries from ssl_ciphers and skip the dhparam file. ECDHE is preferred anyway; DHE is there only for compatibility with clients that do not support ECDHE.

What is next

The cipher configuration is a shared file that the nginx reverse proxy article will include in each server block. The nginx article covers the full server block structure for proxying internal services and serving public-facing sites.