SSD Nodes Learn Hosting plans →
Guías Matt ConnorPor Matt Connor · Actualizado 2026-08-07

Instalar LAMP en Ubuntu 24.04 con PHP-FPM

Guía para instalar Apache, MariaDB con unix_socket, PHP 8.3 y PHP-FPM en Ubuntu 24.04, configurar un vhost y activar HTTPS gratis con Certbot.

Qué va a crear

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

La instalación se realiza con cuatro comandos apt. Casi todo lo demás de esta guía consiste en conectar estos componentes y evitar los errores habituales que hacen que una pila recién instalada muestre una página en blanco, entregue el código fuente al navegador como una descarga o rechace el acceso a la base de datos que acaba de instalar. Cada problema tiene una señal identificable. A continuación se indica cada uno junto con el texto exacto que verá.

Requisitos previos y limitaciones importantes

Suponga un VPS KVM Ubuntu 24.04 recién instalado, con un usuario que pueda usar sudo o con acceso como root, y una dirección IPv4 pública. Una pila mínima funciona con 1 GB de RAM. Asígnele 2 GB antes de instalar una aplicación real respaldada por una base de datos, porque los búferes predeterminados de MariaDB y varios procesos de trabajo de PHP-FPM consumen rápidamente el primer gigabyte.

Antes de que Certbot funcione al final, deben cumplirse dos condiciones. Configure todo ahora. Necesita un nombre de dominio con un registro A que apunte a la IP pública del VPS. Let's Encrypt valida el dominio mediante HTTP y una dirección IP sin nombre de dominio nunca puede obtener un certificado. Además, los puertos 80 y 443 deben ser accesibles desde Internet. En muchos proveedores, esto implica abrirlos en el firewall de red del panel de control y también en ufw del servidor. Los cambios de DNS pueden tardar hasta una hora en propagarse. Configure primero el registro A para que esté activo cuando lo necesite.

Paso 1 - Instale Apache y confirme la página predeterminada

sudo apt update
sudo apt install -y apache2

apt inicia y habilita el servicio. Compruébelo:

systemctl status apache2

Debe aparecer una línea con active (running). Ahora abra http://YOUR_SERVER_IP/ en un navegador. La página predeterminada de Apache2 en Ubuntu, con el banner grande "It works!", es el resultado correcto. Confirma que Apache está sirviendo contenido; no es un error. La página se encuentra en /var/www/html/index.html y la sirve el host virtual predeterminado incluido, 000-default.conf. Más adelante deshabilitará ambos. Por ahora, su presencia es exactamente lo que debe comprobar.

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

Paso 2 - Abrir el firewall para HTTP y HTTPS

El paquete apache2 registra tres perfiles de aplicación de ufw. Enumérelos:

sudo ufw app list

Verá Apache, Apache Full y Apache Secure. Apache sólo corresponde al puerto 80, Apache Secure sólo al 443 y Apache Full corresponde a ambos. Ese es el que necesita porque 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 de forma predeterminada todo el tráfico entrante. Si lo habilita sin una regla para SSH, perderá su propia conexión en cuanto se active. Mantendrá la sesión actual, pero no podrá volver a conectarse. Confírmelo con sudo ufw status. OpenSSH, Apache Full y sus equivalentes v6 deben mostrar ALLOW.

Paso 3 - Instalar MariaDB y protegerlo

sudo apt install -y mariadb-server
systemctl status mariadb

Ubuntu 24.04 incluye MariaDB 10.11, una versión con soporte a largo plazo, por lo que no necesita un repositorio externo. Con el servicio en ejecución, aplique estas medidas de protección:

sudo mysql_secure_installation

Conviene leer las indicaciones en lugar de pulsar Enter repetidamente. Cuando solicite la contraseña actual de root, pulse Enter porque todavía no existe. Cuando pregunte "Switch to unix_socket authentication?", la respuesta no cambia nada porque ya está habilitada en este paquete, así que pulse n. Responda n a "Change the root password?" por el motivo explicado en el párrafo siguiente. Después, responda Y al resto: elimine los usuarios anónimos, impida el inicio de sesión remoto de root, elimine la base de datos de prueba y vuelva a cargar las tablas de privilegios.

Esta es la parte que suele causar confusión. En MariaDB para Ubuntu, la cuenta de base de datos root usa autenticación unix_socket, no una contraseña. Esto significa que la base de datos confía en el usuario del sistema operativo con el que ya se autenticó. Por eso, lo siguiente funciona desde un shell de root:

sudo mysql

...y muestra un indicador de MariaDB [(none)]> sin solicitar ninguna contraseña. El mismo comando ejecutado por un usuario sin privilegios se rechaza, que es precisamente el objetivo: el acceso a la cuenta root de la base de datos está vinculado a sudo en el equipo y no existe ninguna contraseña que se pueda robar, suplantar o someter a fuerza bruta. Es más seguro que usar una contraseña, así que déjelo como está. La regla que se deriva de esto es la siguiente: nunca configure una aplicación para usar la cuenta root. Cree un usuario específico para cada aplicación (Paso 7), porque una aplicación que se conecta mediante TCP con un nombre de usuario y una contraseña no puede usar la autenticación por socket y debe limitarse al acceso a su propia base de datos.

Paso 4 - Instalar PHP 8.3 con PHP-FPM

El PHP predeterminado de Ubuntu 24.04 es 8.3. Instale el gestor de procesos FPM y las extensiones que necesita 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 aparece en esa lista: libapache2-mod-php. Ese paquete antiguo integra un intérprete de PHP en cada proceso de Apache. Es sencillo, pero cada worker contiene una copia de PHP, tanto si sirve un script como una imagen estática. Además, ambos comparten el mismo ciclo de vida y sólo funciona con el MPM prefork de Apache, que es el menos eficiente. PHP-FPM ejecuta PHP en su propio grupo de procesos, con el que Apache se comunica mediante un socket. Apache puede usar el MPM con hilos event para los archivos estáticos y enviar sólo las peticiones PHP al grupo de procesos. Este grupo se ajusta de forma independiente del servidor web, y la misma configuración de FPM funciona más adelante si coloca nginx delante. Es el valor predeterminado actual por un motivo justificado.

Apache accede a FPM mediante el módulo proxy_fcgi. Habilítelo, habilite la configuración que instaló 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 que dirige los archivos PHP al socket de FPM. La regla busca cualquier archivo .php y lo reenvía 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 edite ese archivo; el paquete lo instala correctamente. Sin embargo, conocer la ruta del socket permite diagnosticar más adelante los errores «PHP se descarga en lugar de ejecutarse» y «Primary script unknown». Ambos se deben a que Apache y FPM no coinciden en este socket o en el archivo que hay detrás.

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

El alojamiento virtual basado en nombre permite que una sola IP sirva varios sitios; Apache selecciona el sitio mediante la cabecera Host: de la solicitud. Crea un directorio para la aplicación, lejos del /var/www/html predeterminado:

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 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 en los que la aplicación debe escribir, como una carpeta de cargas, deben pertenecer a www-data. Si también vas a editar archivos con tu usuario de inicio de sesión, un patrón habitual es que los archivos sean de tu propiedad y añadir tu usuario al grupo www-data; para un despliegue sencillo, www-data:www-data es la opción más predecible.

Crea 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>

Establece ServerName en tu dominio real. Options -Indexes impide que Apache muestre el contenido del directorio cuando no existe un archivo de índice; de lo contrario, los visitantes pueden examinar el árbol de código fuente. AllowOverride All permite usar un archivo .htaccess, que la mayoría de las aplicaciones PHP necesitan para las URL legibles; cambia su valor a None para obtener una pequeña mejora de rendimiento si tu aplicación no lo necesita. Habilita este sitio, deshabilita el predeterminado, comprueba la configuración y vuelve a cargarla:

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

apache2ctl configtest debería mostrar Syntax OK. La línea a2dissite 000-default es la que más se olvida, y por eso la página predeterminada puede seguir apareciendo más adelante, como se explica en la sección de fallos.

Paso 6 - Compruebe que PHP se ejecuta y elimine la prueba

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

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

Visite http://app.example.com/info.php. El resultado correcto es la tabla larga de PHP Version 8.3.x, de color morado y gris, que muestra los módulos cargados. La línea Server API debe mostrar FPM/FastCGI. Esta última línea confirma que las peticiones pasan por PHP-FPM y no por mod_php.

Ahora elimínelo de inmediato:

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

phpinfo() expone la versión exacta de PHP, todas las extensiones cargadas, las rutas de los archivos y los detalles del entorno. Esto proporciona información útil a cualquiera que sondee el servidor en busca de una versión con una vulnerabilidad conocida. Es una prueba, no una funcionalidad. Elimínelo en cuanto haya visto la página. Si el navegador ofreció descargar info.php en lugar de mostrar la tabla, PHP no está conectado a Apache. Vaya a la sección de errores antes de hacer cualquier otra cosa.

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

Abra la base de datos como el usuario root autenticado mediante socket:

sudo mysql

A continuación, cree una base de datos y un usuario con acceso limitado exactamente 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;

Aquí hay tres decisiones deliberadas. utf8mb4 es UTF-8 real de cuatro bytes. El alias antiguo utf8 trunca silenciosamente los emoji y algunos caracteres CJK, por lo que debe usar siempre utf8mb4. El permiso se concede sobre appdb.*, no sobre *.*: este usuario puede acceder a su propia base de datos y a ninguna otra. Por tanto, una vulnerabilidad de inyección SQL en la aplicación no puede leer las tablas de los demás sitios. Además, 'appuser'@'localhost' restringe la cuenta a las conexiones originadas en el propio servidor.

Pruébelo con ese usuario:

mysql -u appuser -p appdb

El comando solicita la contraseña y muestra un indicador MariaDB [appdb]>. Observe que no hay ninguna opción -h. Déjela sin incluir: el cliente se conecta mediante el socket Unix local, que es exactamente lo que MariaDB considera localhost. Hay 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 como localhost. Por tanto, ambas coinciden con la cuenta. Sin embargo, en servidores con skip-name-resolve habilitado (un ajuste de rendimiento habitual y la configuración normal en muchas imágenes de contenedor), ambos valores se comparan como hosts diferentes. En ese caso, una aplicación que se conecte a 127.0.0.1 recibe ERROR 1045 (28000): Access denied for user 'appuser'@'127.0.0.1' (using password: YES), incluso cuando la contraseña es correcta.

Configure la aplicación con el host localhost, el usuario appuser y la base de datos appdb. No use root. Tanto mysqli de PHP como PDO cambian al socket Unix cuando el host es la cadena literal localhost. Así coinciden con la cuenta que acaba de crear. Si un framework exige un host TCP numérico, cree el usuario de acuerdo con la forma en que se conecta realmente: 'appuser'@'127.0.0.1' o @'%' (junto con una regla de firewall) sólo si debe acceder a la base de datos desde otro equipo.

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 claro y todos los navegadores modernos marcan la página como «No segura». Certbot lo soluciona con un solo comando. Instálelo con el plugin de Apache:

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

Certbot usa dos plugins en este caso. El autenticador de apache demuestra que controla el dominio al servir temporalmente un archivo de desafío a través de Apache, y el instalador de apache reescribe después el host virtual para añadir el bloque 443, lo configura para usar el certificado nuevo y redirige de forma predeterminada todo el tráfico HTTP a HTTPS. Desde Certbot 2.0 ya no se pregunta si se desea aplicar la redirección; pase --no-redirect si necesita seguir sirviendo HTTP sin cifrar. Como configuró un ServerName real en el paso 5, Certbot detecta el dominio automáticamente. Los certificados duran 90 días y el paquete instala un temporizador de systemd que los renueva; compruebe el temporizador con sudo certbot renew --dry-run, cuyo resultado debería terminar en Congratulations, all simulated renewals succeeded.

Para consultar el procedimiento completo del desafío, el temporizador de renovación y los requisitos de DNS y del firewall, consulte la guía complementaria sobre cómo emitir certificados TLS gratuitos de Let's Encrypt con Certbot en Apache.

Copias de seguridad, actualizaciones y refuerzo de seguridad

Haga copias de seguridad de los dos elementos que contienen el estado del sistema: las bases de datos y la raíz web. Un volcado lógico nocturno es el enfoque fiable más sencillo, sudo sh -c 'mysqldump --all-databases --single-transaction | gzip > /root/db-$(date +%F).sql.gz', y después se copia fuera del servidor. Es importante envolver toda la canalización en sudo sh -c: sin ello, el shell ejecuta la redirección > /root/... con su propio usuario y falla con Permission denied, porque sólo mysqldump heredó sudo. --single-transaction crea una instantánea coherente de las tablas de InnoDB sin bloquearlas. Combínelo con un tar de /var/www y /etc/apache2/sites-available, y podrá reconstruir toda la pila en un VPS nuevo a partir de esos archivos.

Las actualizaciones forman parte del mantenimiento habitual de sudo apt update && sudo apt upgrade. El cambio problemático es una actualización de la versión de PHP. Cuando una versión futura de Ubuntu cambie la versión predeterminada a PHP 8.4, apt puede instalar php8.4-fpm junto con 8.3, el socket pasa a ser /run/php/php8.4-fpm.sock y la configuración de Apache sigue apuntando al socket de 8.3. Habilite la nueva configuración (sudo a2enconf php8.4-fpm) y deshabilite la anterior. De lo contrario, el sitio empezará a devolver Primary script unknown después de una actualización rutinaria. Las versiones de PHP avanzan más rápido que una distribución LTS. Consulte las notas de la versión actual de PHP en lugar de fijar una versión de parche.

Conviene aplicar dos medidas de refuerzo de seguridad desde el primer día. Primero, configure Fail2Ban para monitorizar SSH en el servidor. Un VPS público recibe intentos de inicio de sesión automatizados en cuestión de minutos. Un jail pequeño reduce miles de intentos a unos pocos antes de aplicar un bloqueo. Segundo, si prefiere administrar los hosts virtuales de Apache, las bases de datos de MariaDB y los usuarios mediante un navegador en lugar de editar archivos manualmente, el panel de control web Webmin se ejecuta sobre esta misma pila y modifica los mismos archivos de configuración que acaba de crear. Ninguna de las dos opciones sustituye a comprender cada componente, pero ambas reducen el trabajo diario.

Modos de fallo y mensajes que verá

La página predeterminada no desaparece. Ha editado el host virtual, ha recargado la configuración y el navegador todavía muestra "Apache2 Ubuntu Default Page" y el aviso "It works!". Apache sirve el primer host virtual coincidente. Cuando ningún ServerName coincide con la petición, gana la primera configuración en orden alfabético; 000-default.conf se ordena antes que testapp.conf. El nombre de host de la petición no coincide con su ServerName, o no ha ejecutado sudo a2dissite 000-default. Deshabilite el predeterminado con sudo systemctl reload apache2 y confirme el resultado con apache2ctl -S, que muestra el mapa de hosts virtuales e indica qué configuración controla el predeterminado. Borre también la caché del navegador; una respuesta 200 almacenada de la página anterior puede seguir mostrándose.

Un archivo .php se descarga en lugar de ejecutarse. Abre info.php y el navegador descarga un archivo que contiene el código fuente <?php sin procesar, o lo muestra como texto plano, en lugar de ejecutarlo. Apache sirve el archivo como un recurso estático porque el controlador de PHP no está asociado, porque omitió sudo a2enmod proxy_fcgi o sudo a2enconf php8.3-fpm, o porque no reinició Apache después. Ejecute los tres pasos (Paso 4) y vuelva a cargar la página. Confirme que el módulo está cargado con apache2ctl -M | grep fcgi; debería mostrar proxy_fcgi_module. Esto es una filtración de código fuente, no un problema visual. Corríjalo antes de alojar datos reales en el servidor.

ERROR 1698 (28000): Access denied for user 'root'@'localhost'. Ha ejecutado mysql -u root o mariadb -u root sin sudo. La cuenta root usa autenticación mediante unix_socket, por lo que sólo acepta la conexión cuando el usuario del sistema operativo es realmente root. La solución es sudo mysql, sin -u root y sin contraseña. Este mensaje es el comportamiento esperado de una autenticación mediante socket configurada correctamente, no una instalación dañada.

ERROR 1045 (28000): Access denied for user 'appuser'@'127.0.0.1' desde la aplicación, aunque la contraseña sea correcta. La cuenta existe como 'appuser'@'localhost', pero la aplicación se conecta mediante TCP a 127.0.0.1 en un servidor donde la resolución de nombres de host está deshabilitada (skip-name-resolve). Por eso MariaDB considera que son dos hosts distintos: localhost es el socket Unix y 127.0.0.1 es TCP. Configure la aplicación para usar el host localhost, de modo que utilice el socket y coincida con la cuenta, o cree una segunda cuenta 'appuser'@'127.0.0.1' si el framework sólo puede usar TCP.

AH01071: Got error 'Primary script unknown' en /var/log/apache2/testapp-error.log, mientras el navegador muestra File not found.. Apache entregó la petición a PHP-FPM, pero FPM no pudo encontrar el script en la ruta que Apache le proporcionó. Hay dos causas habituales: el socket de FPM de la configuración apunta a una versión de PHP que no está instalada, por ejemplo, un socket php8.4 después de una actualización cuando sólo se ejecuta 8.3; o el archivo realmente no existe porque DocumentRoot y el directorio real no coinciden. Compruebe que el socket exista con ls -l /run/php/, confirme que DocumentRoot coincida con la ubicación del archivo y reinicie php8.3-fpm y apache2.

AH00558: apache2: Could not reliably determine the server's fully qualified domain name en cada reinicio. Es una advertencia inocua, no un error. Apache indica que no se ha definido ningún ServerName global. Elimínela 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 cuando Apache se inicia. Otro servidor web ya está usando el puerto 80, normalmente una instancia de nginx que quedó de una prueba anterior. Localícelo con sudo ss -ltnp | grep :80 y detenga y deshabilite 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 sobrecarga de PHP incluso al servir una imagen estática. PHP-FPM ejecuta PHP en un pool independiente que puede ajustarse por separado y al que Apache accede mediante un socket. También funciona con el MPM event, basado en hilos y más rápido, y permite migrar posteriormente a nginx sin cambios importantes. Es la opción predeterminada moderna. mod_php sólo tiene sentido para una aplicación heredada que dependa de algún comportamiento dentro del proceso.

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

Apache trata el archivo .php como una descarga estática porque no tiene asociado ningún controlador de PHP. En Ubuntu 24.04 con FPM, esto significa que omitió uno de sudo a2enmod proxy_fcgi, sudo a2enconf php8.3-fpm o el reinicio posterior de Apache. Ejecute los tres comandos y vuelva a cargar la configuración. Después, compruebe con apache2ctl -M | grep fcgi que aparezca proxy_fcgi_module. Hasta que lo corrija, el servidor está exponiendo el código fuente, por lo que debe tratarlo como un problema urgente.

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

Porque no hay ninguna contraseña. MariaDB en Ubuntu autentica la cuenta root mediante unix_socket y la vincula al usuario root del sistema operativo. mysql -u root desde un shell normal devuelve ERROR 1698 (28000): Access denied for user 'root'@'localhost' de forma intencionada. Conéctese con sudo mysql y cree un usuario independiente con autenticación mediante 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 del dominio al servidor y ejecute sudo certbot --apache. El autenticador de Apache demuestra que controla el dominio mediante el Apache en ejecución. Después, el instalador modifica el virtual host para el puerto 443 y configura la renovación automática. El recorrido completo de Certbot y Apache explica el desafío, el temporizador de renovación y los fallos habituales.

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