Tema
Apache
Apache HTTP Server es el servidor web más veterano y sigue presente en la mayoría de hostings compartidos. Su gran ventaja es el archivo .htaccess, que permite configurar cada carpeta sin tocar la configuración global. En esta lección lo instalarás en tu VPS, crearás virtual hosts para varios dominios y dominarás mod_rewrite.
Concepto
- Virtual host: un bloque de configuración por sitio. Con uno solo, Apache puede servir
clientea.comyclienteb.comdesde el mismo servidor, decidiendo por el nombre que pide el navegador. - Módulos: funciones que se activan o desactivan (
rewrite,headers,ssl,expires,deflate…). - .htaccess: archivo de configuración por carpeta, leído en cada petición. Cómodo, pero algo más lento que poner las reglas en el virtual host.
Instalación (Ubuntu 24.04)
bash
sudo apt update
sudo apt install apache2 -y
# Permitir HTTP y HTTPS en el firewall
sudo ufw allow 'Apache Full'
# Comprobar
sudo systemctl status apache2
curl -I http://localhostSi visitas la IP del servidor verás la página "Apache2 Ubuntu Default Page".
Estructura de archivos
txt
/etc/apache2/
├── apache2.conf configuración global
├── ports.conf puertos que escucha (80, 443)
├── sites-available/ virtual hosts disponibles
│ └── 000-default.conf
├── sites-enabled/ enlaces a los sitios activos
├── mods-available/ módulos disponibles
├── mods-enabled/ módulos activos
└── conf-available/ configuraciones extra
/var/www/html/ sitio por defecto
/var/log/apache2/ access.log y error.logComandos propios de Apache en Ubuntu
bash
sudo a2ensite misitio.conf # activar sitio
sudo a2dissite 000-default.conf # desactivar sitio
sudo a2enmod rewrite # activar módulo
sudo a2dismod autoindex # desactivar módulo
sudo apachectl configtest # revisar sintaxis (debe decir "Syntax OK")
sudo systemctl reload apache2 # aplicar cambios sin cortar conexionesEjemplo: virtual hosts para dos clientes
1. Carpetas
bash
sudo mkdir -p /var/www/clientea.com/public
sudo mkdir -p /var/www/clienteb.com/public
sudo chown -R $USER:www-data /var/www/clientea.com /var/www/clienteb.com
echo "<h1>Cliente A funciona</h1>" > /var/www/clientea.com/public/index.html
echo "<h1>Cliente B funciona</h1>" > /var/www/clienteb.com/public/index.html2. Virtual host
bash
sudo nano /etc/apache2/sites-available/clientea.com.confapache
# /etc/apache2/sites-available/clientea.com.conf
<VirtualHost *:80>
ServerName clientea.com
ServerAlias www.clientea.com
ServerAdmin admin@clientea.com
DocumentRoot /var/www/clientea.com/public
<Directory /var/www/clientea.com/public>
Options -Indexes +FollowSymLinks
# Permite que .htaccess sobrescriba la configuración
AllowOverride All
Require all granted
</Directory>
# Logs separados por sitio
ErrorLog ${APACHE_LOG_DIR}/clientea.com-error.log
CustomLog ${APACHE_LOG_DIR}/clientea.com-access.log combined
</VirtualHost>Repite para clienteb.com cambiando nombres y rutas.
3. Activar
bash
sudo a2ensite clientea.com.conf clienteb.com.conf
sudo a2dissite 000-default.conf
sudo a2enmod rewrite headers expires deflate
sudo apachectl configtest
sudo systemctl reload apache2Probar sin DNS
Antes de cambiar el DNS puedes probar desde tu computadora editando el archivo hosts (Linux/Mac: /etc/hosts; Windows: C:\Windows\System32\drivers\etc\hosts, como administrador) y agregando 203.0.113.10 clientea.com. O con curl: curl -H "Host: clientea.com" http://203.0.113.10.
.htaccess y mod_rewrite
mod_rewrite reescribe o redirige URLs según reglas. La estructura es:
apache
RewriteEngine On
RewriteCond <qué revisar> <patrón> [banderas]
RewriteRule <patrón de URL> <destino> [banderas]Banderas más usadas: L (última regla), R=301 (redirección permanente), NC (sin distinguir mayúsculas), QSA (conservar la query string).
Recetas listas para copiar
Forzar HTTPS:
apache
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [L,R=301]Quitar www:
apache
RewriteEngine On
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteRule ^(.*)$ https://%1/$1 [L,R=301]URLs amigables a un front controller (PHP, Laravel, Slim): /productos/15 se procesa en index.php.
apache
RewriteEngine On
# Si no es un archivo ni carpeta real…
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# …mándalo a index.php conservando los parámetros
RewriteRule ^ index.php [QSA,L]En PHP lees la ruta con $_SERVER['REQUEST_URI'].
URL bonita a parámetro: /blog/mi-articulo → articulo.php?slug=mi-articulo
apache
RewriteEngine On
RewriteRule ^blog/([a-z0-9-]+)/?$ articulo.php?slug=$1 [L,QSA]Fallback para SPA (React/Vue):
apache
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]Redirigir una página vieja:
apache
Redirect 301 /contacto.html /contacto
Redirect 301 /servicios-viejos https://clientea.com/serviciosSeguridad, caché y compresión
Este .htaccess (o su equivalente dentro del virtual host) es un buen punto de partida para producción:
apache
# ---------- Seguridad ----------
Options -Indexes
ServerSignature Off
<FilesMatch "^\.|\.(env|sql|log|ini|bak|sh)$">
Require all denied
</FilesMatch>
<IfModule mod_headers.c>
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"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
</IfModule>
# ---------- Caché del navegador ----------
<IfModule mod_expires.c>
ExpiresActive On
ExpiresDefault "access plus 1 hour"
ExpiresByType text/html "access plus 0 seconds"
ExpiresByType text/css "access plus 1 year"
ExpiresByType application/javascript "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/svg+xml "access plus 1 year"
ExpiresByType font/woff2 "access plus 1 year"
</IfModule>
# ---------- Compresión ----------
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/css text/xml
AddOutputFilterByType DEFLATE application/javascript application/json image/svg+xml
</IfModule>
# ---------- Páginas de error ----------
ErrorDocument 404 /404.html
ErrorDocument 500 /500.htmlPara ocultar la versión de Apache en todo el servidor, edita /etc/apache2/conf-available/security.conf:
apache
ServerTokens Prod
ServerSignature OffLogs
bash
# Errores en tiempo real
sudo tail -f /var/log/apache2/clientea.com-error.log
# Peticiones que devolvieron 404
sudo awk '$9 == 404 {print $7}' /var/log/apache2/clientea.com-access.log | sort | uniq -c | sort -rn | headError común: el .htaccess "no hace nada"
Casi siempre es porque el virtual host tiene AllowOverride None (el valor por defecto) o porque mod_rewrite no está activo. Revisa: AllowOverride All en el <Directory> y sudo a2enmod rewrite && sudo systemctl reload apache2.
Error común: 403 Forbidden
Apache no puede leer la carpeta. Revisa permisos (carpetas 755, archivos 644) y que exista Require all granted en el bloque <Directory>.
Ejercicios
- Crea un virtual host para
portafolio.testcon DocumentRoot/var/www/portafolio/publicy logs propios. - Escribe una regla que convierta
/producto/25enproducto.php?id=25aceptando solo números. - ¿Qué comando usas antes de recargar Apache para evitar tumbar todos los sitios por un error de sintaxis?
Ver solución
apache
<VirtualHost *:80>
ServerName portafolio.test
DocumentRoot /var/www/portafolio/public
<Directory /var/www/portafolio/public>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/portafolio-error.log
CustomLog ${APACHE_LOG_DIR}/portafolio-access.log combined
</VirtualHost>apache
RewriteEngine On
RewriteRule ^producto/([0-9]+)/?$ producto.php?id=$1 [L,QSA]sudo apachectl configtest.
Resumen
- Instala con
apt install apache2; configuraciones en/etc/apache2. - Un virtual host por dominio en
sites-available, activado cona2ensite. AllowOverride Allhabilita.htaccess;a2enmod rewritehabilita mod_rewrite.- Recetas clave: forzar HTTPS, quitar www, front controller y fallback SPA.
- Siempre
apachectl configtestantes dereload. - Siguiente: Nginx.