SSD Nodes Learn
Guías Matt ConnorPor Matt Connor · Actualizado 2026-07-24

instalar LAMP stack en Ubuntu 24.04 con PHP-FPM

Guía para configurar Apache, MariaDB con unix_socket, PHP 8.3 y PHP-FPM en Ubuntu 24.04. Incluye configuración de vhost y certificados SSL con Certbot.

Qué vas a construir

Un stack LAMP consiste en cuatro componentes en un servidor Ubuntu 24.04: Linux como base, Apache para responder HTTP, MariaDB para almacenar los datos y PHP 8.3 para ejecutar el código. Al finalizar, tendrás un virtual host basado en nombres que sirve un directorio de aplicación real, una base de datos con un usuario dedicado de privilegios mínimos, PHP conectado a Apache mediante PHP-FPM y un certificado Let's Encrypt gratuito.

La instalación consta de cuatro comandos apt. Casi todo el contenido de esta guía trata sobre la configuración entre los componentes y los errores comunes que causan que un stack nuevo muestre una página en blanco, descargue el código fuente en el navegador o deniegue el acceso a la base de datos recién instalada. Cada error tiene un patrón identificable y se detalla a continuación con el texto exacto que verás.

Requisitos previos y detalles importantes

Suponga que tiene un VPS con Ubuntu 24.04 KVM recién instalado, un usuario con privilegios sudo o acceso root, y una dirección IPv4 pública. Una pila mínima funciona con 1 GB de RAM; asigne 2 GB antes de instalar una aplicación con base de datos real, ya que los buffers por defecto de MariaDB y varios procesos PHP-FPM consumen el primer gigabyte rápidamente.

Dos condiciones deben cumplirse para que Certbot funcione correctamente al final. Primero, necesita un nombre de dominio con un registro A que apunte a la IP pública del VPS; Let's Encrypt valida mediante HTTP a ese nombre, y una dirección IP sola no puede obtener un certificado. Segundo, los puertos 80 y 443 deben ser accesibles desde internet; en muchos proveedores, esto requiere abrirlos tanto en el firewall de red del panel de control como en ufw en el servidor. Los cambios de DNS pueden tardar hasta una hora en propagarse, por lo que debe configurar el registro A primero para que esté operativo cuando lo necesite.

Paso 1 - Instalar Apache y confirmar la página por defecto

sudo apt update
sudo apt install -y apache2

apt inicia y habilita el servicio automáticamente. Verifíquelo:

systemctl status apache2

Debe ver una línea que indique active (running). Ahora abra http://YOUR_SERVER_IP/ en un navegador. El Apache2 Ubuntu Default Page con el banner "It works!" es el resultado correcto; esto confirma que Apache está funcionando. Esa página se encuentra en /var/www/html/index.html y es servida por el virtual host por defecto 000-default.conf. Deshabilitará ambos más adelante; por ahora, es necesario que estén presentes.

Si la página no carga pero systemctl indica que el proceso está en ejecución, un firewall está bloqueando la conexión. Ese es el siguiente paso.

Paso 2 - Abrir el firewall para HTTP y HTTPS

El paquete apache2 registra tres perfiles de aplicación ufw. Listarlos:

sudo ufw app list

Verá Apache, Apache Full y Apache Secure. Apache es solo el puerto 80, Apache Secure es solo el 443, y Apache Full incluye ambos; ese es el que necesita, ya que añadirá TLS al final.

sudo ufw allow OpenSSH
sudo ufw allow "Apache Full"
sudo ufw enable

Permita OpenSSH antes de ejecutar ufw enable. ufw deniega todo el tráfico entrante por defecto. Activar el firewall sin una regla de SSH cortará su propia conexión al activarse; mantendrá la sesión actual, pero no podrá volver a conectarse. Confirme con sudo ufw status; debe tener OpenSSH, Apache Full y sus equivalentes en v6 con el estado ALLOW.

Paso 3 - Instalar MariaDB y asegurar la configuración

sudo apt install -y mariadb-server
systemctl status mariadb

Ubuntu 24.04 incluye MariaDB 10.11, una versión con soporte a largo plazo (LTS), por lo que no es necesario usar un repositorio externo. Con el servicio en ejecución, asegúrelo:

sudo mysql_secure_installation

Es recomendable leer las instrucciones en lugar de presionar Enter repetidamente. Cuando solicite la current root password, presione Enter, ya que aún no existe ninguna. Cuando pregunte "Switch to unix_socket authentication?", la respuesta no cambia nada porque ya está habilitada en este paquete, así que presione n. Seleccione n en "Change the root password?" por el motivo expuesto en el siguiente párrafo, y responda Y al resto: eliminar usuarios anónimos, deshabilitar el inicio de sesión remoto de root, eliminar la base de datos de prueba y recargar las tablas de privilegios.

Esta es la parte que genera confusión. En la MariaDB de Ubuntu, la cuenta de base de datos root utiliza autenticación unix_socket en lugar de una contraseña. Esto significa que la base de datos confía en el usuario del operating-system con el que ya se ha autenticado. Por lo tanto, esto funciona desde una shell de root:

sudo mysql

...y le llevará a un prompt MariaDB [(none)]> sin solicitar contraseña. El mismo comando ejecutado como un usuario sin privilegios será rechazado, que es el objetivo principal: el acceso a la raíz de la base de datos está vinculado a sudo en el sistema, y no hay contraseña que robar, realizar phishing o atacar por fuerza bruta. Esto es más seguro que una contraseña, así que no la modifique. La regla derivada de esto es: nunca configure una aplicación para usar la cuenta root. Cree un usuario dedicado para cada aplicación (Paso 7), ya que una aplicación que se conecta vía TCP con un nombre de usuario y contraseña no puede usar autenticación por socket, y debe limitar cada aplicación a su propia base de datos.

Paso 4 - Instalar PHP 8.3 con PHP-FPM

La versión por defecto de PHP en Ubuntu 24.04 es la 8.3. Instale el gestor de procesos FPM y las extensiones necesarias para una aplicación típica:

sudo apt install -y php8.3-fpm php8.3-mysql php8.3-cli \
  php8.3-curl php8.3-xml php8.3-mbstring php8.3-zip

Observe lo que no está en esa lista: libapache2-mod-php. Ese paquete antiguo integra un intérprete PHP dentro de cada proceso de Apache. Es sencillo, pero cada worker carga una copia de PHP tanto si sirve un script como si sirve una imagen estática; ambos comparten ciclo de vida y solo funciona con el MPM prefork de Apache, que es el menos eficiente. PHP-FPM, en cambio, ejecuta PHP como un grupo de procesos propio con el que Apache se comunica mediante un socket. Apache puede usar el MPM event (basado en hilos) para archivos estáticos y delegar únicamente las peticiones PHP; el pool se configura de forma independiente al servidor web y la misma configuración de FPM funcionará si decide usar nginx delante. Es el estándar actual por una buena razón.

Apache se comunica con FPM a través del módulo proxy_fcgi. Habilite el módulo, habilite la configuración instalada por el paquete FPM y reinicie:

sudo a2enmod proxy_fcgi setenvif
sudo a2enconf php8.3-fpm
sudo systemctl restart apache2

a2enconf php8.3-fpm activa /etc/apache2/conf-available/php8.3-fpm.conf, que contiene la regla para redirigir los archivos PHP al socket de FPM. El núcleo de la configuración busca cualquier archivo .php y lo redirige al socket en /run/php/php8.3-fpm.sock:

<FilesMatch ".+\.ph(ar|p|tml)$">
    SetHandler "proxy:unix:/run/php/php8.3-fpm.sock|fcgi://localhost"
</FilesMatch>

No debe editar ese archivo; viene configurado correctamente. Sin embargo, conocer la ruta del socket es fundamental para diagnosticar errores como "PHP descarga el archivo en lugar de ejecutarlo" o "Primary script unknown"; ambos ocurren porque Apache y FPM no coinciden en la ruta del socket o en el archivo correspondiente.

Paso 5 - Un host virtual basado en nombre para su aplicación

El hosting virtual basado en nombre permite que una IP sirva múltiples sitios; Apache selecciona el sitio mediante el encabezado Host: de la solicitud. Cree un directorio para la aplicación, lejos del directorio predeterminado /var/www/html:

sudo mkdir -p /var/www/testapp
sudo chown -R www-data:www-data /var/www/testapp
sudo chmod -R 755 /var/www/testapp

La propiedad de los archivos es importante. Apache y PHP-FPM se ejecutan como el usuario www-data en Ubuntu, por lo que los archivos que el servidor web debe leer —y los directorios donde la aplicación debe escribir, como una carpeta de uploads— deben pertenecer a www-data. Si también va a editar archivos con su usuario de inicio de sesión, un patrón común es ser el propietario de los archivos y añadir su usuario al grupo www-data; para un despliegue simple, www-data:www-data es la opción más predecible.

Cree el host virtual en /etc/apache2/sites-available/testapp.conf:

<VirtualHost *:80>
    ServerName app.example.com
    DocumentRoot /var/www/testapp

    <Directory /var/www/testapp>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/testapp-error.log
    CustomLog ${APACHE_LOG_DIR}/testapp-access.log combined
</VirtualHost>

Configure ServerName con su dominio real. Options -Indexes evita que Apache liste el directorio cuando no existe un archivo index; de lo contrario, los visitantes podrán navegar por su árbol de código fuente. AllowOverride All permite el funcionamiento de un archivo .htaccess, que la mayoría de las aplicaciones PHP requieren para URLs amigables; elimínelo en None para obtener una pequeña mejora de rendimiento si su aplicación no lo necesita. Habilite este sitio, deshabilite el predeterminado, verifique la configuración y recargue:

sudo a2ensite testapp
sudo a2dissite 000-default
sudo apache2ctl configtest
sudo systemctl reload apache2

apache2ctl configtest debería imprimir Syntax OK. La línea a2dissite 000-default es la que la mayoría olvida, y es la razón por la que la página predeterminada parece quedarse bloqueada —detalle cubierto en la sección de errores.

Paso 6 - Comprobar que PHP funciona y eliminar la prueba

Cree un archivo PHP de una sola línea en el directorio raíz de la aplicación:

echo "<?php phpinfo();" | sudo tee /var/www/testapp/info.php

Acceda a http://app.example.com/info.php. El resultado correcto es la tabla larga de color púrpura y gris PHP Version 8.3.x que muestra los módulos cargados, con la línea Server API indicando FPM/FastCGI. Esa última línea confirma que las peticiones pasan por PHP-FPM y no por mod_php.

Elimínelo inmediatamente:

sudo rm /var/www/testapp/info.php

phpinfo() expone su versión exacta de PHP, todas las extensiones cargadas, rutas de archivos y detalles del entorno; esto facilita el trabajo a cualquiera que busque vulnerabilidades conocidas en el servidor. Es una prueba, no una funcionalidad. Elimínelo en cuanto haya visto la página. Si en lugar de la tabla su navegador intenta descargar info.php, PHP no está configurado correctamente con Apache; diríjase a la sección de errores antes de continuar.

Paso 7 - Crear la base de datos de la aplicación y un usuario con privilegios mínimos

Acceda a la base de datos como root mediante autenticación por socket:

sudo mysql

Luego, cree una base de datos y un usuario con acceso limitado únicamente a esa base de datos:

CREATE DATABASE appdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'appuser'@'localhost' IDENTIFIED BY 'a-long-random-password';
GRANT ALL PRIVILEGES ON appdb.* TO 'appuser'@'localhost';
FLUSH PRIVILEGES;
EXIT;

Se han realizado tres elecciones deliberadas. utf8mb4 es UTF-8 de cuatro bytes real; el alias utf8 antiguo trunca silenciosamente emojis y algunos caracteres CJK, por lo que siempre debe usar utf8mb4. El permiso se otorga sobre appdb.*, no sobre *.*: este usuario solo puede acceder a su propia base de datos y a nada más. Así, una vulnerabilidad de SQL-injection en la aplicación no podrá leer las tablas de otros sitios. Además, 'appuser'@'localhost' restringe la cuenta a conexiones originadas en el propio servidor.

Pruebe la conexión con ese usuario:

mysql -u appuser -p appdb

El sistema solicitará la contraseña y le llevará a un prompt de MariaDB [appdb]>. Observe que no hay ningún flag -h; si se omite, el cliente se conecta mediante el socket Unix local, que es como MariaDB identifica a localhost. Un detalle importante: para MySQL y MariaDB, localhost significa el socket Unix y 127.0.0.1 significa una conexión TCP. En una instalación estándar de MariaDB en Ubuntu 24.04, el servidor todavía resuelve una conexión TCP desde 127.0.0.1 hacia localhost, por lo que ambas coinciden con la cuenta. Sin embargo, en servidores con skip-name-resolve habilitado (un ajuste común de rendimiento y el estándar en muchas imágenes de contenedores), ambas se consideran hosts distintos. Una aplicación que intente conectar vía 127.0.0.1 será rechazada con ERROR 1045 (28000): Access denied for user 'appuser'@'127.0.0.1' (using password: YES) incluso si la contraseña es correcta.

Por tanto, configure su aplicación con el host localhost, usuario appuser y base de datos appdb; nunca use root. Tanto mysqli de PHP como PDO cambian al socket Unix cuando el host es la cadena literal localhost, coincidiendo con la cuenta recién creada. Si un framework requiere un host TCP numérico, cree el usuario para que coincida con su método de conexión real: 'appuser'@'127.0.0.1', o @'%' (junto con una regla de firewall) solo si es estrictamente necesario acceder a la base de datos desde otra máquina.

Paso 8 - Añadir HTTPS con Certbot

Servir un formulario de inicio de sesión mediante HTTP sin cifrar envía las contraseñas en texto plano. Además, todos los navegadores modernos marcarán la página como "No segura". Certbot soluciona esto con un único comando. Instálelo con el plugin de Apache:

sudo apt install -y certbot python3-certbot-apache
sudo certbot --apache

Certbot utiliza dos plugins en este proceso. El apache authenticator demuestra que usted controla el dominio al servir brevemente un archivo de validación a través de su instancia de Apache. El apache installer reescribe su virtual host para añadir el bloque 443, apunta al nuevo certificado y redirige todo el tráfico HTTP a HTTPS por defecto. Desde Certbot 2.0 no hay pregunta sobre la redirección; use --no-redirect si necesita seguir sirviendo HTTP sin cifrar. Debido a que configuró un ServerName real en el Paso 5, Certbot detecta el dominio automáticamente. Los certificados tienen una validez de 90 días y el paquete instala un timer de systemd para renovarlos; verifique el timer con sudo certbot renew --dry-run, que debería terminar en Congratulations, all simulated renewals succeeded.

Para la guía completa sobre el proceso de validación, el timer de renovación y los requisitos de DNS y firewall, consulte la guía complementaria en emisión de certificados TLS gratuitos de Let's Encrypt con Certbot en Apache.

Backups, upgrades, and hardening

Realice una copia de seguridad de los dos elementos que mantienen su estado: las bases de datos y el directorio raíz web. Un volcado lógico nocturno es el método más sencillo y fiable: sudo sh -c 'mysqldump --all-databases --single-transaction | gzip > /root/db-$(date +%F).sql.gz', y luego se copia fuera del servidor. Es importante envolver todo el proceso en sudo sh -c: sin esto, el shell ejecuta el redireccionamiento > /root/... con su propio usuario y falla con Permission denied, porque solo mysqldump heredó el sudo. --single-transaction proporciona una instantánea consistente de las tablas InnoDB sin bloquearlas. Combínelo con un tar de /var/www y /etc/apache2/sites-available, y podrá reconstruir todo el stack en un VPS nuevo utilizando esos archivos.

Las actualizaciones son un sudo apt update && sudo apt upgrade habitual. El problema crítico es una actualización de la versión de PHP: cuando una versión futura de Ubuntu cambie el valor predeterminado a PHP 8.4, apt podría instalar php8.4-fpm junto a 8.3, el socket pasará a ser /run/php/php8.4-fpm.sock y su configuración de Apache seguirá apuntando al socket de 8.3. Habilite el nuevo archivo de configuración (sudo a2enconf php8.4-fpm) y desactive el anterior, o su sitio empezará a devolver Primary script unknown tras una actualización que de otro modo sería rutinaria. Debido a que los lanzamientos de PHP son más rápidos que una distro LTS, verifique las notas de la versión de PHP actual en lugar de fijar una versión de parche específica.

Dos pasos de endurecimiento (hardening) valen la pena desde el primer día. Primero, instale Fail2Ban para vigilar SSH en el servidor; un VPS público recibe intentos de inicio de sesión automatizados en cuestión de minutos, y una cárcel (jail) pequeña convierte miles de intentos en unos pocos antes de aplicar un bloqueo. Segundo, si prefiere gestionar los virtual hosts de Apache, las bases de datos MariaDB y los usuarios mediante un navegador en lugar de editar archivos manualmente, el panel de control web Webmin funciona sobre este mismo stack y gestiona los mismos archivos de configuración que acaba de escribir. Ninguno reemplaza el conocimiento de cada componente, pero ambos reducen la fricción en el trabajo diario.

Modos de fallo y los mensajes que verá

La página por defecto no desaparece. Editó su virtual host, recargó, pero el navegador sigue mostrando "Apache2 Ubuntu Default Page" y su banner "It works!". Apache sirve el primer virtual host que coincida; si ningún ServerName coincide con la petición, gana la configuración que aparezca primero alfabéticamente — 000-default.conf se ordena antes que testapp.conf. El nombre de host de la petición no coincide con su ServerName, o no ejecutó sudo a2dissite 000-default. Desactive el sitio por defecto, sudo systemctl reload apache2, y confirme con apache2ctl -S, que imprime el mapa de vhosts y muestra qué configuración controla el sitio por defecto. Limpie también la caché del navegador; una respuesta 200 de la página antigua puede persistir en caché.

Un archivo .php se descarga en lugar de ejecutarse. Abre info.php y el navegador descarga un archivo con el código fuente <?php original, o lo muestra como texto plano, en lugar de ejecutarlo. Apache sirve el archivo como un recurso estático porque el handler de PHP no está vinculado — omitió sudo a2enmod proxy_fcgi, o sudo a2enconf php8.3-fpm, o no reinició Apache después. Ejecute los tres pasos (Paso 4) y recargue. Confirme que el módulo está cargado con apache2ctl -M | grep fcgi, que debería listar proxy_fcgi_module. Esto es una fuga de código fuente, no un error estético; soluciónelo antes de subir contenido real al servidor.

ERROR 1698 (28000): Access denied for user 'root'@'localhost'. Ejecutó mysql -u root o mariadb -u root sin sudo. La cuenta root usa autenticación unix_socket, por lo que solo le permite el acceso si su usuario del SO es realmente root. La solución es sudo mysql — sin -u root, sin contraseña. Este mensaje es el comportamiento esperado de la autenticación por socket funcionando correctamente, no es una instalación rota.

ERROR 1045 (28000): Access denied for user 'appuser'@'127.0.0.1' desde la aplicación, con la contraseña correcta. La cuenta existe como 'appuser'@'localhost', pero su aplicación se conecta por TCP a 127.0.0.1 en un servidor donde la resolución de nombres de host está desactivada (skip-name-resolve). Por tanto, MariaDB identifica ambos como hosts distintos — localhost es el socket Unix, 127.0.0.1 es TCP. Apunte la aplicación al host localhost para que use el socket y coincida con la cuenta, o cree una segunda cuenta 'appuser'@'127.0.0.1' si el framework solo admite TCP.

AH01071: Got error 'Primary script unknown' en /var/log/apache2/testapp-error.log, con el navegador mostrando File not found.. Apache envió la petición a PHP-FPM, pero FPM no pudo encontrar el script en la ruta que Apache le proporcionó. Dos causas comunes: el socket de FPM en su configuración apunta a una versión de PHP que no está instalada (un socket php8.4 tras una actualización cuando solo corre la 8.3), o el archivo realmente no está porque DocumentRoot y el directorio real no coinciden. Verifique que el socket existe con ls -l /run/php/, confirme que DocumentRoot coincide con la ubicación del archivo, y reinicie tanto php8.3-fpm como apache2.

AH00558: apache2: Could not reliably determine the server's fully qualified domain name en cada reinicio. Esto es una advertencia inofensiva, no un error — Apache le indica que no hay un ServerName global configurado. Siléncielo escribiendo ServerName your.domain en /etc/apache2/conf-available/servername.conf y ejecutando sudo a2enconf servername.

(98)Address already in use: AH00072: make_sock: could not bind to address 0.0.0.0:80 al iniciar Apache. Otro servidor web ya ocupa el puerto 80 — a menudo es un proceso nginx residual de un experimento previo. Encuéntrelo con sudo ss -ltnp | grep :80, luego detenga y desactive el otro servicio antes de iniciar Apache.

FAQ

mod_php o PHP-FPM: ¿cuál debo usar?

Use PHP-FPM. mod_php integra un intérprete en cada proceso de Apache y obliga a usar el lento MPM prefork, por lo que Apache mantiene la carga de PHP incluso al servir una imagen estática. PHP-FPM ejecuta PHP como un pool separado e independiente, Apache se comunica con él mediante un socket, funciona con el MPM event basado en hilos (más rápido) y es compatible con nginx en el futuro. Es el estándar moderno; mod_php solo es útil para aplicaciones heredadas que dependen de comportamientos dentro del proceso.

¿Por qué mi navegador descarga el archivo PHP en lugar de ejecutarlo?

Apache trata el archivo .php como una descarga estática porque no tiene asignado un manejador de PHP. En Ubuntu 24.04 con FPM, esto significa que omitió uno de los pasos sudo a2enmod proxy_fcgi, sudo a2enconf php8.3-fpm, o el reinicio de Apache. Ejecute los tres pasos y recargue; luego verifique con apache2ctl -M | grep fcgi que proxy_fcgi_module aparezca en la lista. Hasta que lo solucione, el servidor está filtrando código fuente, así que trátelo como un error urgente.

¿Por qué se deniega el acceso root en MariaDB incluso con la contraseña correcta?

Porque no hay contraseña; la MariaDB de Ubuntu autentica la cuenta root mediante unix_socket, vinculándola al usuario root del sistema operativo. mysql -u root desde una shell normal devuelve ERROR 1698 (28000): Access denied for user 'root'@'localhost' por diseño. Conéctese con sudo mysql en su lugar y cree un usuario con autenticación por contraseña para cada aplicación en lugar de reutilizar root.

¿Cómo añado HTTPS a mi sitio LAMP?

Instale certbot y python3-certbot-apache, apunte el registro A de su dominio hacia el servidor y ejecute sudo certbot --apache. El autenticador de Apache demuestra el control del dominio a través de su instancia de Apache y el instalador reescribe el virtual host para el puerto 443 y configura la renovación automática. La guía completa de Certbot y Apache cubre el proceso de validación, el temporizador de renovación y los fallos comunes.

#lamp#apache#mariadb#php-fpm#ubuntu