TLS 1.3 reduce el handshake a un solo viaje de ida y vuelta, elimina los algoritmos débiles de versiones anteriores y garantiza secreto perfecto hacia adelante en todas las conexiones. En esta guía configurarás Nginx y Apache en Ubuntu 24.04 para usar TLS 1.3 y mantener TLS 1.2 solo con cifrados modernos, ajustarás la reanudación de sesiones, decidirás qué hacer con 0-RTT y comprobarás el resultado desde la línea de comandos.

Requisitos previos

  • Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con un usuario no root con privilegios sudo.
  • Nginx (1.24 en Ubuntu 24.04) o Apache (2.4.58) sirviendo ya tu dominio por HTTPS con un certificado válido, por ejemplo de Let's Encrypt.
  • Un dominio (your_domain en los ejemplos) que apunte al servidor.

Ubuntu 24.04 incluye OpenSSL 3.0, que soporta TLS 1.3 sin configuración adicional. Puedes confirmarlo:

openssl version
OpenSSL 3.0.13 30 Jan 2024 (Library: OpenSSL 3.0.13 30 Jan 2024)

Qué cambia en TLS 1.3

Antes de tocar la configuración conviene tener claras tres diferencias que afectan a cómo se escribe:

AspectoTLS 1.2TLS 1.3
Handshake completo2 viajes de ida y vuelta1 viaje de ida y vuelta
Suites de cifradoDecenas de combinaciones, algunas inseguras5 definidas, OpenSSL activa 3 por defecto
Nombre de suiteECDHE-RSA-AES128-GCM-SHA256TLS_AES_128_GCM_SHA256
Intercambio de clavesRSA, DHE o ECDHESiempre (EC)DHE efímero
Directiva en Nginxssl_ciphersssl_conf_command Ciphersuites
Directiva en ApacheSSLCipherSuiteSSLCipherSuite TLSv1.3

La consecuencia práctica más importante: ssl_ciphers en Nginx y SSLCipherSuite sin prefijo en Apache solo afectan a TLS 1.2 y anteriores. Nombres como TLS13-AES-256-GCM-SHA384 que aparecen en muchos tutoriales no existen en OpenSSL y no hacen nada.

Las tres suites que OpenSSL activa por defecto en TLS 1.3 son TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256 y TLS_AES_128_GCM_SHA256. Todas son seguras, así que en la mayoría de servidores no hace falta tocarlas.

Paso 1: Configurar protocolos y cifrados en Nginx

En Ubuntu, /etc/nginx/nginx.conf ya define ssl_protocols y ssl_prefer_server_ciphers en el bloque http. Modifícalos ahí en lugar de repetirlos en otro archivo, porque declarar dos veces la misma directiva en el mismo bloque provoca un error.

sudo nano /etc/nginx/nginx.conf

Busca la sección SSL Settings del bloque http y déjala así:

    ##
    # SSL Settings
    ##

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers off;

    # Cifrados de TLS 1.2 (TLS 1.3 usa los valores por defecto de OpenSSL)
    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;

    # Curvas para el intercambio de claves
    ssl_ecdh_curve X25519:prime256v1:secp384r1;

    # Reanudación de sesiones
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

Por qué cada ajuste:

  • ssl_protocols TLSv1.2 TLSv1.3: desactiva TLS 1.0 y 1.1, que los navegadores ya no aceptan. El protocolo se negocia antes de que Nginx sepa qué server atiende la conexión, por eso se define aquí de forma global.
  • ssl_prefer_server_ciphers off: todos los cifrados de la lista son seguros, así que se deja elegir al cliente; los móviles sin aceleración AES prefieren ChaCha20, que es más rápido para ellos.
  • ssl_ciphers: solo suites ECDHE con AES-GCM o ChaCha20-Poly1305. Funciona tanto con certificados RSA como ECDSA.
  • ssl_ecdh_curve: X25519 primero, que es la curva más rápida y la que usan casi todos los clientes.
  • ssl_session_cache y ssl_session_timeout: guardan las sesiones en memoria compartida (unas 40.000 sesiones por cada 10 MB) para que los clientes que vuelven reanuden sin handshake completo.
  • ssl_session_tickets off: Nginx no rota por sí mismo la clave de los tickets de sesión hasta que se reinicia, lo que debilita el secreto hacia adelante. Con los tickets desactivados, la reanudación sigue funcionando a través de la caché compartida.

Si tu sitio usa un certificado de Certbot, el bloque server probablemente incluye include /etc/letsencrypt/options-ssl-nginx.conf;. Ese archivo vuelve a definir protocolos, cifrados y caché a nivel de server y sobrescribe lo anterior. Ábrelo y elimina o comenta esa línea:

sudo nano /etc/nginx/sites-available/your_domain
    # include /etc/letsencrypt/options-ssl-nginx.conf;

Deja la línea ssl_dhparam si existe; no molesta.

Comprueba la configuración y recarga:

sudo nginx -t
sudo systemctl reload nginx

Fijar las suites de TLS 1.3 (opcional)

Solo si una política interna te obliga a limitar las suites de TLS 1.3, hazlo con ssl_conf_command, disponible desde Nginx 1.19.4. Añádelo en la misma sección SSL Settings:

    ssl_conf_command Ciphersuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256;

Paso 2: Configurar protocolos y cifrados en Apache

En Apache los valores globales de TLS están en /etc/apache2/mods-available/ssl.conf. En lugar de editar ese archivo, crea uno propio que se carga después y lo sobrescribe:

sudo nano /etc/apache2/conf-available/tls-modern.conf
SSLProtocol -all +TLSv1.2 +TLSv1.3
SSLHonorCipherOrder off
SSLCipherSuite 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
SSLOpenSSLConfCmd Curves X25519:prime256v1:secp384r1
SSLSessionTickets off

Las directivas equivalen a las de Nginx del paso anterior. Si necesitas fijar las suites de TLS 1.3, Apache 2.4.36 o superior acepta el prefijo de protocolo en una línea aparte:

SSLCipherSuite TLSv1.3 TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256

Activa el archivo y los módulos, comprueba y recarga:

sudo a2enmod ssl
sudo a2enconf tls-modern
sudo apache2ctl configtest
Syntax OK
sudo systemctl reload apache2

Si tu VirtualHost incluye /etc/letsencrypt/options-ssl-apache.conf, ocurre lo mismo que en Nginx: ese archivo define SSLProtocol y SSLCipherSuite a nivel de sitio. Comenta la línea Include correspondiente en /etc/apache2/sites-available/your_domain-le-ssl.conf y vuelve a recargar.

Paso 3: Decidir sobre 0-RTT (early data)

TLS 1.3 permite que un cliente que reanuda sesión envíe la primera petición HTTP dentro del propio handshake (0-RTT), ahorrando un viaje de ida y vuelta. El precio es que esos datos pueden ser reenviados por un atacante (replay), así que una petición POST que crea un pedido podría ejecutarse dos veces.

Nginx lo tiene desactivado por defecto y la recomendación es dejarlo así salvo que tengas un caso claro. Si lo activas, hazlo solo para contenido idempotente y avisa al backend para que rechace las peticiones peligrosas:

server {
    # ...
    ssl_early_data on;

    location / {
        proxy_set_header Early-Data $ssl_early_data;
        proxy_pass http://127.0.0.1:8080;
    }
}

$ssl_early_data vale 1 cuando la petición llegó como 0-RTT. El backend debe responder 425 Too Early a métodos no idempotentes cuando reciba esa cabecera. Apache no soporta 0-RTT, así que no hay nada que configurar.

Paso 4: Verificar la configuración

Comprueba que el servidor negocia TLS 1.3 y qué suite elige:

openssl s_client -connect your_domain:443 -servername your_domain -tls1_3 < /dev/null 2>/dev/null | grep -E '^(New|Protocol|Cipher)'
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384

Comprueba que TLS 1.2 sigue disponible para clientes antiguos:

openssl s_client -connect your_domain:443 -servername your_domain -tls1_2 < /dev/null 2>/dev/null | grep -E '^New'
New, TLSv1.2, Cipher is ECDHE-RSA-AES128-GCM-SHA256

Con curl verás lo mismo desde el punto de vista de un cliente HTTP:

curl -sv -o /dev/null https://your_domain 2>&1 | grep 'SSL connection'
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519 / RSASSA-PSS

Para comprobar que TLS 1.0 y 1.1 están realmente desactivados en el servidor no uses el openssl de Ubuntu 24.04, que ya los bloquea como cliente. Usa nmap, que enumera protocolos y cifrados aceptados:

sudo apt install nmap
nmap --script ssl-enum-ciphers -p 443 your_domain
PORT    STATE SERVICE
443/tcp open  https
| ssl-enum-ciphers:
|   TLSv1.2:
|     ciphers:
|       TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (secp256r1) - A
|       ...
|   TLSv1.3:
|     ciphers:
|       TLS_AKE_WITH_AES_256_GCM_SHA384 (ecdh_x25519) - A
|       ...
|_  least strength: A

Solo deben aparecer TLSv1.2 y TLSv1.3, y todos los cifrados con nota A.

Solución de problemas

Los cambios no se aplican a un sitio concreto. Algún archivo incluido en el bloque server o VirtualHost redefine protocolos o cifrados. Búscalo:

sudo grep -rnE 'ssl_protocols|ssl_ciphers|SSLProtocol|SSLCipherSuite' /etc/nginx /etc/apache2 /etc/letsencrypt/*.conf

nginx: [emerg] "ssl_protocols" directive is duplicate. Has añadido la directiva en un archivo de conf.d o en otro punto del bloque http además de nginx.conf. Déjala en un solo sitio.

Un cliente antiguo deja de conectar. Clientes anteriores a 2014 (Android 4.3 o inferior, Java 7, Windows XP) no soportan TLS 1.2 con ECDHE-GCM. Si de verdad tienes que atenderlos, necesitarás una configuración menos estricta solo para ese servicio.

Conclusión

Tu servidor negocia ahora TLS 1.3 con las suites por defecto de OpenSSL, mantiene TLS 1.2 solo con cifrados ECDHE y AEAD, reanuda sesiones desde una caché compartida y no acepta 0-RTT salvo que lo actives a propósito. Como siguientes pasos, añade la cabecera HSTS, revisa la calificación del dominio en SSL Labs y automatiza la renovación del certificado.