Cómo instalar y configurar Apache2 en Debian 13: guía completa para VirtualHosts, PHP, HTTPS y servidores de producción

Apache HTTP Server, conocido comúnmente como Apache2, es uno de los servidores web más utilizados para alojar sitios y aplicaciones web.
Gracias a Apache podemos convertir una computadora con Linux en un servidor capaz de entregar páginas HTML, hojas de estilo CSS, archivos JavaScript, imágenes y otros recursos. Además, mediante tecnologías como PHP-FPM, podemos ejecutar aplicaciones PHP, conectarlas a bases de datos y alojar proyectos más complejos.
Pero instalar Apache2 no significa que nuestro servidor esté preparado para producción.
Para publicar páginas correctamente debemos comprender su funcionamiento, configurar los VirtualHosts, establecer permisos adecuados, habilitar HTTPS, revisar los registros y aplicar medidas de seguridad.
En este tutorial aprenderemos a hacerlo paso a paso utilizando Debian 13, tanto en un entorno local de desarrollo como en un servidor VPS con dominios reales.
1. ¿En qué sistemas operativos podemos utilizar Apache?
Aunque utilizaremos Debian 13, Apache HTTP Server está disponible para diferentes sistemas operativos.
Entre ellos encontramos:
| Sistema operativo | Nombre habitual del paquete | Administrador de paquetes |
|---|---|---|
| Debian 13 | apache2 | APT |
| Debian 12 | apache2 | APT |
| Ubuntu Server | apache2 | APT |
| Linux Mint | apache2 | APT |
| Fedora | httpd | DNF |
| Rocky Linux | httpd | DNF |
| AlmaLinux | httpd | DNF |
| Arch Linux | apache | Pacman |
También existen versiones para otros sistemas, incluido Windows.
Sin embargo, debemos considerar que las rutas, los nombres de los servicios y la organización de los archivos de configuración pueden cambiar entre distribuciones.
Por ejemplo, en Debian y Ubuntu encontramos habitualmente:
/etc/apache2/
Mientras que en sistemas de la familia Red Hat se utiliza normalmente:
/etc/httpd/
Los comandos de este tutorial están diseñados principalmente para Debian 13 y distribuciones derivadas que mantienen una estructura similar.
2. ¿Qué es Apache2 y cómo funciona?
Apache es un servidor HTTP.
Su función consiste en recibir solicitudes de clientes, generalmente navegadores web, y responder con los recursos correspondientes.
Imaginemos que escribimos en el navegador:
http://localhost
El navegador establece una conexión con el servidor web.
Apache recibe la solicitud, determina qué sitio corresponde al nombre solicitado, busca el recurso y devuelve una respuesta HTTP.
Podemos representar el proceso de esta manera:
NAVEGADOR
|
| Solicitud HTTP
v
APACHE2
|
| Busca el VirtualHost correspondiente
v
DIRECTORIO DEL SITIO
|
| Localiza index.html
v
RESPUESTA HTTP
|
v
NAVEGADOR
Si la página contiene HTML, CSS y JavaScript, Apache entrega esos archivos al navegador.
El navegador interpreta el HTML, aplica los estilos CSS y ejecuta el JavaScript correspondiente.
Apache no ejecuta normalmente el JavaScript del navegador. Su función es entregar el archivo para que el cliente lo ejecute.
En cambio, cuando utilizamos PHP, necesitamos un intérprete del lado del servidor.
3. Diferencia entre HTML, CSS, JavaScript y PHP en Apache
Antes de configurar nuestro servidor, conviene comprender cómo se procesa cada tecnología.
| Tecnología | ¿Quién la procesa principalmente? | ¿Necesita PHP? |
|---|---|---|
| HTML | Navegador | No |
| CSS | Navegador | No |
| JavaScript del navegador | Navegador | No |
| PHP | Servidor mediante PHP-FPM u otro controlador | Sí |
| Imágenes | Navegador, después de recibirlas | No |
| JSON estático | Aplicación cliente | No |
Por ejemplo, una página sencilla puede tener esta estructura:
mi-sitio/
├── index.html
├── css/
│ └── styles.css
├── js/
│ └── script.js
└── imagenes/
└── portada.webp
Apache puede entregar todos esos archivos sin necesidad de instalar PHP.
Pero si utilizamos:
index.php
Necesitaremos configurar un mecanismo para interpretar el código PHP.
En este tutorial utilizaremos PHP-FPM, una opción adecuada para instalaciones modernas.
4. Instalar Apache2 en Debian 13
Abrimos la terminal y actualizamos la información de los paquetes:
sudo apt update
Después instalamos Apache2:
sudo apt install apache2
Durante la instalación, Debian creará los directorios de configuración, instalará los módulos necesarios y normalmente iniciará el servicio.
Podemos comprobarlo:
systemctl status apache2
Si funciona correctamente, veremos un estado similar a:
Active: active (running)
Iniciar Apache manualmente
sudo systemctl start apache2
Detener Apache
sudo systemctl stop apache2
Reiniciar Apache
sudo systemctl restart apache2
Recargar la configuración
sudo systemctl reload apache2
Habilitar el inicio automático
sudo systemctl enable apache2
También podemos iniciar y habilitar el servicio simultáneamente:
sudo systemctl enable --now apache2
Diferencia entre restart y reload
restart detiene y vuelve a iniciar el servicio.
reload solicita recargar la configuración sin realizar un reinicio completo del servicio.
Cuando modificamos archivos de configuración, generalmente conviene validar primero y después recargar:
sudo apache2ctl configtest
sudo systemctl reload apache2
Si la configuración es correcta, el primer comando debería mostrar:
Syntax OK
5. Comprobar que Apache funciona
Abrimos nuestro navegador y visitamos:
http://localhost
También podemos utilizar:
http://127.0.0.1
Si Apache está funcionando y conserva su sitio predeterminado, veremos la página inicial de Apache para Debian.
Podemos comprobar la respuesta desde la terminal:
curl -I http://localhost
Una respuesta habitual sería:
HTTP/1.1 200 OK
El código 200 indica que la solicitud se procesó correctamente.
Si no funciona, podemos revisar:
sudo systemctl status apache2
Y los registros:
sudo journalctl -u apache2 --since "10 minutes ago"
6. Conocer los directorios de Apache2
En Debian, la configuración principal se encuentra en:
/etc/apache2/
Su estructura habitual es:
/etc/apache2/
├── apache2.conf
├── ports.conf
├── envvars
├── sites-available/
├── sites-enabled/
├── mods-available/
├── mods-enabled/
├── conf-available/
└── conf-enabled/
Veamos para qué sirve cada elemento.
apache2.conf
Es uno de los archivos principales de configuración.
Contiene directivas globales y referencias a otros archivos de configuración.
ports.conf
Define los puertos en los que Apache debe escuchar conexiones.
Normalmente encontraremos el puerto HTTP:
Listen 80
Y, cuando corresponde, configuraciones para HTTPS en el puerto 443.
sites-available
Almacena los archivos de configuración de nuestros sitios web.
Por ejemplo:
/etc/apache2/sites-available/empresa.conf
/etc/apache2/sites-available/tienda.conf
Que un archivo exista aquí no significa que el sitio esté habilitado.
sites-enabled
Contiene enlaces simbólicos a los sitios habilitados.
No necesitamos copiar manualmente los archivos.
Podemos utilizar:
sudo a2ensite empresa.conf
Para deshabilitar:
sudo a2dissite empresa.conf
mods-available y mods-enabled
Almacenan la configuración de módulos disponibles y habilitados.
Por ejemplo, para activar el módulo de reescritura:
sudo a2enmod rewrite
Para desactivarlo:
sudo a2dismod rewrite
conf-available y conf-enabled
Permiten organizar configuraciones adicionales.
Podemos habilitarlas mediante:
sudo a2enconf nombre
Y deshabilitarlas con:
sudo a2disconf nombre
Esta organización facilita administrar diferentes sitios sin colocar toda la configuración en un único archivo.
7. ¿Qué es un VirtualHost?
Un VirtualHost es una configuración que permite a Apache identificar y servir un sitio web específico.
Gracias a los VirtualHosts podemos alojar varias páginas utilizando un mismo servidor y una misma dirección IP.
Por ejemplo:
Servidor Debian
IP: 192.168.1.100
|
v
Apache2
|
+--- empresa.test
|
+--- tienda.test
|
+--- academia.test
Cada dominio puede tener su propio directorio, configuración y archivos de registro.
Apache utiliza información de la solicitud HTTP, como el nombre del host, para seleccionar el VirtualHost correspondiente.
En HTTPS también interviene SNI durante la negociación TLS, que permite seleccionar certificados para diferentes nombres de dominio.
En Apache 2.4 no necesitamos agregar la antigua directiva NameVirtualHost para configurar sitios basados en nombres.
8. Laboratorio local: crear nuestro primer VirtualHost
Vamos a crear una página llamada:
empresa.test
Utilizaremos .test porque es un dominio reservado para pruebas y no necesitamos comprarlo.
Paso 1. Crear el directorio del sitio
Ejecutamos:
sudo mkdir -p /var/www/empresa.test/public
La estructura será:
/var/www/
└── empresa.test/
└── public/
El directorio public contendrá los archivos que queremos mostrar a los visitantes.
Esta separación resulta especialmente útil cuando más adelante trabajemos con aplicaciones que tienen configuraciones y archivos privados fuera del directorio público.
Paso 2. Crear nuestra primera página HTML
sudo nano /var/www/empresa.test/public/index.html
Agregamos:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Empresa - Servidor Apache2</title>
<link rel="stylesheet" href="css/styles.css">
</head>
<body>
<main>
<h1>¡Apache2 está funcionando!</h1>
<p>
Este sitio utiliza un VirtualHost
configurado en Debian 13.
</p>
<button id="saludar">
Probar JavaScript
</button>
</main>
<script src="js/script.js"></script>
</body>
</html>
Paso 3. Crear los directorios CSS y JavaScript
sudo mkdir -p /var/www/empresa.test/public/css
sudo mkdir -p /var/www/empresa.test/public/js
Creamos la hoja de estilos:
sudo nano /var/www/empresa.test/public/css/styles.css
Contenido:
* {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
background: #0d1117;
color: #e6edf3;
font-family: Arial, sans-serif;
}
main {
width: min(90%, 600px);
padding: 40px;
text-align: center;
border: 1px solid #00ff41;
border-radius: 16px;
background: #161b22;
}
h1 {
color: #00ff41;
}
button {
padding: 12px 24px;
border: none;
border-radius: 8px;
background: #00ff41;
color: #0d1117;
font-weight: bold;
cursor: pointer;
}
Ahora creamos JavaScript:
sudo nano /var/www/empresa.test/public/js/script.js
Contenido:
"use strict";
const boton = document.querySelector("#saludar");
boton.addEventListener("click", () => {
alert("JavaScript funciona correctamente con Apache2.");
});
Con esto tenemos una página HTML que utiliza CSS y JavaScript.
9. Crear el archivo de configuración del VirtualHost
Ahora configuraremos Apache para reconocer nuestro sitio.
Creamos:
sudo nano /etc/apache2/sites-available/empresa.test.conf
Contenido:
<VirtualHost *:80>
ServerName empresa.test
DocumentRoot /var/www/empresa.test/public
<Directory /var/www/empresa.test/public>
Options -Indexes -FollowSymLinks
AllowOverride None
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/empresa.test-error.log
CustomLog ${APACHE_LOG_DIR}/empresa.test-access.log combined
</VirtualHost>
Vamos a explicar cada directiva.
VirtualHost *:80
<VirtualHost *:80>
Indica que la configuración corresponde a solicitudes HTTP recibidas en el puerto 80, para las direcciones de escucha que coincidan.
ServerName
ServerName empresa.test
Establece el nombre principal del sitio.
Apache utiliza este nombre para identificar el VirtualHost.
DocumentRoot
DocumentRoot /var/www/empresa.test/public
Define el directorio desde el cual se entregarán los archivos públicos.
Directory
<Directory /var/www/empresa.test/public>
Permite establecer reglas aplicables a ese directorio.
Options -Indexes -FollowSymLinks
Options -Indexes -FollowSymLinks
-Indexes evita que Apache genere automáticamente un listado de archivos cuando no encuentra un documento índice y no existe otra configuración que lo permita.
-FollowSymLinks deshabilita el seguimiento de enlaces simbólicos mediante esa opción. Puede ser necesario ajustar esta configuración para aplicaciones que dependen de enlaces simbólicos.
AllowOverride None
AllowOverride None
Impide que los archivos .htaccess sobrescriban la configuración de ese directorio.
Esto mejora el control centralizado y evita la lectura de archivos .htaccess en cada solicitud.
Require all granted
Require all granted
Autoriza el acceso HTTP al contenido de ese directorio, sujeto a otras restricciones que puedan existir.
ErrorLog
ErrorLog ${APACHE_LOG_DIR}/empresa.test-error.log
Establece el archivo donde se registrarán errores del sitio.
CustomLog
CustomLog ${APACHE_LOG_DIR}/empresa.test-access.log combined
Guarda información sobre las solicitudes HTTP recibidas.
El formato combined incluye datos como IP, fecha, recurso solicitado, código de respuesta, referente y agente de usuario.
10. Habilitar nuestro VirtualHost
Ejecutamos:
sudo a2ensite empresa.test.conf
Validamos:
sudo apache2ctl configtest
Si obtenemos:
Syntax OK
Recargamos:
sudo systemctl reload apache2
También podemos deshabilitar el sitio predeterminado de Apache si no lo necesitamos:
sudo a2dissite 000-default.conf
Después validamos y recargamos nuevamente.
Importante: cuando Apache no encuentra un VirtualHost que coincida con el nombre solicitado, puede utilizar el primer VirtualHost correspondiente a esa dirección y puerto. En producción conviene configurar explícitamente el comportamiento para dominios desconocidos.
11. Configurar /etc/hosts para utilizar dominios locales
Hasta ahora creamos el sitio, pero nuestro equipo todavía necesita saber qué dirección IP corresponde a:
empresa.test
Para pruebas locales podemos utilizar:
/etc/hosts
Este archivo permite asociar nombres de host con direcciones IP en el propio equipo.
Lo editamos:
sudo nano /etc/hosts
Agregamos:
127.0.0.1 empresa.test
Guardamos.
Ahora podemos visitar:
http://empresa.test
Nuestro navegador se conectará a la dirección 127.0.0.1, donde Apache recibirá la solicitud y seleccionará el VirtualHost correspondiente.
¿Por qué funciona?
Porque el archivo /etc/hosts proporciona una resolución local del nombre.
El navegador no necesita consultar un servidor DNS público para ese nombre cuando la resolución local tiene prioridad.
¿Otros dispositivos podrán acceder?
No automáticamente.
La dirección:
127.0.0.1
Siempre representa el propio equipo desde el que se utiliza.
Si queremos acceder desde otra computadora de nuestra red, debemos utilizar la dirección privada del servidor, por ejemplo:
192.168.1.100 empresa.test
Esta entrada debe configurarse en el equipo cliente o utilizar un DNS interno.
Además, Apache debe escuchar en una interfaz accesible, el firewall debe permitir las conexiones y la red debe tener conectividad.
12. Alojar una segunda página web en el mismo servidor
Vamos a crear otro sitio:
tienda.test
Primero creamos su directorio:
sudo mkdir -p /var/www/tienda.test/public
Creamos una página sencilla:
sudo nano /var/www/tienda.test/public/index.html
Contenido:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Mi tienda</title>
</head>
<body>
<h1>Bienvenido a mi tienda</h1>
<p>Segundo VirtualHost funcionando.</p>
</body>
</html>
Ahora creamos:
sudo nano /etc/apache2/sites-available/tienda.test.conf
Contenido:
<VirtualHost *:80>
ServerName tienda.test
DocumentRoot /var/www/tienda.test/public
<Directory /var/www/tienda.test/public>
Options -Indexes -FollowSymLinks
AllowOverride None
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/tienda.test-error.log
CustomLog ${APACHE_LOG_DIR}/tienda.test-access.log combined
</VirtualHost>
Habilitamos:
sudo a2ensite tienda.test.conf
Validamos:
sudo apache2ctl configtest
Recargamos:
sudo systemctl reload apache2
Editamos:
sudo nano /etc/hosts
Agregamos:
127.0.0.1 empresa.test tienda.test
Ahora podemos abrir:
http://empresa.test
http://tienda.test
Ambas páginas funcionan en el mismo servidor Apache, utilizando el mismo puerto 80.
La diferencia está en el nombre solicitado.
Consultar los VirtualHosts activos
Apache incluye un comando muy útil:
sudo apache2ctl -S
Este comando muestra información sobre los VirtualHosts configurados, los nombres asociados y qué sitio funciona como predeterminado.
Es una herramienta fundamental para diagnosticar conflictos entre dominios.
13. Probar VirtualHosts sin modificar /etc/hosts
Existe otra manera de comprobar que Apache reconoce un dominio.
Podemos enviar una solicitud HTTP con un encabezado Host específico:
curl -H "Host: empresa.test" http://127.0.0.1/
Para probar el segundo sitio:
curl -H "Host: tienda.test" http://127.0.0.1/
Esto resulta útil cuando queremos comprobar rápidamente la selección del VirtualHost.
No necesitamos modificar el archivo /etc/hosts para realizar estas pruebas HTTP.
Para HTTPS, donde también importa SNI, conviene utilizar herramientas como curl --resolve, que veremos más adelante.
14. Instalar PHP para ejecutar páginas dinámicas
Hasta ahora Apache puede entregar HTML, CSS y JavaScript.
Ahora vamos a habilitar PHP.
En Debian 13, la versión predeterminada de PHP de sus repositorios oficiales es PHP 8.4.
Para comprobar qué versión ofrece nuestro sistema:
apt policy php-fpm
Instalamos PHP-FPM y algunas extensiones habituales:
sudo apt install php-fpm php-cli php-mysql php-curl php-mbstring php-xml php-zip
No todas las aplicaciones necesitan todas estas extensiones. En producción debemos instalar únicamente las necesarias.
¿Qué es PHP-FPM?
PHP-FPM significa PHP FastCGI Process Manager.
Es un administrador de procesos PHP que permite ejecutar scripts PHP mediante un servicio separado del servidor web.
La comunicación funciona aproximadamente así:
NAVEGADOR
|
v
APACHE2
|
| Solicitud de archivo PHP
v
PHP-FPM
|
| Interpreta el código PHP
v
RESULTADO GENERADO
|
v
APACHE2
|
v
NAVEGADOR
Apache recibe la solicitud y delega la ejecución del archivo PHP a PHP-FPM.
Después entrega la respuesta generada.
Esta separación permite utilizar Apache con el módulo de procesamiento de conexiones mpm_event y administrar los procesos PHP mediante PHP-FPM.
15. Conectar Apache2 con PHP-FPM
Primero verificamos qué versión está instalada:
php -v
También podemos comprobar los archivos de configuración disponibles:
ls /etc/apache2/conf-available/*fpm*
En una instalación habitual de Debian 13 encontraremos:
php8.4-fpm.conf
Activamos los módulos necesarios:
sudo a2enmod proxy_fcgi setenvif
Después habilitamos la configuración de PHP-FPM:
sudo a2enconf php8.4-fpm
Comprobamos el servicio:
systemctl status php8.4-fpm
Si es necesario:
sudo systemctl enable --now php8.4-fpm
Validamos Apache:
sudo apache2ctl configtest
Y reiniciamos:
sudo systemctl restart apache2
Si tenemos instalado y habilitado mod_php con mpm_prefork, debemos revisar esa configuración antes de cambiar a mpm_event, porque los módulos MPM son mutuamente excluyentes.
Para comprobar qué MPM utiliza Apache:
apache2ctl -M | grep mpm
No debemos activar varios MPM simultáneamente.
16. Crear una página PHP de prueba
Vamos a crear:
sudo nano /var/www/empresa.test/public/prueba.php
Contenido:
<?php
declare(strict_types=1);
$fecha = date('Y-m-d H:i:s');
$version = PHP_VERSION;
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Prueba PHP</title>
</head>
<body>
<h1>PHP funciona correctamente</h1>
<p>
Fecha del servidor:
<?= htmlspecialchars($fecha, ENT_QUOTES, 'UTF-8') ?>
</p>
<p>
Versión PHP:
<?= htmlspecialchars($version, ENT_QUOTES, 'UTF-8') ?>
</p>
</body>
</html>
Abrimos:
http://empresa.test/prueba.php
Si PHP-FPM está correctamente configurado, veremos el HTML generado por PHP.
El código PHP no debe mostrarse como texto fuente en el navegador.
¿Qué pasa si el navegador descarga el archivo PHP?
Puede indicar que Apache no está enviando el archivo al intérprete PHP.
Debemos revisar la configuración de PHP-FPM antes de publicar el sitio.
Si el servidor expone código fuente PHP, especialmente archivos con credenciales, debemos retirar el sitio de acceso público hasta corregir el problema.
¿Es recomendable utilizar phpinfo()?
phpinfo() es útil para diagnosticar configuraciones, pero revela información detallada sobre PHP y el entorno.
Si creamos una página temporal con esa función, debemos eliminarla después de utilizarla.
17. Configurar DirectoryIndex
Cuando visitamos:
http://empresa.test/
No indicamos directamente qué archivo queremos abrir.
Apache utiliza la directiva DirectoryIndex para determinar qué documentos debe buscar.
Podemos establecerla dentro de nuestro VirtualHost:
DirectoryIndex index.php index.html
Esto indica que Apache buscará primero index.php y después index.html, sujeto a la configuración aplicable.
Es útil para sitios desarrollados con PHP.
En un sitio exclusivamente estático podemos priorizar:
DirectoryIndex index.html
18. Permisos correctos para los archivos web
Uno de los errores más frecuentes al configurar Apache consiste en asignar permisos excesivos.
Por ejemplo:
sudo chmod -R 777 /var/www/
No debemos utilizar este comando como solución general.
Otorgaría permisos de lectura, escritura y ejecución a todos los usuarios sobre una gran cantidad de archivos y directorios.
¿Qué usuario utiliza Apache?
En Debian, Apache suele utilizar el usuario:
www-data
Podemos comprobar los procesos:
ps aux | grep '[a]pache2'
Normalmente veremos un proceso principal con privilegios elevados y procesos trabajadores ejecutándose como www-data.
Permisos habituales
Para archivos web estáticos:
644
Para directorios:
755
Estos valores son comunes, pero no son obligatorios en todos los entornos.
Configurar permisos del laboratorio
Si estamos administrando un sitio cuyos archivos deben permanecer bajo control de root y solo ser leídos por Apache, podemos utilizar:
sudo chown -R root:root /var/www/empresa.test
Después:
sudo find /var/www/empresa.test -type d -exec chmod 755 {} +
Y:
sudo find /var/www/empresa.test -type f -exec chmod 644 {} +
Así, Apache puede leer los archivos públicos sin tener permisos generales para modificarlos.
¿Y si necesitamos editar los archivos como usuario normal?
Podemos organizar la propiedad del proyecto para que una cuenta de despliegue sea propietaria de los archivos.
Por ejemplo, en un entorno de un solo administrador:
sudo chown -R "$USER":www-data /var/www/empresa.test
Sin embargo, esto debe acompañarse de permisos adecuados y una estrategia de despliegue.
No debemos asumir que dar propiedad al grupo www-data es necesario para todos los archivos.
En producción, resulta preferible separar las cuentas que despliegan código de los procesos que lo ejecutan.
¿Qué ocurre con las carpetas de subida?
Las aplicaciones que reciben archivos pueden necesitar directorios escribibles.
Debemos otorgar escritura únicamente sobre esas rutas, no sobre todo el proyecto.
Además, las carpetas de archivos subidos deben protegerse para impedir que los usuarios ejecuten código arbitrario desde ellas.
19. Configurar un dominio real en un VPS
Ahora vamos a pasar del laboratorio local a un entorno de producción.
Supongamos que tenemos:
Dominio: ejemplo.com
VPS: 203.0.113.10
La dirección IP mostrada pertenece a un rango reservado para documentación. Debemos sustituirla por la dirección pública real de nuestro servidor.
Para publicar el sitio necesitamos:
- Un servidor con una dirección IP pública o una configuración de red que permita recibir tráfico.
- Apache instalado y funcionando.
- Un dominio registrado.
- Registros DNS que apunten al servidor.
- Puertos HTTP y HTTPS accesibles.
- Certificados TLS válidos.
- Permisos y configuraciones de seguridad adecuados.
Configurar DNS
En el panel DNS del proveedor de nuestro dominio, creamos registros similares a:
Tipo: A
Nombre: @
Valor: 203.0.113.10
Y, si queremos utilizar www:
Tipo: CNAME
Nombre: www
Valor: ejemplo.com
También podríamos utilizar otro registro A para www.
Si existen registros AAAA, deben apuntar a una dirección IPv6 correctamente configurada.
Comprobar la resolución
Podemos utilizar:
dig ejemplo.com
O:
dig +short ejemplo.com A
Para comprobar www:
dig +short www.ejemplo.com
El DNS permite que los clientes encuentren la dirección del servidor.
El DNS no crea el VirtualHost: únicamente ayuda a dirigir la conexión hacia la máquina correspondiente.
20. Crear el VirtualHost del dominio real
Creamos el directorio:
sudo mkdir -p /var/www/ejemplo.com/public
Creamos la página inicial:
sudo nano /var/www/ejemplo.com/public/index.html
Contenido:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Mi sitio en producción</title>
</head>
<body>
<h1>Bienvenido a ejemplo.com</h1>
<p>Servidor Apache2 en Debian 13.</p>
</body>
</html>
Ahora creamos el VirtualHost:
sudo nano /etc/apache2/sites-available/ejemplo.com.conf
Contenido:
<VirtualHost *:80>
ServerName ejemplo.com
ServerAlias www.ejemplo.com
ServerAdmin [email protected]
DocumentRoot /var/www/ejemplo.com/public
<Directory /var/www/ejemplo.com/public>
Options -Indexes -FollowSymLinks
AllowOverride None
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/ejemplo.com-error.log
CustomLog ${APACHE_LOG_DIR}/ejemplo.com-access.log combined
</VirtualHost>
Debemos sustituir el dominio y el correo administrativo por valores reales.
Habilitamos:
sudo a2ensite ejemplo.com.conf
Validamos:
sudo apache2ctl configtest
Recargamos:
sudo systemctl reload apache2
¿Qué es ServerAlias?
Esta directiva permite asociar nombres adicionales al mismo VirtualHost.
Por ejemplo:
ServerAlias www.ejemplo.com
Permite que el sitio responda también a solicitudes dirigidas a www.ejemplo.com.
No crea automáticamente el registro DNS ni el certificado HTTPS correspondiente.
21. Configurar el firewall
En un servidor de producción debemos controlar los puertos accesibles.
Apache utiliza habitualmente:
80 = HTTP
443 = HTTPS
Podemos utilizar UFW si forma parte de nuestra estrategia de firewall.
Instalamos:
sudo apt install ufw
Antes de activarlo en un VPS remoto, debemos permitir SSH.
Si utilizamos el puerto SSH habitual:
sudo ufw allow 22/tcp
Si utilizamos otro puerto, debemos permitir el puerto real antes de habilitar el firewall.
Después permitimos HTTP y HTTPS:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
Podemos comprobar las reglas:
sudo ufw status verbose
Y habilitarlo:
sudo ufw enable
Advertencia: una configuración incorrecta del firewall puede bloquear nuestra conexión SSH y dejarnos sin acceso remoto.
También debemos comprobar las reglas del proveedor del VPS, porque algunos servicios utilizan firewalls adicionales fuera del sistema operativo.
22. Configurar HTTPS con Let’s Encrypt
En producción debemos utilizar HTTPS para proteger la comunicación entre el navegador y el servidor.
HTTPS utiliza TLS para cifrar las conexiones y autenticar el servidor mediante certificados digitales.
Una opción ampliamente utilizada para obtener certificados es Let’s Encrypt.
Podemos administrar estos certificados con Certbot.
Instalar Certbot
En Debian:
sudo apt install certbot python3-certbot-apache
Antes de continuar, debemos comprobar que:
- El dominio apunta al servidor correcto.
- Apache responde por HTTP.
- El puerto 80 está accesible desde Internet.
- Los VirtualHosts están configurados correctamente.
Solicitar el certificado
sudo certbot --apache -d ejemplo.com -d www.ejemplo.com
Certbot intentará validar el control de ambos nombres y configurar el certificado en Apache.
Si uno de los nombres no está configurado correctamente en DNS, la validación puede fallar.
Durante el proceso, Certbot puede ofrecer opciones relacionadas con la redirección de HTTP a HTTPS.
Después podremos visitar:
https://ejemplo.com
Comprobar la renovación
Los certificados tienen una vigencia limitada.
Certbot suele instalar un mecanismo de renovación automática mediante systemd o cron, según el paquete y el entorno.
Podemos realizar una prueba:
sudo certbot renew --dry-run
También conviene comprobar qué temporizador está disponible:
systemctl list-timers | grep certbot
La renovación debe verificarse periódicamente, especialmente después de cambios en DNS, firewall o configuración del servidor.
23. Comprender un VirtualHost HTTPS
Una configuración HTTPS utiliza normalmente el puerto 443.
Un ejemplo conceptual sería:
<VirtualHost *:443>
ServerName ejemplo.com
ServerAlias www.ejemplo.com
DocumentRoot /var/www/ejemplo.com/public
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/ejemplo.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/ejemplo.com/privkey.pem
<Directory /var/www/ejemplo.com/public>
Options -Indexes -FollowSymLinks
AllowOverride None
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/ejemplo.com-ssl-error.log
CustomLog ${APACHE_LOG_DIR}/ejemplo.com-ssl-access.log combined
</VirtualHost>
Este bloque es ilustrativo: no debemos activarlo antes de que existan los certificados correspondientes y el módulo SSL esté habilitado.
Cuando utilizamos Certbot con el complemento de Apache, normalmente puede generar y administrar parte de esta configuración automáticamente.
SSLEngine on
Activa TLS para ese VirtualHost.
SSLCertificateFile
Indica la ubicación del certificado y su cadena.
SSLCertificateKeyFile
Indica la ubicación de la clave privada.
La clave privada nunca debe colocarse dentro del directorio público del sitio.
Tampoco debemos modificar indiscriminadamente los permisos de /etc/letsencrypt.
24. Configuraciones básicas de seguridad en Apache2
La seguridad de Apache depende de varias capas: configuración del servidor, sistema operativo, permisos, aplicaciones, actualizaciones y controles de red.
No existe una sola directiva que convierta automáticamente un servidor en seguro.
Ocultar información innecesaria del servidor
Podemos crear una configuración global:
sudo nano /etc/apache2/conf-available/seguridad-oplinux.conf
Contenido:
ServerTokens Prod
ServerSignature Off
TraceEnable Off
ServerTokens Prod
Reduce la información de versión que Apache anuncia en determinadas respuestas HTTP.
ServerSignature Off
Deshabilita la firma del servidor en páginas generadas por Apache, como algunas respuestas de error.
TraceEnable Off
Deshabilita el método HTTP TRACE.
Estas medidas reducen información expuesta, pero no sustituyen las actualizaciones ni corrigen vulnerabilidades.
Habilitamos:
sudo a2enconf seguridad-oplinux
Validamos:
sudo apache2ctl configtest
Y recargamos:
sudo systemctl reload apache2
25. Evitar el listado de directorios
Supongamos que tenemos:
/var/www/ejemplo.com/public/imagenes/
Y ese directorio no contiene un archivo índice.
Si Apache permite generar índices, podría mostrar un listado de sus archivos.
Para evitarlo utilizamos:
Options -Indexes
Podemos establecer esta opción dentro del bloque <Directory> de cada VirtualHost.
Esto evita la generación automática del listado, pero no convierte los archivos públicos en privados.
Si alguien conoce la URL de un archivo accesible, podría seguir solicitándolo directamente.
Los archivos confidenciales deben permanecer fuera del DocumentRoot o estar protegidos mediante reglas de acceso apropiadas.
26. Proteger archivos sensibles
Las aplicaciones pueden contener archivos como:
.env
.git/
composer.json
composer.lock
config.php
backup.sql
No todos estos archivos tienen la misma sensibilidad, pero varios pueden revelar información importante.
La primera medida es diseñar correctamente el directorio público.
Por ejemplo, en una aplicación Laravel, el DocumentRoot debe apuntar al directorio:
public/
No a la raíz completa del proyecto.
Además, podemos agregar reglas complementarias.
Por ejemplo, dentro de un VirtualHost:
<FilesMatch "^\.">
Require all denied
</FilesMatch>
Esta regla deniega archivos cuyo nombre comienza con punto dentro del alcance correspondiente.
Debemos tener cuidado con aplicaciones que necesiten servir determinados recursos bajo rutas especiales.
Para proteger archivos con extensiones sensibles, podemos utilizar una regla adicional adaptada al proyecto:
<FilesMatch "\.(?:bak|old|sql|ini|log)$">
Require all denied
</FilesMatch>
Estas reglas son defensas complementarias, no una garantía de que todos los secretos estén protegidos.
La solución principal sigue siendo no colocar información privada dentro del directorio público.
27. Cabeceras HTTP de seguridad
Apache permite configurar cabeceras HTTP mediante mod_headers.
Habilitamos:
sudo a2enmod headers
Después podemos incorporar determinadas cabeceras al VirtualHost.
Ejemplo:
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set X-Frame-Options "SAMEORIGIN"
X-Content-Type-Options
Con nosniff, indica a los navegadores que respeten los tipos MIME en los contextos donde se aplica esta protección.
Referrer-Policy
Controla qué información de referencia puede compartir el navegador al navegar hacia otros recursos.
X-Frame-Options
Con SAMEORIGIN, restringe determinados usos del sitio dentro de marcos de otros orígenes.
Debemos comprobar la compatibilidad de estas cabeceras con nuestra aplicación.
Por ejemplo, un sitio que necesita integrarse mediante iframes puede requerir una política diferente.
¿Qué sucede con HSTS?
HSTS permite indicar a los navegadores que deben utilizar HTTPS para un dominio durante un periodo determinado.
Ejemplo:
Header always set Strict-Transport-Security "max-age=31536000"
Pero solo debe habilitarse después de comprobar que HTTPS funciona correctamente y que estamos preparados para mantenerlo.
No debemos activar includeSubDomains sin verificar todos los subdominios.
¿Y Content-Security-Policy?
CSP es una protección importante contra determinadas clases de inyección de contenido.
Sin embargo, no existe una política universal que funcione correctamente para todos los sitios.
Debe diseñarse según los scripts, estilos, imágenes, fuentes y servicios externos que utiliza cada aplicación.
Copiar una CSP restrictiva sin probarla puede romper funcionalidades legítimas.
28. ¿Qué es mod_rewrite y para qué sirve?
mod_rewrite permite aplicar reglas de reescritura de URL.
Es utilizado por aplicaciones y gestores de contenido para trabajar con rutas amigables.
Por ejemplo:
https://ejemplo.com/productos
En lugar de:
https://ejemplo.com/index.php?pagina=productos
Habilitamos el módulo:
sudo a2enmod rewrite
Validamos y recargamos Apache.
¿Es obligatorio utilizar .htaccess?
No.
Las reglas pueden establecerse directamente en los archivos de configuración de Apache.
Esto suele ser preferible cuando administramos el servidor.
Sin embargo, aplicaciones como WordPress utilizan frecuentemente .htaccess para sus reglas de enlaces permanentes.
En esos casos podemos permitir las categorías de directivas necesarias mediante AllowOverride, por ejemplo:
<Directory /var/www/wordpress/public>
Options -Indexes -FollowSymLinks
AllowOverride FileInfo
Require all granted
</Directory>
Este ejemplo permite determinadas directivas relacionadas con el procesamiento de solicitudes y reescrituras.
La configuración concreta debe adaptarse a la aplicación.
No conviene utilizar AllowOverride All indiscriminadamente cuando podemos limitar las directivas necesarias.
29. Configurar Apache para WordPress
WordPress utiliza PHP y normalmente una base de datos MySQL o MariaDB.
Apache puede alojarlo mediante un VirtualHost convencional.
Por ejemplo:
<VirtualHost *:80>
ServerName blog.ejemplo.com
DocumentRoot /var/www/blog.ejemplo.com/public
DirectoryIndex index.php index.html
<Directory /var/www/blog.ejemplo.com/public>
Options -Indexes -FollowSymLinks
AllowOverride FileInfo
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/blog-error.log
CustomLog ${APACHE_LOG_DIR}/blog-access.log combined
</VirtualHost>
Además, necesitaremos instalar WordPress, configurar la base de datos, establecer permisos adecuados y habilitar HTTPS.
¿Qué sucede con wp-config.php?
Contiene información sensible de configuración.
No debemos publicarlo como archivo de texto ni permitir que Apache entregue su código fuente.
Una configuración correcta de PHP es fundamental.
También debemos mantener WordPress, sus temas y plugins actualizados.
30. Configurar Apache para Laravel
Laravel utiliza una estructura diferente a la de un sitio PHP tradicional.
Un proyecto puede contener:
mi-proyecto/
├── app/
├── bootstrap/
├── config/
├── database/
├── public/
├── resources/
├── routes/
├── storage/
└── vendor/
El directorio público es:
public/
Por eso, el VirtualHost debe apuntar allí:
DocumentRoot /var/www/mi-proyecto/public
Ejemplo:
<VirtualHost *:80>
ServerName laravel.test
DocumentRoot /var/www/mi-proyecto/public
<Directory /var/www/mi-proyecto/public>
Options -Indexes -FollowSymLinks
AllowOverride None
Require all granted
FallbackResource /index.php
</Directory>
ErrorLog ${APACHE_LOG_DIR}/laravel-error.log
CustomLog ${APACHE_LOG_DIR}/laravel-access.log combined
</VirtualHost>
FallbackResource permite dirigir solicitudes que no corresponden a archivos existentes hacia el controlador frontal de Laravel, siempre que la aplicación y el resto de la configuración sean compatibles.
Es una alternativa a determinadas reglas de reescritura.
Permisos especiales en Laravel
Laravel necesita escribir en algunos directorios, especialmente:
storage/
bootstrap/cache/
Debemos conceder escritura al usuario o grupo que ejecuta PHP-FPM únicamente donde sea necesario.
No debemos aplicar 777 a todo el proyecto.
También debemos configurar correctamente:
APP_ENV=production
APP_DEBUG=false
En producción, los mensajes detallados de depuración no deben exponerse a los visitantes.
31. Alojar varios sitios PHP: una consideración de seguridad
Cuando alojamos varios proyectos PHP en el mismo servidor, es importante considerar el aislamiento entre ellos.
En una configuración básica, todos pueden ejecutarse mediante un mismo pool PHP-FPM y una misma identidad del sistema, como www-data.
Esto significa que una aplicación vulnerable podría tener acceso a archivos de otra aplicación si los permisos del sistema lo permiten.
Para entornos con múltiples proyectos independientes podemos configurar pools PHP-FPM separados, con usuarios y grupos específicos.
Por ejemplo:
Sitio A
|
v
Pool PHP-FPM A
Usuario: sitio-a
Sitio B
|
v
Pool PHP-FPM B
Usuario: sitio-b
Esto permite establecer límites y permisos diferenciados.
Sin embargo, requiere una configuración adicional de sockets, usuarios, propietarios y VirtualHosts.
Tener varios VirtualHosts no significa que los sitios estén aislados entre sí.
Para alojamientos de múltiples clientes, debemos considerar controles de aislamiento más sólidos, actualizaciones, políticas de acceso y, cuando corresponda, contenedores o máquinas virtuales independientes.
32. Consultar los registros de Apache
Los registros son fundamentales para detectar errores y analizar solicitudes.
En Debian suelen almacenarse en:
/var/log/apache2/
Podemos consultar los errores generales:
sudo tail -f /var/log/apache2/error.log
O los de nuestro sitio:
sudo tail -f /var/log/apache2/empresa.test-error.log
Para revisar las solicitudes:
sudo tail -f /var/log/apache2/empresa.test-access.log
Códigos HTTP importantes
| Código | Significado |
|---|---|
| 200 | Solicitud correcta |
| 301 | Redirección permanente |
| 302 | Redirección temporal |
| 403 | Acceso prohibido |
| 404 | Recurso no encontrado |
| 500 | Error interno del servidor |
| 502 | Respuesta incorrecta de un servidor intermediario |
| 503 | Servicio no disponible |
Si una página PHP devuelve un error 500, debemos revisar tanto los registros de Apache como los de PHP-FPM y la propia aplicación.
Consultar PHP-FPM
sudo journalctl -u php8.4-fpm --since "15 minutes ago"
Esto puede ayudarnos a detectar problemas de ejecución, configuración o permisos.
33. Diagnosticar errores comunes
Error: Apache no inicia
Primero ejecutamos:
sudo apache2ctl configtest
Después:
sudo systemctl status apache2
Y revisamos:
sudo journalctl -u apache2 -n 50 --no-pager
Error: el puerto 80 está ocupado
Podemos consultar qué proceso lo utiliza:
sudo ss -ltnp '( sport = :80 )'
Puede existir otro servidor web, un contenedor Docker o una instancia previa utilizando ese puerto.
Error: aparece la página predeterminada de Apache
Revisamos:
sudo apache2ctl -S
También comprobamos:
ls -l /etc/apache2/sites-enabled/
El problema puede estar relacionado con el VirtualHost predeterminado o con un nombre de host que no coincide.
Error: 403 Forbidden
Debemos revisar:
- Permisos del archivo.
- Permisos de los directorios superiores.
- Directivas
<Directory>. - Reglas
Require. - ACL y otras restricciones.
Un comando útil es:
namei -l /var/www/empresa.test/public/index.html
Error: 404 Not Found
Comprobamos que el archivo exista dentro del DocumentRoot correcto.
También revisamos reglas de reescritura y rutas de la aplicación.
Error: PHP no funciona
Revisamos:
systemctl status php8.4-fpm
Después:
apache2ctl -M | grep proxy
Y comprobamos que la configuración PHP-FPM esté habilitada.
Error: el dominio funciona localmente, pero no desde Internet
Debemos comprobar:
- DNS público.
- IP del servidor.
- Firewall del sistema.
- Firewall del proveedor.
- Puertos de escucha.
- Configuración del VirtualHost.
- Conectividad desde el exterior.
34. Comprobar un dominio antes de cambiar el DNS
Cuando migramos un sitio web a otro servidor, podemos probarlo antes de modificar los registros públicos.
Por ejemplo:
curl --resolve ejemplo.com:80:203.0.113.10 http://ejemplo.com/
Esto hace que curl utilice la dirección indicada para esa solicitud, manteniendo el nombre del dominio.
Para HTTPS:
curl --resolve ejemplo.com:443:203.0.113.10 https://ejemplo.com/
En HTTPS, el certificado debe ser válido para el nombre solicitado.
Esta técnica es especialmente útil durante migraciones y pruebas de nuevos servidores.
35. Mejorar el rendimiento de Apache
Apache utiliza módulos de multiprocesamiento conocidos como MPM.
Entre ellos encontramos:
mpm_eventmpm_workermpm_prefork
Para comprobar cuál está activo:
apache2ctl -M | grep mpm
En instalaciones modernas que utilizan PHP-FPM, mpm_event suele ser una buena elección.
Sin embargo, el rendimiento depende de la memoria disponible, el número de conexiones, las aplicaciones, PHP-FPM, la base de datos y otros factores.
Consultar la configuración de módulos
apache2ctl -M
No debemos activar módulos innecesarios.
Ajustar MaxRequestWorkers
Esta directiva limita el número de solicitudes que pueden procesarse simultáneamente según el MPM utilizado.
No existe un valor universal adecuado para todos los servidores.
Debemos medir el consumo de memoria y la carga real antes de modificarlo.
Compresión y caché
Apache dispone de módulos para compresión y políticas de caché.
Por ejemplo:
sudo a2enmod deflate expires
Sin embargo, debemos diseñar cuidadosamente las reglas de caché.
No conviene almacenar en caché respuestas privadas, contenido personalizado o páginas dinámicas sin considerar sus encabezados y comportamiento.
La optimización debe basarse en pruebas, no en activar todas las funciones disponibles.
36. Mantener Apache actualizado
En producción debemos mantener actualizados Apache, PHP, el sistema operativo y las aplicaciones alojadas.
Podemos consultar los paquetes pendientes:
apt list --upgradable
Para instalar actualizaciones:
sudo apt update
sudo apt upgrade
En servidores de producción conviene planificar las actualizaciones, revisar cambios relevantes y mantener respaldos verificables.
También podemos considerar unattended-upgrades para determinadas actualizaciones automáticas de seguridad, con una política de reinicios y supervisión apropiada.
37. Lista de comprobación antes de publicar un servidor
Antes de considerar que nuestro Apache está preparado para producción, debemos revisar:
- Apache inicia correctamente y su configuración supera
apache2ctl configtest. - Cada dominio tiene su VirtualHost correspondiente.
- Los registros DNS apuntan al servidor correcto.
- Los puertos 80 y 443 están configurados y protegidos.
- HTTPS funciona y los certificados pueden renovarse.
- Los archivos privados están fuera del DocumentRoot.
- Los directorios públicos no permiten escritura innecesaria.
- PHP-FPM funciona y utiliza una versión mantenida.
- Los mensajes de depuración no están expuestos.
- Los módulos innecesarios están deshabilitados.
- Los registros se almacenan y se revisan.
- Los respaldos existen y se han probado.
- El sistema operativo recibe actualizaciones.
- Existe un procedimiento de recuperación ante fallas.
- Las aplicaciones están actualizadas y configuradas de forma segura.
Estas comprobaciones reducen riesgos, pero no sustituyen una auditoría de seguridad ni garantizan que un servidor esté libre de vulnerabilidades.
38. Resumen de comandos fundamentales
| Comando | Función |
|---|---|
sudo apt install apache2 | Instalar Apache |
systemctl status apache2 | Consultar el servicio |
sudo systemctl restart apache2 | Reiniciar Apache |
sudo systemctl reload apache2 | Recargar configuración |
sudo apache2ctl configtest | Validar configuración |
sudo apache2ctl -S | Consultar VirtualHosts |
sudo a2ensite sitio.conf | Habilitar un sitio |
sudo a2dissite sitio.conf | Deshabilitar un sitio |
sudo a2enmod rewrite | Habilitar reescritura |
sudo a2enmod headers | Habilitar cabeceras |
sudo a2enconf configuracion | Habilitar configuración adicional |
apache2ctl -M | Mostrar módulos cargados |
sudo tail -f /var/log/apache2/error.log | Seguir registros de errores |
39. Conclusión
Apache2 es mucho más que un programa para mostrar páginas HTML.
Es una herramienta que permite construir y administrar servidores web capaces de alojar múltiples sitios, ejecutar aplicaciones PHP, trabajar con dominios reales y ofrecer conexiones HTTPS.
Durante este tutorial aprendimos a instalar Apache2 en Debian 13, comprender sus directorios de configuración, crear VirtualHosts, utilizar dominios locales mediante /etc/hosts, publicar sitios con DNS real y configurar PHP-FPM.
También revisamos medidas fundamentales de seguridad, permisos, certificados, registros y mantenimiento.
La idea principal es comprender que un servidor web correctamente configurado no depende únicamente de Apache, sino de la combinación de varios elementos:
SISTEMA OPERATIVO
|
v
RED Y FIREWALL
|
v
DNS Y DOMINIOS
|
v
APACHE2
|
+--- HTML / CSS / JavaScript
|
+--- PHP-FPM
| |
| +--- Aplicaciones PHP
| +--- WordPress
| +--- Laravel
|
+--- HTTPS / TLS
|
+--- REGISTROS Y SEGURIDAD
Dominar estas herramientas es un paso importante para administrar servidores Linux, desarrollar aplicaciones web y comprender cómo funcionan los servicios de alojamiento.
Lo recomendable es comenzar en un entorno local, practicar con varios VirtualHosts y después trasladar esos conocimientos a un VPS de pruebas antes de publicar aplicaciones reales.
OPERSLINUX — Aprende Linux comprendiendo cómo funciona.
La administración de servidores se aprende practicando, investigando los errores y entendiendo la función de cada configuración, no simplemente copiando comandos.



