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 A apuntando a la IP pública del servidor, necesario para el paso de HTTPS. En la guía se usa your_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 .php en lugar de ejecutarlo: el módulo de PHP no está cargado. Comprueba con sudo apache2ctl -M | grep php; si no aparece, actívalo con sudo a2enmod php8.3 y reinicia Apache.
  • Apache no arranca tras un cambio: ejecuta sudo apache2ctl configtest para ver la línea con el error y revisa sudo journalctl -u apache2 -n 50.
  • 403 Forbidden: el directorio no tiene índice (Options -Indexes impide el listado) o Apache no puede leer los archivos. Comprueba que existe index.php o index.html y 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_domain y sudo 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.