---
title: "Cómo hacer hardening de Apache y Nginx en Ubuntu 24.04"
description: "Endurece Apache y Nginx en Ubuntu 24.04: oculta la versión, desactiva módulos y listados, limita peticiones, configura TLS moderno y cabeceras de seguridad."
category: "Servidores Web"
language: es
source: https://cubepath.com/es/docs/servidores-web/hardening-de-apache-y-nginx
updated: 2026-09-25
---
# Cómo hacer hardening de Apache y Nginx en Ubuntu 24.04
La configuración por defecto de Apache y Nginx está pensada para funcionar a la primera, no para exponer lo mínimo posible. En este tutorial aplicarás en Ubuntu 24.04 un conjunto de ajustes concretos para cada servidor: ocultar la versión, quitar módulos y listados de directorios que no necesitas, limitar el tamaño y los métodos de las peticiones, bloquear archivos ocultos, dejar solo TLS 1.2 y 1.3 y añadir cabeceras de seguridad. Al final comprobarás cada cambio con `curl`.
Las secciones de Apache y Nginx son independientes: sigue solo la del servidor que uses.
## Requisitos previos
- Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath.
- Un usuario no root con privilegios `sudo`.
- Apache (`apache2`) o Nginx instalado desde los repositorios de Ubuntu y sirviendo al menos un sitio.
- Para la parte de TLS, un dominio (`your_domain`) apuntando al servidor y un certificado válido, por ejemplo de Let's Encrypt con Certbot.
- Acceso a la consola del VPS por si un error de configuración deja el servidor web sin arrancar.
Antes de empezar, haz una copia de la configuración actual para poder volver atrás:
```bash
sudo cp -a /etc/apache2 /etc/apache2.bak # si usas Apache
sudo cp -a /etc/nginx /etc/nginx.bak # si usas Nginx
```
## Hardening de Apache
### Paso 1: Ocultar la versión y el sistema operativo
Por defecto Apache devuelve una cabecera como `Server: Apache/2.4.58 (Ubuntu)` y firma las páginas de error con la versión. Esa información solo ayuda a quien busca vulnerabilidades de una versión concreta. En Ubuntu estos ajustes están en `/etc/apache2/conf-available/security.conf`, que ya viene activado:
```bash
sudo nano /etc/apache2/conf-available/security.conf
```
Localiza las directivas `ServerTokens`, `ServerSignature` y `TraceEnable` y déjalas así:
```apache
ServerTokens Prod
ServerSignature Off
TraceEnable Off
```
`TraceEnable Off` desactiva el método HTTP `TRACE`, que no tiene uso legítimo en producción.
### Paso 2: Desactivar módulos y listados de directorios
Cada módulo cargado es código que puede tener fallos. Lista los módulos activos:
```bash
apache2ctl -M
```
En una instalación estándar de Ubuntu hay dos que casi nunca hacen falta en un servidor público: `autoindex`, que genera listados de directorios cuando no hay `index.html`, y `status`, que expone `/server-status`. Desactívalos:
```bash
sudo a2dismod -f autoindex status
```
El parámetro `-f` es necesario porque Debian marca `autoindex` como módulo esencial y pide confirmación. Si alguna aplicación usa `/server-status` para monitorización, deja `status` activo y restringe el acceso por IP.
Aunque `autoindex` esté desactivado, conviene quitar también la opción `Indexes` en el directorio web. Edita la configuración principal:
```bash
sudo nano /etc/apache2/apache2.conf
```
Busca el bloque de `/var/www/` y déjalo así:
```apache
Options -Indexes +FollowSymLinks
AllowOverride None
Require all granted
```
Deja `AllowOverride None` salvo que tu aplicación (WordPress, Laravel...) necesite archivos `.htaccess`; en ese caso usa `AllowOverride All` solo en el `` de ese sitio.
### Paso 3: Limitar peticiones y bloquear archivos ocultos
Crea un archivo de configuración propio para agrupar el resto de ajustes y no mezclarlos con los del paquete:
```bash
sudo nano /etc/apache2/conf-available/hardening.conf
```
```apache
# Tamaño máximo del cuerpo de una petición (10 MB)
LimitRequestBody 10485760
# Tiempo máximo de espera de E/S (el valor por defecto es 300 segundos)
Timeout 60
# Bloquear archivos y directorios ocultos (.git, .env, .htpasswd)
# excepto /.well-known/, que usan Let's Encrypt y otros servicios
Require all denied
```
Ajusta `LimitRequestBody` si tu aplicación acepta subidas de archivos más grandes. El módulo `reqtimeout`, activo por defecto en Ubuntu, ya protege contra clientes que envían las cabeceras muy despacio (ataques tipo Slowloris).
Activa el archivo:
```bash
sudo a2enconf hardening
```
### Paso 4: Configurar TLS moderno
Si tus sitios usan HTTPS, limita los protocolos a TLS 1.2 y 1.3 y usa solo cifrados con secreto perfecto hacia adelante. Crea otro archivo de configuración:
```bash
sudo nano /etc/apache2/conf-available/tls-hardening.conf
```
```apache
SSLProtocol -all +TLSv1.2 +TLSv1.3
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
SSLHonorCipherOrder off
SSLSessionTickets off
```
`SSLCipherSuite` solo afecta a TLS 1.2; los cifrados de TLS 1.3 ya son seguros por diseño. Actívalo:
```bash
sudo a2enconf tls-hardening
```
> Nota: si generaste el certificado con Certbot, cada virtual host HTTPS incluye `/etc/letsencrypt/options-ssl-apache.conf`, que define sus propios protocolos y cifrados y tiene prioridad sobre la configuración global. Sus valores actuales ya son seguros, así que no es necesario modificarlo.
### Paso 5: Añadir cabeceras de seguridad
Las cabeceras de seguridad indican al navegador cómo tratar tu contenido. Activa `mod_headers`:
```bash
sudo a2enmod headers
```
Añade las cabeceras al final de `/etc/apache2/conf-available/hardening.conf`:
```bash
sudo nano /etc/apache2/conf-available/hardening.conf
```
```apache
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
```
La cabecera HSTS obliga al navegador a usar siempre HTTPS, por lo que debe ir solo en el virtual host HTTPS (el de `:443`, por ejemplo `/etc/apache2/sites-available/your_domain-le-ssl.conf`), dentro de ``:
```apache
Header always set Strict-Transport-Security "max-age=31536000"
```
> Advertencia: una vez que un navegador recibe HSTS, se negará a conectar por HTTP durante todo el `max-age`. Actívala solo cuando HTTPS funcione bien en el dominio, y no añadas `includeSubDomains` si algún subdominio aún no tiene certificado.
### Paso 6: Validar y aplicar los cambios
Comprueba la sintaxis antes de recargar:
```bash
sudo apache2ctl configtest
```
```output
Syntax OK
```
Si ves un error, el mensaje indica el archivo y la línea. Cuando la sintaxis sea correcta, recarga Apache:
```bash
sudo systemctl reload apache2
```
Continúa en la sección "Verificar el hardening", al final de la guía.
## Hardening de Nginx
### Paso 1: Ocultar la versión
Nginx incluye su versión en la cabecera `Server` y en las páginas de error. En Ubuntu la directiva está comentada en el bloque `http` de `/etc/nginx/nginx.conf`:
```bash
sudo nano /etc/nginx/nginx.conf
```
Descomenta la línea para que quede así:
```nginx
server_tokens off;
```
La cabecera seguirá diciendo `Server: nginx`, pero sin número de versión. Eliminarla del todo requiere módulos externos y no aporta seguridad real.
### Paso 2: Ajustar límites globales
Crea un archivo en `/etc/nginx/conf.d/`, que Nginx incluye dentro del bloque `http`:
```bash
sudo nano /etc/nginx/conf.d/hardening.conf
```
```nginx
# Tamaño máximo del cuerpo de una petición
client_max_body_size 10m;
# Tiempos de espera para clientes lentos
client_body_timeout 15s;
client_header_timeout 15s;
send_timeout 15s;
keepalive_timeout 30s;
# Sin listados de directorios (es el valor por defecto, lo dejamos explícito)
autoindex off;
# Cabeceras de seguridad
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
```
> Importante: en Nginx, si un bloque `server` o `location` define su propio `add_header`, deja de heredar todos los `add_header` de niveles superiores. Si algún sitio añade cabeceras propias, repite en él las tres cabeceras de seguridad.
Sube `client_max_body_size` en el `server` de las aplicaciones que acepten archivos grandes.
### Paso 3: Rechazar peticiones a hosts desconocidos
Si alguien accede por IP o con un `Host` que no es tuyo, Nginx sirve el primer sitio que encuentra. Es mejor cerrar esas conexiones con un servidor por defecto que no responde nada. Primero desactiva el sitio de ejemplo de Ubuntu, que ya ocupa `default_server`:
```bash
sudo rm /etc/nginx/sites-enabled/default
```
Crea el servidor por defecto:
```bash
sudo nano /etc/nginx/sites-available/000-default-deny
```
```nginx
server {
listen 80 default_server;
listen [::]:80 default_server;
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
server_name _;
ssl_reject_handshake on;
return 444;
}
```
`return 444` cierra la conexión sin respuesta y `ssl_reject_handshake on` rechaza el handshake TLS sin necesidad de un certificado. Si ninguno de tus sitios usa HTTPS, quita las dos líneas `listen 443` y `ssl_reject_handshake`. Actívalo:
```bash
sudo ln -s /etc/nginx/sites-available/000-default-deny /etc/nginx/sites-enabled/
```
### Paso 4: Proteger cada sitio
Dentro del bloque `server` de cada sitio (por ejemplo `/etc/nginx/sites-available/your_domain`), bloquea los archivos ocultos y los métodos HTTP que no uses:
```bash
sudo nano /etc/nginx/sites-available/your_domain
```
```nginx
# Solo los métodos que usa el sitio
if ($request_method !~ ^(GET|HEAD|POST)$) {
return 405;
}
# Bloquear .git, .env, .htpasswd, etc., salvo /.well-known/
location ~ /\.(?!well-known/) {
deny all;
}
```
Si sirves una API REST que usa `PUT`, `PATCH` o `DELETE`, añádelos a la expresión regular.
### Paso 5: Configurar TLS moderno
Certbot incluye `/etc/letsencrypt/options-ssl-nginx.conf` en cada sitio HTTPS, y ese archivo ya limita los protocolos a TLS 1.2 y 1.3. Si gestionas los certificados a mano, crea un fragmento reutilizable:
```bash
sudo nano /etc/nginx/snippets/tls-hardening.conf
```
```nginx
ssl_protocols TLSv1.2 TLSv1.3;
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;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
```
E inclúyelo en el `server` HTTPS, junto a tus directivas `ssl_certificate`:
```nginx
include snippets/tls-hardening.conf;
```
No incluyas este fragmento en un sitio que ya incluye `options-ssl-nginx.conf`: definir dos veces la misma directiva en un bloque da error. Añade HSTS solo en el `server` que escucha en el puerto 443 (recuerda repetir aquí las cabeceras del paso 2 si este bloque define su propio `add_header`):
```nginx
add_header Strict-Transport-Security "max-age=31536000" always;
```
### Paso 6: Validar y aplicar los cambios
```bash
sudo nginx -t
```
```output
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
```
Recarga Nginx:
```bash
sudo systemctl reload nginx
```
## Verificar el hardening
Comprueba las cabeceras de tu sitio desde el propio servidor o desde tu equipo:
```bash
curl -sI https://your_domain
```
```output
HTTP/2 200
server: nginx
strict-transport-security: max-age=31536000
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
```
Con Apache verás `server: Apache`, sin versión ni sistema operativo. Comprueba que los archivos ocultos están bloqueados (debe devolver `403`):
```bash
curl -s -o /dev/null -w "%{http_code}\n" https://your_domain/.git/config
```
```output
403
```
Comprueba que el servidor negocia TLS 1.3:
```bash
openssl s_client -connect your_domain:443 -servername your_domain /dev/null | grep "Protocol"
```
```output
Protocol : TLSv1.3
```
Si usas Nginx, verifica también que las peticiones por IP se cierran sin respuesta:
```bash
curl -I http://your_server_ip
```
```output
curl: (52) Empty reply from server
```
Para confirmar que TLS 1.0 y 1.1 están desactivados y obtener un análisis externo completo, pasa tu dominio por [SSL Labs](https://www.ssllabs.com/ssltest/) y por [securityheaders.com](https://securityheaders.com).
## Solución de problemas
**El servicio no recarga tras un cambio.** Ejecuta `sudo apache2ctl configtest` o `sudo nginx -t` y revisa el archivo y la línea que indican. Para ver el error completo del servicio usa `sudo journalctl -u apache2 -n 30` o `sudo journalctl -u nginx -n 30`. Si no encuentras el fallo, restaura la copia del principio.
**Error 403 en páginas que antes funcionaban.** Con `Options -Indexes`, un directorio sin `index.html` o `index.php` devuelve 403 en lugar de un listado. Añade el archivo de índice o revisa la directiva `DirectoryIndex` (Apache) o `index` (Nginx).
**Error 413 Request Entity Too Large al subir archivos.** El límite de `LimitRequestBody` o `client_max_body_size` es menor que el archivo. Súbelo solo en el sitio o `location` que recibe las subidas.
**Una integración deja de funcionar con 405.** La aplicación usa un método que has bloqueado en el paso 4 de Nginx. Añádelo a la lista de métodos permitidos.
## Conclusión
Tu servidor web ya no anuncia su versión, no lista directorios, rechaza archivos ocultos, peticiones sobredimensionadas y protocolos TLS antiguos, y envía cabeceras de seguridad básicas. Estos ajustes reducen la superficie de ataque sin cambiar el comportamiento de las aplicaciones legítimas.
Como siguientes pasos puedes:
- Limitar la tasa de peticiones por IP con los módulos `limit_req` de Nginx para frenar ataques de fuerza bruta.
- Instalar Fail2ban para bloquear en el firewall las IP que generan errores repetidos en los logs.
- Definir una `Content-Security-Policy` adaptada a tu aplicación, empezando en modo `Content-Security-Policy-Report-Only`.