Cifrar los datos en tránsito significa que nada de lo que viaja por la red entre tus usuarios, tu servidor y otros servicios pueda leerse o modificarse por el camino. Es un requisito explícito de normas como el RGPD (artículo 32) o PCI-DSS, y en la práctica se consigue con TLS. En este tutorial configurarás HTTPS en Apache con un certificado de Let's Encrypt en Ubuntu 24.04, limitarás el servidor a TLS 1.2 y 1.3, activarás HSTS y obligarás a que las conexiones remotas a MySQL vayan cifradas.
Requisitos previos
- Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath.
- Un usuario no root con privilegios
sudo. - Un dominio con registros DNS
A(yAAAAsi usas IPv6) apuntando a la IP del servidor, tanto parayour_domaincomo parawww.your_domain. Sustituyeyour_domainpor tu dominio en todos los comandos. - Los puertos 80 y 443 accesibles desde Internet. Let's Encrypt valida el dominio por el puerto 80.
- Opcional: MySQL 8 instalado, si quieres seguir el paso 6.
Paso 1: Instalar Apache y abrir el cortafuegos
Instala Apache:
sudo apt update
sudo apt install apache2
Si usas UFW, permite SSH antes de nada para no perder el acceso, y después HTTP y HTTPS con el perfil Apache Full:
sudo ufw allow OpenSSH
sudo ufw allow 'Apache Full'
sudo ufw enable
Comprueba las reglas:
sudo ufw status
Status: active
To Action From
-- ------ ----
OpenSSH ALLOW Anywhere
Apache Full ALLOW Anywhere
OpenSSH (v6) ALLOW Anywhere (v6)
Apache Full (v6) ALLOW Anywhere (v6)
Paso 2: Crear el virtual host del dominio
Certbot necesita un virtual host con ServerName para saber dónde instalar el certificado. Crea el directorio del sitio y una página de prueba:
sudo mkdir -p /var/www/your_domain
echo '<h1>your_domain funciona</h1>' | sudo tee /var/www/your_domain/index.html
Crea el archivo del virtual host:
sudo nano /etc/apache2/sites-available/your_domain.conf
<VirtualHost *:80>
ServerName your_domain
ServerAlias www.your_domain
DocumentRoot /var/www/your_domain
ErrorLog ${APACHE_LOG_DIR}/your_domain_error.log
CustomLog ${APACHE_LOG_DIR}/your_domain_access.log combined
</VirtualHost>
Activa el sitio, desactiva el sitio por defecto y valida la sintaxis antes de recargar:
sudo a2ensite your_domain.conf
sudo a2dissite 000-default.conf
sudo apache2ctl configtest
Syntax OK
sudo systemctl reload apache2
Comprueba que el sitio responde por HTTP:
curl -s http://your_domain
<h1>your_domain funciona</h1>
Paso 3: Obtener un certificado de Let's Encrypt
Instala Certbot y su plugin para Apache:
sudo apt install certbot python3-certbot-apache
Solicita el certificado para los dos nombres. El plugin de Apache activa mod_ssl, crea el virtual host HTTPS en /etc/apache2/sites-available/your_domain-le-ssl.conf y, con --redirect, añade la redirección permanente de HTTP a HTTPS:
sudo certbot --apache --redirect -d your_domain -d www.your_domain
La primera vez te pedirá un correo para avisos de caducidad y que aceptes los términos del servicio. Al terminar verás algo similar a:
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/your_domain/fullchain.pem
Key is saved at: /etc/letsencrypt/live/your_domain/privkey.pem
...
Congratulations! You have successfully enabled HTTPS on https://your_domain and https://www.your_domain
Comprueba que HTTP redirige a HTTPS:
curl -sI http://your_domain | grep -iE '^(HTTP|Location)'
HTTP/1.1 301 Moved Permanently
Location: https://your_domain/
Paso 4: Limitar el servidor a TLS 1.2 y TLS 1.3
TLS 1.0 y 1.1 están obsoletos (RFC 8996) y no se admiten en PCI-DSS. El virtual host HTTPS que genera Certbot incluye el archivo /etc/letsencrypt/options-ssl-apache.conf, que ya los desactiva. Compruébalo:
grep -E '^SSL(Protocol|HonorCipherOrder|SessionTickets)' /etc/letsencrypt/options-ssl-apache.conf
SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
SSLHonorCipherOrder off
SSLSessionTickets off
all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 deja solo TLS 1.2 y 1.3. No edites este archivo: Certbot lo gestiona y lo actualiza con recomendaciones nuevas. Si algún día necesitas otra política, define SSLProtocol en tu propio virtual host después de la línea Include.
Verifica desde el propio servidor que se negocia TLS 1.3:
openssl s_client -connect your_domain:443 -servername your_domain </dev/null 2>/dev/null | grep '^New,'
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Para una comprobación completa de versiones y cifrados aceptados, ejecuta desde otra máquina con nmap instalado:
nmap --script ssl-enum-ciphers -p 443 your_domain
La salida solo debe listar TLSv1.2 y TLSv1.3. También puedes analizar el dominio con SSL Labs (ssllabs.com/ssltest).
Paso 5: Activar HSTS
La redirección a HTTPS deja una ventana: la primera petición de un navegador sigue saliendo por HTTP en claro. La cabecera HTTP Strict Transport Security (HSTS) indica al navegador que use siempre HTTPS con ese dominio durante el tiempo que marques. Activa el módulo headers:
sudo a2enmod headers
Edita el virtual host HTTPS generado por Certbot:
sudo nano /etc/apache2/sites-available/your_domain-le-ssl.conf
Añade la línea Header dentro del bloque <VirtualHost *:443>, por ejemplo después de DocumentRoot:
<IfModule mod_ssl.c>
<VirtualHost *:443>
ServerName your_domain
ServerAlias www.your_domain
DocumentRoot /var/www/your_domain
Header always set Strict-Transport-Security "max-age=300"
ErrorLog ${APACHE_LOG_DIR}/your_domain_error.log
CustomLog ${APACHE_LOG_DIR}/your_domain_access.log combined
SSLCertificateFile /etc/letsencrypt/live/your_domain/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/your_domain/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
</IfModule>
Valida y recarga:
sudo apache2ctl configtest
sudo systemctl reload apache2
Comprueba la cabecera:
curl -sI https://your_domain | grep -i strict-transport
strict-transport-security: max-age=300
Importanteempieza con un
max-agecorto (300 segundos) mientras compruebas que todo el sitio y sus subdominios funcionan por HTTPS. Cuando estés seguro, súbelo a un año o más (max-age=31536000). Los navegadores recuerdan la cabecera durante ese tiempo, así que un valor largo es difícil de deshacer.
Paso 6: Obligar a MySQL a usar conexiones cifradas
HTTPS protege el tráfico de los usuarios, pero si tu aplicación o tus réplicas se conectan a MySQL a través de la red, esas consultas también viajan con datos personales. MySQL 8 en Ubuntu genera certificados TLS automáticamente al instalarse y acepta TLS por defecto, pero también permite conexiones sin cifrar. Si MySQL solo escucha en 127.0.0.1, el tráfico no sale del servidor y puedes saltarte este paso.
Edita la configuración del servidor:
sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf
Añade esta línea dentro de la sección [mysqld]:
require_secure_transport = ON
Reinicia MySQL y comprueba la variable:
sudo systemctl restart mysql
sudo mysql -e "SHOW VARIABLES LIKE 'require_secure_transport';"
+--------------------------+-------+
| Variable_name | Value |
+--------------------------+-------+
| require_secure_transport | ON |
+--------------------------+-------+
Las conexiones locales por socket Unix se consideran seguras, así que sudo mysql sigue funcionando. Desde un cliente remoto, una conexión sin TLS se rechaza:
mysql -h your_server_ip -u app_user -p --ssl-mode=DISABLED
ERROR 3159 (HY000): Connections using insecure transport are prohibited while --require_secure_transport=ON.
Y una conexión normal negocia TLS. Compruébalo con el comando status del cliente:
mysql -h your_server_ip -u app_user -p -e 'status' | grep -i ssl
SSL: Cipher in use is TLS_AES_256_GCM_SHA384
Los certificados autogenerados cifran la conexión pero no permiten al cliente verificar la identidad del servidor. Si la conexión cruza redes que no controlas, configura en el cliente --ssl-mode=VERIFY_IDENTITY con un certificado firmado por tu propia CA, o lleva el tráfico por una red privada o una VPN.
Paso 7: Verificar la renovación automática
Los certificados de Let's Encrypt caducan a los 90 días o antes. El paquete certbot instala un temporizador de systemd que intenta renovarlos dos veces al día. Comprueba que está activo:
systemctl list-timers certbot.timer
NEXT LEFT LAST PASSED UNIT ACTIVATES
Fri 2026-09-25 21:14:00 UTC 9h left Fri 2026-09-25 09:02:11 UTC 2h ago certbot.timer certbot.service
Simula una renovación para asegurarte de que funcionará cuando toque:
sudo certbot renew --dry-run
Congratulations, all simulated renewals succeeded:
/etc/letsencrypt/live/your_domain/fullchain.pem (success)
Solución de problemas
Certbot falla con Timeout during connect o Connection refused. Let's Encrypt no llega al puerto 80. Revisa que el DNS apunta a la IP correcta (dig +short your_domain) y que UFW permite Apache Full.
Invalid command 'Header' al validar la configuración. El módulo headers no está activo. Ejecuta sudo a2enmod headers y recarga Apache.
Avisos de contenido mixto en el navegador. La página carga recursos con URL http://. Cambia esas URL a https:// en la aplicación o en la base de datos (en WordPress, la URL del sitio).
Un cliente antiguo no conecta tras desactivar TLS 1.0/1.1. Es el comportamiento esperado: ese cliente debe actualizarse. No vuelvas a activar versiones obsoletas para un caso aislado.
Conclusión
Tu servidor sirve el sitio solo por HTTPS con TLS 1.2 y 1.3, los navegadores recuerdan usar HTTPS gracias a HSTS, los certificados se renuevan solos y MySQL rechaza conexiones remotas sin cifrar. Como siguientes pasos, aplica el mismo criterio al resto de servicios que escuchan en la red (correo con STARTTLS, Redis, paneles internos), sube el max-age de HSTS cuando hayas validado el sitio y complementa esta configuración con el cifrado de los datos en reposo.
