Los virtual hosts permiten que un único servidor Apache sirva varios sitios web, cada uno con su dominio, su directorio y sus logs. Apache elige qué sitio responder comparando la cabecera Host de la petición con las directivas ServerName y ServerAlias de cada virtual host. En este tutorial configurarás dos dominios en Ubuntu 24.04, activarás PHP-FPM en uno de ellos, protegerás ambos con HTTPS y redirigirás www al dominio principal.

Requisitos previos

Para seguir esta guía necesitas:

  • Un servidor con Ubuntu 24.04 LTS y Apache instalado, con el perfil Apache Full permitido en UFW. Si aún no lo tienes, sigue primero la guía de instalación de Apache en Ubuntu 24.04.
  • Un usuario no root con privilegios sudo.
  • Dos dominios con registros DNS A que apunten a la IP pública del servidor, tanto para el dominio raíz como para www. En esta guía se usan site1.your_domain y site2.your_domain como ejemplo: sustitúyelos por los tuyos.

Cómo elige Apache el virtual host

Casi todos los sitios usan virtual hosts basados en nombre: todos comparten la misma IP y el puerto 80 o 443, y Apache los distingue por el nombre de dominio. Para cada petición, Apache:

  1. Busca los virtual hosts que coinciden con la IP y el puerto de la conexión (*:80).
  2. Entre ellos, elige el que tenga un ServerName o ServerAlias igual a la cabecera Host.
  3. Si ninguno coincide, responde con el primero que cargó para ese puerto, que actúa como sitio por defecto.

El punto 3 explica el problema más habitual: si un nombre no está bien escrito, Apache no da error, simplemente muestra otro sitio.

Paso 1: Crear los directorios de los sitios

Crea un directorio por sitio dentro de /var/www. Usar un subdirectorio public_html deja espacio fuera de la raíz web para archivos que no deben publicarse (configuración, copias, logs de la aplicación):

sudo mkdir -p /var/www/site1.your_domain/public_html
sudo mkdir -p /var/www/site2.your_domain/public_html

Asigna los directorios a tu usuario para desplegar sin sudo. Apache (www-data) solo necesita leer, y los permisos por defecto (755 en directorios, 644 en archivos) ya lo permiten:

sudo chown -R $USER:$USER /var/www/site1.your_domain /var/www/site2.your_domain

Paso 2: Crear una página de prueba en cada sitio

Crea un index.html distinto en cada sitio para distinguir cuál responde:

echo '<h1>Sitio 1 funciona</h1>' > /var/www/site1.your_domain/public_html/index.html
echo '<h1>Sitio 2 funciona</h1>' > /var/www/site2.your_domain/public_html/index.html

Paso 3: Crear el primer virtual host

En Ubuntu cada virtual host es un archivo en /etc/apache2/sites-available/. Nómbralo con el dominio y la extensión .conf, que a2ensite exige:

sudo nano /etc/apache2/sites-available/site1.your_domain.conf
<VirtualHost *:80>
    ServerName site1.your_domain
    ServerAlias www.site1.your_domain
    ServerAdmin [email protected]_domain
    DocumentRoot /var/www/site1.your_domain/public_html

    <Directory /var/www/site1.your_domain/public_html>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/site1.your_domain_error.log
    CustomLog ${APACHE_LOG_DIR}/site1.your_domain_access.log combined
</VirtualHost>

Las directivas clave:

  • ServerName es el nombre principal y ServerAlias acepta nombres adicionales (puedes poner varios separados por espacios, o comodines como *.site1.your_domain).
  • DocumentRoot es la carpeta que se publica.
  • Options -Indexes impide listar directorios sin archivo índice.
  • AllowOverride All permite .htaccess. Si tu aplicación no lo usa, pon None para que Apache no lo busque en cada petición.
  • ErrorLog y CustomLog separan los logs por sitio, lo que facilita mucho el diagnóstico.

Paso 4: Crear el segundo virtual host

Copia el archivo y sustituye el nombre del dominio:

sudo cp /etc/apache2/sites-available/site1.your_domain.conf /etc/apache2/sites-available/site2.your_domain.conf
sudo sed -i 's/site1\.your_domain/site2.your_domain/g' /etc/apache2/sites-available/site2.your_domain.conf

Revisa el resultado:

grep -E 'ServerName|ServerAlias|DocumentRoot' /etc/apache2/sites-available/site2.your_domain.conf
    ServerName site2.your_domain
    ServerAlias www.site2.your_domain
    DocumentRoot /var/www/site2.your_domain/public_html

Paso 5: Activar los sitios

Activa los dos virtual hosts. a2ensite crea los enlaces en sites-enabled/:

sudo a2ensite site1.your_domain.conf site2.your_domain.conf

Desactiva el sitio por defecto de Ubuntu para que no responda a nombres desconocidos con la página de bienvenida:

sudo a2dissite 000-default.conf

Comprueba la sintaxis y recarga Apache:

sudo apache2ctl configtest
sudo systemctl reload apache2
Syntax OK

Paso 6: Verificar qué virtual hosts ha cargado Apache

apache2ctl -S muestra cómo ha interpretado Apache la configuración: qué virtual hosts hay en cada puerto, sus alias y cuál es el predeterminado:

sudo apache2ctl -S
VirtualHost configuration:
*:80                   is a NameVirtualHost
         default server site1.your_domain (/etc/apache2/sites-enabled/site1.your_domain.conf:1)
         port 80 namevhost site1.your_domain (/etc/apache2/sites-enabled/site1.your_domain.conf:1)
                 alias www.site1.your_domain
         port 80 namevhost site2.your_domain (/etc/apache2/sites-enabled/site2.your_domain.conf:1)
                 alias www.site2.your_domain

Ahora prueba cada dominio. Con --resolve no necesitas esperar al DNS ni editar /etc/hosts:

curl --resolve site1.your_domain:80:127.0.0.1 http://site1.your_domain
curl --resolve site2.your_domain:80:127.0.0.1 http://site2.your_domain
<h1>Sitio 1 funciona</h1>
<h1>Sitio 2 funciona</h1>

Si el DNS ya apunta al servidor, abre ambos dominios en el navegador.

Paso 7: Ejecutar PHP en un sitio con PHP-FPM

Con PHP-FPM, Apache mantiene el MPM event y PHP corre en su propio servicio. Instala PHP-FPM y activa los módulos de proxy FastCGI:

sudo apt install php8.3-fpm
sudo a2enmod proxy_fcgi setenvif

Podrías activar PHP para todos los sitios con sudo a2enconf php8.3-fpm. Para limitarlo al sitio 2, añade este bloque dentro de su <VirtualHost *:80>:

sudo nano /etc/apache2/sites-available/site2.your_domain.conf
    <FilesMatch "\.php$">
        SetHandler "proxy:unix:/run/php/php8.3-fpm.sock|fcgi://localhost"
    </FilesMatch>
    DirectoryIndex index.php index.html

Reinicia Apache para cargar los módulos nuevos y crea un archivo de prueba:

sudo apache2ctl configtest
sudo systemctl restart apache2
echo '<?php echo "PHP " . PHP_VERSION . " en site2\n";' > /var/www/site2.your_domain/public_html/info.php
curl --resolve site2.your_domain:80:127.0.0.1 http://site2.your_domain/info.php
PHP 8.3.6 en site2

Borra el archivo de prueba al terminar:

rm /var/www/site2.your_domain/public_html/info.php

Paso 8: Activar HTTPS en todos los sitios

Instala Certbot con el plugin de Apache:

sudo apt install certbot python3-certbot-apache

Solicita un certificado por sitio. Certbot crea un virtual host *:443 para cada uno (archivos *-le-ssl.conf) y añade la redirección de HTTP a HTTPS:

sudo certbot --apache -d site1.your_domain -d www.site1.your_domain
sudo certbot --apache -d site2.your_domain -d www.site2.your_domain

Comprueba que ahora hay virtual hosts en el puerto 443:

sudo apache2ctl -S | grep 443
*:443                  is a NameVirtualHost
         port 443 namevhost site1.your_domain (/etc/apache2/sites-enabled/site1.your_domain-le-ssl.conf:2)
         port 443 namevhost site2.your_domain (/etc/apache2/sites-enabled/site2.your_domain-le-ssl.conf:2)

Confirma que la renovación automática funcionará:

sudo certbot renew --dry-run

Paso 9: Redirigir www al dominio principal

Ahora cada sitio responde igual en site1.your_domain y en www.site1.your_domain, lo que duplica las URL a efectos de SEO. Unifícalas con una redirección 301 en el virtual host HTTPS que ha creado Certbot. Asegúrate de que mod_rewrite está activo (Certbot suele activarlo al crear la redirección a HTTPS):

sudo a2enmod rewrite
sudo nano /etc/apache2/sites-available/site1.your_domain-le-ssl.conf

Añade estas líneas dentro del bloque <VirtualHost *:443>, por ejemplo justo después de DocumentRoot:

    RewriteEngine On
    RewriteCond %{HTTP_HOST} ^www\.site1\.your_domain$ [NC]
    RewriteRule ^ https://site1.your_domain%{REQUEST_URI} [R=301,L]

La regla conserva la ruta, así que /contacto en www redirige a /contacto en el dominio principal. Comprueba y recarga:

sudo apache2ctl configtest
sudo systemctl restart apache2
curl -I https://www.site1.your_domain/contacto
HTTP/1.1 301 Moved Permanently
Location: https://site1.your_domain/contacto

Repite el cambio en site2.your_domain-le-ssl.conf con su dominio. Las peticiones a http://www... pasan primero por la redirección a HTTPS de Certbot y después por esta.

Paso 10: Añadir o retirar un sitio

Con esta estructura, gestionar sitios es repetitivo y seguro:

  • Para añadir uno: crea su directorio, copia un .conf existente, cambia el dominio, ejecuta sudo a2ensite nuevo.conf, sudo apache2ctl configtest y sudo systemctl reload apache2, y después Certbot.
  • Para retirarlo temporalmente sin borrar nada: sudo a2dissite site2.your_domain.conf y sudo a2dissite site2.your_domain-le-ssl.conf, y recarga Apache.

Solución de problemas

Todos los dominios muestran el mismo sitio: el nombre de la petición no coincide con ningún ServerName o ServerAlias y Apache responde con el virtual host por defecto. Revisa la salida de sudo apache2ctl -S y comprueba que el dominio aparece exactamente igual.

ERROR: Site site1.your_domain does not exist! al ejecutar a2ensite: el archivo no está en sites-available/ o no termina en .conf.

403 Forbidden: falta Require all granted en el bloque <Directory>, la ruta de <Directory> no coincide con DocumentRoot, o www-data no puede leer la ruta. namei -l /var/www/site1.your_domain/public_html/index.html muestra los permisos de cada nivel.

El navegador descarga el archivo PHP en lugar de ejecutarlo: el bloque FilesMatch no está en ese virtual host (ni en su versión -le-ssl.conf, si lo añadiste después de ejecutar Certbot) o php8.3-fpm no está activo. Comprueba el servicio con systemctl status php8.3-fpm.

503 Service Unavailable en archivos PHP: Apache no encuentra el socket de PHP-FPM. Verifica que existe /run/php/php8.3-fpm.sock y que la ruta del SetHandler es exactamente esa.

Conclusión

Has configurado dos virtual hosts en Apache sobre Ubuntu 24.04, cada uno con su directorio y sus logs, PHP-FPM en uno de ellos, HTTPS con renovación automática y una redirección de www al dominio principal. Como siguientes pasos puedes:

  • Crear un pool de PHP-FPM por sitio para aislar aplicaciones de distintos clientes.
  • Mover las reglas de .htaccess al virtual host y usar AllowOverride None para ganar rendimiento.
  • Programar copias de seguridad de /var/www y /etc/apache2.