LAMP es la combinación clásica para servir aplicaciones PHP: Linux como sistema operativo, Apache como servidor web, MySQL como base de datos y PHP como lenguaje. En este tutorial instalarás los tres componentes desde los repositorios de Ubuntu 24.04 (Apache 2.4, MySQL 8.0 y PHP 8.3), crearás un virtual host para tu dominio, comprobarás que PHP lee datos de MySQL y activarás HTTPS con un certificado gratuito de Let's Encrypt.
Requisitos previos
- Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con al menos 1 GB de RAM (2 GB recomendados si vas a alojar WordPress u otro CMS).
- Un usuario no root con privilegios
sudo. - Un dominio con un registro
Aapuntando a la IP pública del servidor, necesario para el paso de HTTPS. En la guía se usayour_domain: sustitúyelo por el tuyo.
Paso 1: Instalar Apache y abrir el firewall
Actualiza el índice de paquetes e instala Apache:
sudo apt update
sudo apt install -y apache2
En Ubuntu el servicio queda arrancado y habilitado al instalarlo. Compruébalo:
sudo systemctl status apache2 --no-pager
● apache2.service - The Apache HTTP Server
Loaded: loaded (/usr/lib/systemd/system/apache2.service; enabled; preset: enabled)
Active: active (running) since Thu 2026-09-24 10:12:03 UTC; 15s ago
Apache registra perfiles en UFW. Permite SSH antes de nada (si activas el firewall sin esta regla perderás la conexión) y después el perfil Apache Full, que abre los puertos 80 y 443:
sudo ufw allow OpenSSH
sudo ufw allow 'Apache Full'
sudo ufw enable
sudo ufw status
Status: active
To Action From
-- ------ ----
OpenSSH ALLOW Anywhere
Apache Full ALLOW Anywhere
OpenSSH (v6) ALLOW Anywhere (v6)
Apache Full (v6) ALLOW Anywhere (v6)
Abre http://your_server_ip en el navegador. Deberías ver la página "Apache2 Default Page". Desde la propia terminal puedes comprobarlo con:
curl -sI http://localhost | head -n 1
HTTP/1.1 200 OK
Paso 2: Instalar MySQL
Instala el servidor MySQL:
sudo apt install -y mysql-server
Comprueba que está en marcha y qué versión tienes:
sudo systemctl status mysql --no-pager
mysql --version
mysql Ver 8.0.43-0ubuntu0.24.04.1 for Linux on x86_64 ((Ubuntu))
Ejecuta el asistente de seguridad, que elimina los usuarios anónimos, la base de datos de prueba y el acceso remoto de root:
sudo mysql_secure_installation
Responde Y a todas las preguntas. Si activas el componente de validación de contraseñas, elige el nivel 1 (MEDIUM) o 2 (STRONG). En Ubuntu, el usuario root de MySQL se autentica con el plugin auth_socket: no usa contraseña, sino que solo permite entrar al usuario root del sistema. Por eso el asistente puede saltarse el paso de la contraseña de root, y es el comportamiento deseado.
Comprueba que puedes entrar a la consola de MySQL:
sudo mysql
Sal con exit. Las aplicaciones nunca deben usar la cuenta root: en el paso 5 crearás un usuario propio.
Paso 3: Instalar PHP
Instala PHP 8.3 junto con el módulo de Apache (libapache2-mod-php), el conector de MySQL y las extensiones que piden la mayoría de aplicaciones:
sudo apt install -y php libapache2-mod-php php-mysql php-curl php-gd php-mbstring php-xml php-zip php-intl
Comprueba la versión:
php -v
PHP 8.3.6 (cli) (built: Jul 14 2025 18:30:55) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.3.6, Copyright (c) Zend Technologies
with Zend OPcache v8.3.6, Copyright (c), by Zend Technologies
El módulo de PHP se activa automáticamente en Apache. Compruébalo:
sudo apache2ctl -M | grep php
php_module (shared)
OPcache, que guarda en memoria el código PHP ya compilado, viene activado por defecto.
Paso 4: Crear un virtual host para tu dominio
Un virtual host permite servir varios sitios desde el mismo Apache, cada uno con su dominio y su directorio. Crea el directorio del sitio y asígnalo a tu usuario para poder editar los archivos sin sudo:
sudo mkdir -p /var/www/your_domain
sudo chown -R $USER:$USER /var/www/your_domain
Crea el archivo de configuración del virtual host:
sudo nano /etc/apache2/sites-available/your_domain.conf
<VirtualHost *:80>
ServerName your_domain
ServerAlias www.your_domain
DocumentRoot /var/www/your_domain
<Directory /var/www/your_domain>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/your_domain-error.log
CustomLog ${APACHE_LOG_DIR}/your_domain-access.log combined
</VirtualHost>
Options -Indexes impide que Apache liste el contenido de los directorios sin índice, y AllowOverride All permite que las aplicaciones usen archivos .htaccess (WordPress y la mayoría de frameworks los necesitan para las URL amigables).
Habilita el sitio y el módulo rewrite, deshabilita el sitio por defecto y comprueba la sintaxis:
sudo a2ensite your_domain.conf
sudo a2enmod rewrite
sudo a2dissite 000-default.conf
sudo apache2ctl configtest
Syntax OK
Por defecto Apache busca index.html antes que index.php. Para que un index.php tenga prioridad, edita dir.conf:
sudo nano /etc/apache2/mods-enabled/dir.conf
<IfModule mod_dir.c>
DirectoryIndex index.php index.html index.cgi index.pl index.xhtml index.htm
</IfModule>
Recarga Apache para aplicar todo:
sudo systemctl reload apache2
Paso 5: Crear una base de datos y un usuario
Entra en MySQL:
sudo mysql
Crea una base de datos, un usuario con permisos solo sobre ella y una tabla de ejemplo con un par de filas. Sustituye your_strong_password por una contraseña robusta:
CREATE DATABASE app_db CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'your_strong_password';
GRANT ALL PRIVILEGES ON app_db.* TO 'app_user'@'localhost';
CREATE TABLE app_db.tareas (id INT AUTO_INCREMENT PRIMARY KEY, titulo VARCHAR(255) NOT NULL);
INSERT INTO app_db.tareas (titulo) VALUES ('Instalar Apache'), ('Instalar MySQL'), ('Instalar PHP');
exit
Comprueba que el nuevo usuario puede leer la tabla (te pedirá la contraseña):
mysql -u app_user -p -e "SELECT * FROM app_db.tareas;"
+----+-----------------+
| id | titulo |
+----+-----------------+
| 1 | Instalar Apache |
| 2 | Instalar MySQL |
| 3 | Instalar PHP |
+----+-----------------+
Paso 6: Probar PHP y la conexión a MySQL
Crea una página PHP que se conecte a la base de datos con PDO y liste las filas:
nano /var/www/your_domain/prueba.php
<?php
$pdo = new PDO(
'mysql:host=localhost;dbname=app_db;charset=utf8mb4',
'app_user',
'your_strong_password',
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
echo '<h2>Tareas</h2><ul>';
foreach ($pdo->query('SELECT titulo FROM tareas') as $fila) {
echo '<li>' . htmlspecialchars($fila['titulo']) . '</li>';
}
echo '</ul><p>PHP ' . PHP_VERSION . '</p>';
Pídela desde la terminal (o abre http://your_domain/prueba.php en el navegador):
curl -s -H "Host: your_domain" http://localhost/prueba.php
<h2>Tareas</h2><ul><li>Instalar Apache</li><li>Instalar MySQL</li><li>Instalar PHP</li></ul><p>PHP 8.3.6</p>
Si ves la lista, Apache ejecuta PHP y PHP se conecta a MySQL: el stack funciona. Borra el archivo, porque contiene la contraseña de la base de datos:
rm /var/www/your_domain/prueba.php
Paso 7: Ajustar PHP y ocultar las versiones
Los valores por defecto de PHP limitan las subidas a 2 MB, poco para la mayoría de aplicaciones. Edita el php.ini que usa Apache:
sudo nano /etc/php/8.3/apache2/php.ini
Busca estas directivas (con Ctrl+W en nano) y ajústalas:
upload_max_filesize = 64M
post_max_size = 64M
memory_limit = 256M
expose_php = Off
date.timezone = Europe/Madrid
expose_php = Off quita la cabecera X-Powered-By que revela la versión de PHP. Cambia la zona horaria por la tuya. display_errors ya viene desactivado en este archivo, que es lo correcto en producción.
Haz lo mismo con Apache, que por defecto anuncia su versión y el sistema operativo en las cabeceras y en las páginas de error:
sudo nano /etc/apache2/conf-available/security.conf
ServerTokens Prod
ServerSignature Off
Reinicia Apache para aplicar ambos cambios y verifica la cabecera:
sudo systemctl restart apache2
curl -sI -H "Host: your_domain" http://localhost | grep -i '^server'
Server: Apache
Paso 8: Activar HTTPS con Let's Encrypt
Certbot obtiene un certificado gratuito de Let's Encrypt y configura Apache para usarlo. Instálalo con su plugin de Apache:
sudo apt install -y certbot python3-certbot-apache
Comprueba que tu dominio resuelve a la IP del servidor y solicita el certificado para las dos variantes del dominio:
dig +short your_domain
sudo certbot --apache -d your_domain -d www.your_domain
Certbot te pedirá un correo para avisos de caducidad y que aceptes los términos. Al terminar crea el virtual host your_domain-le-ssl.conf en el puerto 443 y añade al del puerto 80 una redirección a HTTPS.
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/your_domain/fullchain.pem
...
Congratulations! You have successfully enabled HTTPS on https://your_domain and https://www.your_domain
Los certificados se renuevan solos gracias al temporizador de systemd que instala el paquete. Compruébalo y simula una renovación:
systemctl list-timers certbot.timer
sudo certbot renew --dry-run
Abre https://your_domain en el navegador y verifica que aparece el candado.
Paso 9: Programar copias de seguridad de la base de datos
Un script sencillo, ejecutado a diario por cron, basta para tener copias de la base de datos con rotación. Crea el directorio de destino y el script:
sudo mkdir -p /var/backups/mysql
sudo nano /usr/local/bin/mysql-backup.sh
#!/usr/bin/env bash
set -euo pipefail
BACKUP_DIR="/var/backups/mysql"
DB_NAME="app_db"
RETENTION_DAYS=7
TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
mysqldump --single-transaction --routines --triggers "$DB_NAME" \
| gzip > "$BACKUP_DIR/${DB_NAME}-${TIMESTAMP}.sql.gz"
find "$BACKUP_DIR" -name "${DB_NAME}-*.sql.gz" -mtime +"$RETENTION_DAYS" -delete
El script se ejecuta como root, así que mysqldump se autentica por auth_socket sin guardar contraseñas en ningún archivo, y --single-transaction hace una copia coherente de las tablas InnoDB sin bloquearlas. Dale permisos de ejecución y pruébalo:
sudo chmod 750 /usr/local/bin/mysql-backup.sh
sudo /usr/local/bin/mysql-backup.sh
ls -lh /var/backups/mysql
-rw-r--r-- 1 root root 1.2K Sep 24 10:40 app_db-20260924-104012.sql.gz
Prográmalo cada noche a las 3:00 creando un archivo en /etc/cron.d:
echo '0 3 * * * root /usr/local/bin/mysql-backup.sh' | sudo tee /etc/cron.d/mysql-backup
Para restaurar una copia: gunzip < archivo.sql.gz | sudo mysql app_db. Guarda también copias fuera del servidor, por ejemplo en un almacenamiento de objetos.
Solución de problemas
- El navegador descarga el archivo
.phpen lugar de ejecutarlo: el módulo de PHP no está cargado. Comprueba consudo apache2ctl -M | grep php; si no aparece, actívalo consudo a2enmod php8.3y reinicia Apache. - Apache no arranca tras un cambio: ejecuta
sudo apache2ctl configtestpara ver la línea con el error y revisasudo journalctl -u apache2 -n 50. 403 Forbidden: el directorio no tiene índice (Options -Indexesimpide el listado) o Apache no puede leer los archivos. Comprueba que existeindex.phpoindex.htmly que los permisos permiten la lectura (ls -l /var/www/your_domain).SQLSTATE[HY000] [1045] Access denied: usuario, contraseña o host incorrectos. Recuerda que'app_user'@'localhost'solo puede conectarse desde el propio servidor.- Certbot falla con un error de validación: el registro DNS no apunta al servidor o el puerto 80 está cerrado. Verifica
dig +short your_domainysudo ufw status.
Conclusión
Tienes un stack LAMP funcionando en Ubuntu 24.04: Apache sirve tu dominio por HTTPS con un certificado que se renueva solo, PHP 8.3 se conecta a MySQL con un usuario propio y la base de datos se copia cada noche. A partir de aquí puedes instalar una aplicación como WordPress en /var/www/your_domain, pasar de mod_php a PHP-FPM para ajustar mejor el consumo de memoria, o comparar este montaje con un stack LEMP basado en Nginx.
