Como instalar LAMP no Ubuntu 24.04 com PHP-FPM
Configure Apache, MariaDB com unix_socket, PHP 8.3 via PHP-FPM, vhost por nome e HTTPS grátis com Certbot no Ubuntu 24.04, passo a passo.
O que você está construindo
Uma pilha LAMP consiste em quatro componentes em um servidor Ubuntu 24.04: Linux como base, Apache respondendo a requisições HTTP, MariaDB armazenando os dados e PHP 8.3 executando o código. Ao final, você terá um host virtual baseado em nome servindo um diretório de aplicação real, um banco de dados com um usuário dedicado e privilégios mínimos, PHP integrado ao Apache por meio do PHP-FPM e um certificado gratuito da Let's Encrypt.
A instalação em si consiste em quatro comandos apt. Quase tudo neste guia trata da integração entre esses componentes e do pequeno conjunto de erros que faz uma pilha recém-instalada exibir uma página em branco, entregar o código-fonte ao navegador como download ou impedir o acesso ao banco de dados que você acabou de instalar. Todos esses problemas têm um padrão que você pode reconhecer, e cada um é identificado abaixo com o texto exato que você verá.
Pré-requisitos e limitações importantes
Considere uma VPS KVM Ubuntu 24.04 recém-instalada, com um usuário sudo ou root e um endereço IPv4 público. Uma pilha mínima funciona com 1 GB de RAM. Reserve 2 GB antes de instalar uma aplicação real que use banco de dados, porque os buffers padrão do MariaDB e alguns workers do PHP-FPM consomem rapidamente o primeiro gigabyte.
Duas condições precisam ser atendidas para que o Certbot funcione no final. Configure-as agora. Você precisa de um nome de domínio com um registro A apontando para o IP público da VPS. O Let's Encrypt faz a validação por HTTP nesse nome. Um endereço IP simples nunca pode receber um certificado. As portas 80 e 443 também precisam estar acessíveis pela internet. Em muitos provedores, isso significa liberá-las no firewall de rede do painel de controle e também no ufw do servidor. As alterações de DNS podem levar até uma hora para se propagar. Configure o registro A primeiro para que ele esteja ativo quando você precisar dele.
Etapa 1 - Instale o Apache e confirme a página padrão
sudo apt update
sudo apt install -y apache2apt inicia e habilita o serviço para você. Verifique:
systemctl status apache2Você deve ver uma linha com active (running). Agora abra http://YOUR_SERVER_IP/ em um navegador. A Apache2 Ubuntu Default Page, com o banner grande "It works!", é o resultado correto. Ela confirma que o Apache está atendendo, não é um erro. Essa página está em /var/www/html/index.html e é atendida pelo host virtual padrão fornecido em 000-default.conf. Você desabilitará ambos mais adiante. Por enquanto, a presença deles é exatamente o que você deve ver.
Se a página não carregar, mas systemctl indicar que o processo está em execução, há um firewall bloqueando o acesso. Essa é a próxima etapa.
Etapa 2 - Abra o firewall para HTTP e HTTPS
O pacote apache2 registra três perfis de aplicativo ufw. Liste-os:
sudo ufw app listVocê verá Apache, Apache Full e Apache Secure. Apache usa apenas a porta 80, Apache Secure usa apenas a porta 443 e Apache Full usa ambas. Esse é o perfil que você quer, porque adicionará TLS no final.
sudo ufw allow OpenSSH
sudo ufw allow "Apache Full"
sudo ufw enablePermita OpenSSH antes de executar ufw enable. Por padrão, ufw nega todo o tráfego de entrada. Ativá-lo sem uma regra para SSH interrompe sua própria conexão assim que ele é ativado. Você mantém a sessão atual, mas não consegue se reconectar. Confirme com sudo ufw status. OpenSSH, Apache Full e os equivalentes v6 devem estar todos definidos como ALLOW.
Etapa 3 - Instale o MariaDB e proteja-o
sudo apt install -y mariadb-server
systemctl status mariadbO Ubuntu 24.04 fornece o MariaDB 10.11, uma versão com suporte de longo prazo. Portanto, você não precisa de um repositório externo. Com o serviço em execução, reforce a segurança:
sudo mysql_secure_installationLeia os prompts em vez de pressionar Enter repetidamente. Quando for solicitada a senha root atual, pressione Enter, pois ela ainda não existe. Quando aparecer "Switch to unix_socket authentication?", a resposta não altera nada, porque esse recurso já está habilitado neste pacote. Portanto, pressione n. Responda n a "Change the root password?" pelo motivo explicado no próximo parágrafo. Depois, responda Y às demais perguntas: remova os usuários anônimos, desabilite o login remoto do root, remova o banco de dados de teste e recarregue as tabelas de privilégios.
Esta é a parte que costuma causar confusão. No MariaDB do Ubuntu, a conta de banco de dados root usa autenticação unix_socket, não uma senha. Isso significa que o banco confia no usuário do sistema operacional com o qual você já se autenticou. Portanto, isto funciona em um shell do root:
sudo mysql...e abre um prompt do MariaDB [(none)]> sem solicitar senha. O mesmo comando executado por um usuário sem privilégios é recusado. Esse é o objetivo: o acesso ao root do banco de dados está vinculado a sudo no servidor, e não existe uma senha para roubar, obter por phishing ou descobrir por força bruta. Isso é mais seguro do que uma senha, portanto, mantenha essa configuração. A regra resultante é: nunca configure um aplicativo para usar a conta root. Crie um usuário dedicado para cada aplicativo (Etapa 7), porque um aplicativo que se conecta por TCP usando nome de usuário e senha não pode usar autenticação por socket. Além disso, cada aplicativo deve ficar limitado ao próprio banco de dados.
Etapa 4 - Instalar PHP 8.3 com PHP-FPM
O PHP padrão do Ubuntu 24.04 é o 8.3. Instale o gerenciador de processos FPM e as extensões necessárias para uma aplicação 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-zipObserve o que não está nessa lista: libapache2-mod-php. Esse pacote mais antigo incorpora um interpretador PHP em cada processo do Apache. Ele é simples, mas cada worker mantém uma cópia do PHP, esteja atendendo um script ou uma imagem estática. Ambos compartilham o mesmo ciclo de vida, e ele funciona somente com o MPM prefork do Apache, o menos eficiente. O PHP-FPM, por outro lado, executa o PHP em seu próprio pool de processos, com o qual o Apache se comunica por meio de um socket. Assim, o Apache pode usar o MPM event, baseado em threads, para arquivos estáticos e encaminhar somente as requisições PHP. O pool pode ser ajustado independentemente do servidor web, e a mesma configuração do FPM funcionará depois se você colocar o nginx na frente. Esse é o padrão atual por um bom motivo.
O Apache acessa o FPM por meio do módulo proxy_fcgi. Ative-o, ative a configuração instalada pelo pacote FPM e reinicie:
sudo a2enmod proxy_fcgi setenvif
sudo a2enconf php8.3-fpm
sudo systemctl restart apache2a2enconf php8.3-fpm ativa /etc/apache2/conf-available/php8.3-fpm.conf, que contém a regra que encaminha arquivos PHP para o socket do FPM. A regra corresponde a qualquer arquivo .php e o encaminha para o socket em /run/php/php8.3-fpm.sock:
<FilesMatch ".+\.ph(ar|p|tml)$">
SetHandler "proxy:unix:/run/php/php8.3-fpm.sock|fcgi://localhost"
</FilesMatch>Não edite esse arquivo; ele já vem com a configuração correta. No entanto, conhecer o caminho do socket permite diagnosticar posteriormente os erros "PHP é baixado em vez de ser executado" e "Primary script unknown". Ambos ocorrem porque o Apache e o FPM estão usando caminhos diferentes para esse socket ou para o arquivo associado a ele.
Etapa 5 - Um host virtual baseado em nome para seu aplicativo
A hospedagem virtual baseada em nome permite que um único IP atenda a vários sites; o Apache escolhe o site pelo cabeçalho Host: na solicitação. Crie um diretório para o aplicativo, bem separado do /var/www/html padrão:
sudo mkdir -p /var/www/testapp
sudo chown -R www-data:www-data /var/www/testapp
sudo chmod -R 755 /var/www/testappA propriedade é importante. O Apache e o PHP-FPM são executados como o usuário www-data no Ubuntu. Portanto, os arquivos que o servidor Web precisa ler e os diretórios nos quais o aplicativo precisa gravar, como uma pasta de uploads, devem pertencer a www-data. Se você também editar arquivos com seu usuário de login, um padrão comum é ser o proprietário dos arquivos e adicionar seu usuário ao grupo www-data; para uma implantação simples, www-data:www-data é a opção mais previsível.
Crie o host virtual em /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>Defina ServerName como seu domínio real. Options -Indexes impede que o Apache liste o diretório quando não há um arquivo de índice. Caso contrário, os visitantes poderão navegar pela sua árvore de código-fonte. AllowOverride All permite o funcionamento de um arquivo .htaccess, que a maioria dos aplicativos PHP espera para usar URLs amigáveis; altere-o para None para obter um pequeno ganho de velocidade se o aplicativo não precisar desse recurso. Ative este site, desative o padrão, verifique a configuração e recarregue o serviço:
sudo a2ensite testapp
sudo a2dissite 000-default
sudo apache2ctl configtest
sudo systemctl reload apache2apache2ctl configtest deve exibir Syntax OK. A linha a2dissite 000-default é a que as pessoas mais esquecem. Por isso, a página padrão pode parecer travada mais tarde, conforme descrito na seção de falhas.
Etapa 6 - Comprove que o PHP está funcionando e exclua o arquivo de teste
Crie um arquivo PHP de uma linha no diretório raiz do aplicativo:
echo "<?php phpinfo();" | sudo tee /var/www/testapp/info.phpAcesse http://app.example.com/info.php. O resultado correto é a tabela longa em roxo e cinza PHP Version 8.3.x, que lista os módulos carregados, com a linha Server API exibindo FPM/FastCGI. Essa última linha confirma que as requisições estão passando pelo PHP-FPM, e não pelo mod_php.
Agora exclua o arquivo imediatamente:
sudo rm /var/www/testapp/info.phpphpinfo() expõe a versão exata do PHP, todas as extensões carregadas, os caminhos dos arquivos e detalhes do ambiente. Essas informações facilitam a exploração do servidor por alguém que esteja procurando uma versão com uma vulnerabilidade conhecida. Esse arquivo serve apenas para teste, não é um recurso do aplicativo. Exclua-o assim que visualizar a página. Se o navegador oferecer o download de info.php em vez de exibir a tabela, o PHP não está integrado ao Apache. Vá para a seção de falhas antes de fazer qualquer outra coisa.
Etapa 7 - Crie o banco de dados da aplicação e um usuário com privilégios mínimos
Abra o banco de dados como root autenticado pelo socket:
sudo mysqlEm seguida, crie um banco de dados e um usuário com acesso restrito exatamente a esse banco:
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;Há três escolhas deliberadas aqui. utf8mb4 é UTF-8 real de quatro bytes. O alias antigo utf8 trunca silenciosamente emojis e alguns caracteres CJK. Portanto, use sempre utf8mb4. A concessão de privilégios é feita em appdb.*, não em *.*. Esse usuário pode acessar o próprio banco de dados e nada mais. Assim, uma falha de SQL injection na aplicação não pode ler as tabelas de todos os outros sites. Além disso, 'appuser'@'localhost' restringe a conta a conexões originadas no próprio servidor.
Teste com esse usuário:
mysql -u appuser -p appdbO comando solicita a senha e abre um prompt MariaDB [appdb]>. Observe que não há a flag -h. Deixe-a de fora. Assim, o cliente se conecta pelo socket Unix local, exatamente o que o MariaDB considera localhost. Há um detalhe importante: para MySQL e MariaDB, localhost significa o socket Unix, e 127.0.0.1 significa uma conexão TCP. Em uma instalação padrão do MariaDB no Ubuntu 24.04, o servidor ainda resolve uma conexão TCP de 127.0.0.1 para localhost. Portanto, ambas correspondem à conta. Porém, em servidores com skip-name-resolve habilitado, uma otimização de desempenho comum e o padrão em muitas imagens de contêiner, os dois valores são tratados como hosts diferentes. Nesse caso, uma aplicação que se conecta a 127.0.0.1 é recusada com ERROR 1045 (28000): Access denied for user 'appuser'@'127.0.0.1' (using password: YES), mesmo quando a senha está correta.
Configure a aplicação para usar o host localhost, o usuário appuser e o banco de dados appdb. Nunca use root. O mysqli do PHP e o PDO usam o socket Unix quando o host é a string literal localhost. Isso corresponde à conta que você acabou de criar. Se um framework exigir um host TCP numérico, crie o usuário de acordo com a forma como ele realmente se conecta: 'appuser'@'127.0.0.1' ou @'%', combinado com uma regra de firewall, somente se ele precisar acessar o banco de dados a partir de outra máquina.
Etapa 8 - Adicionar HTTPS com Certbot
Servir um formulário de início de sessão através de HTTP simples envia as palavras-passe em texto não cifrado, e todos os navegadores modernos assinalam a página como "Não seguro". O Certbot resolve este problema com um comando. Instale-o com o plugin do Apache:
sudo apt install -y certbot python3-certbot-apache
sudo certbot --apacheO Certbot utiliza dois plugins neste caso. O autenticador apache prova que controla o domínio ao servir temporariamente um ficheiro de desafio através do Apache em execução. O instalador apache reescreve depois o seu host virtual para adicionar o bloco 443, aponta-o para o novo certificado e redireciona todo o tráfego HTTP para HTTPS por predefinição. O Certbot deixou de fazer uma pergunta sobre o redirecionamento desde a versão 1.4.0 (maio de 2020). Por isso, passe --no-redirect se precisar de continuar a servir HTTP simples. Como definiu um ServerName real na Etapa 5, o Certbot deteta o domínio automaticamente. Os certificados emitidos atualmente são válidos por 90 dias (o Let's Encrypt reduz o período de validade predefinido para 64 dias em 10 February 2027 e para 45 dias em 16 February 2028), e o pacote instala um temporizador systemd que os renova. Verifique o temporizador com sudo certbot renew --dry-run. O resultado deve terminar em Congratulations, all simulated renewals succeeded.
Para consultar o procedimento completo do desafio, do temporizador de renovação e dos requisitos de DNS e da firewall, consulte o guia complementar sobre emissão de certificados TLS gratuitos do Let's Encrypt com Certbot no Apache.
Backups, upgrades, and hardening
Faça backup dos dois componentes que armazenam o estado: as bases de dados e a raiz web. Um dump lógico noturno é a abordagem fiável mais simples, sudo sh -c 'mysqldump --all-databases --single-transaction | gzip > /root/db-$(date +%F).sql.gz', e depois é copiado para fora do servidor. Envolver todo o pipeline em sudo sh -c é importante: sem isso, a shell executa o redirecionamento > /root/... com o seu próprio utilizador e falha com Permission denied, porque apenas mysqldump herdou sudo. --single-transaction cria um snapshot consistente das tabelas InnoDB sem as bloquear. Combine-o com um tar de /var/www e /etc/apache2/sites-available para poder reconstruir toda a stack numa VPS nova a partir desses ficheiros.
As atualizações são uma sudo apt update && sudo apt upgrade normal. O problema mais comum é uma atualização da versão do PHP durante uma atualização de release. O Ubuntu 26.04 LTS inclui o PHP 8.5 como versão predefinida, e os pacotes php8.3-* instalados neste servidor não existem nessa release. Depois da atualização, estará a executar php8.5-fpm, o socket passará a ser /run/php/php8.5-fpm.sock e a configuração do Apache continuará a apontar para o socket da versão 8.3. Ative a nova configuração (sudo a2enconf php8.5-fpm) e desative a antiga. Caso contrário, o site começa a devolver Primary script unknown depois de uma atualização que, de resto, seria normal. Como as releases do PHP avançam mais depressa do que uma distribuição LTS, consulte as notas da release atual em vez de fixar uma versão de patch.
Vale a pena aplicar duas medidas de hardening no primeiro dia. Primeiro, configure o Fail2Ban para monitorizar o SSH no servidor. Uma VPS pública começa a receber tentativas de login automatizadas em poucos minutos, e uma jail pequena reduz milhares de tentativas a apenas algumas antes de aplicar um ban. Segundo, se preferir gerir virtual hosts do Apache, bases de dados MariaDB e utilizadores através de um browser em vez de editar ficheiros manualmente, o painel de controlo Webmin baseado na web funciona sobre esta mesma stack e altera os mesmos ficheiros de configuração que acabou de escrever. Nenhuma destas ferramentas substitui a compreensão dos componentes, mas ambas reduzem o trabalho diário.
Modos de falha e as mensagens que verá
A página predefinida não desaparece. Editou o virtual host, recarregou a configuração e o browser continua a mostrar "Apache2 Ubuntu Default Page" e o respetivo banner "It works!". O Apache serve o primeiro virtual host correspondente e, quando nenhum ServerName corresponde ao pedido, vence a primeira configuração por ordem alfabética; 000-default.conf fica antes de testapp.conf. O nome de host do pedido não corresponde ao seu ServerName ou nunca executou sudo a2dissite 000-default. Desative o virtual host predefinido com sudo systemctl reload apache2 e confirme com apache2ctl -S, que apresenta o mapa de virtual hosts e mostra qual é a configuração que detém o virtual host predefinido. Limpe também a cache do browser; uma resposta 200 colocada em cache a partir da página antiga pode continuar a ser apresentada.
Um ficheiro .php é descarregado em vez de ser executado. Abre info.php e o browser descarrega um ficheiro com o código-fonte <?php em bruto ou apresenta-o como texto simples, em vez de o executar. O Apache está a servir o ficheiro como um recurso estático porque o handler do PHP não está associado, porque não executou sudo a2enmod proxy_fcgi ou sudo a2enconf php8.3-fpm, ou porque não reiniciou o Apache depois disso. Execute os três comandos (Passo 4) e recarregue a página. Confirme que o módulo está carregado com apache2ctl -M | grep fcgi, que deverá apresentar proxy_fcgi_module. Isto é uma fuga de código-fonte, não um problema cosmético; corrija-o antes de colocar algo real no servidor.
ERROR 1698 (28000): Access denied for user 'root'@'localhost'. Executou mysql -u root ou mariadb -u root sem sudo. A conta root usa autenticação unix_socket, por isso só aceita a ligação quando o utilizador do sistema operativo é efetivamente root. A correção é sudo mysql, sem -u root e sem palavra-passe. Esta mensagem é o comportamento esperado da autenticação por socket a funcionar corretamente, não uma instalação avariada.
ERROR 1045 (28000): Access denied for user 'appuser'@'127.0.0.1' a partir da aplicação, com a palavra-passe correta. A conta existe como 'appuser'@'localhost', mas a aplicação está a ligar-se por TCP a 127.0.0.1 num servidor onde a resolução de nomes de host está desativada (skip-name-resolve), por isso o MariaDB considera os dois hosts diferentes: localhost é o socket Unix e 127.0.0.1 é TCP. Configure a aplicação para usar o host localhost, para que utilize o socket e corresponda à conta, ou crie uma segunda conta 'appuser'@'127.0.0.1' se a framework só comunicar por TCP.
AH01071: Got error 'Primary script unknown' em /var/log/apache2/testapp-error.log, com o browser a mostrar File not found.. O Apache encaminhou o pedido para o PHP-FPM, mas o FPM não conseguiu encontrar o script no caminho que o Apache lhe forneceu. Há duas causas comuns: o socket do FPM na sua configuração aponta para uma versão do PHP que não está instalada (o socket da versão 8.3 continua configurado depois de uma atualização que mudou o sistema para o PHP 8.5) ou o ficheiro não existe realmente porque DocumentRoot e o diretório real não coincidem. Verifique se o socket existe com ls -l /run/php/, confirme se DocumentRoot corresponde ao local onde o ficheiro está e reinicie php8.3-fpm (ou a versão do PHP-FPM que estiver efetivamente instalada) e apache2.
AH00558: apache2: Could not reliably determine the server's fully qualified domain name em cada reinício. Este é um aviso inofensivo, não um erro. O Apache está a indicar que não está definido nenhum ServerName global. Suprima-o escrevendo ServerName your.domain em /etc/apache2/conf-available/servername.conf e executando sudo a2enconf servername.
(98)Address already in use: AH00072: make_sock: could not bind to address 0.0.0.0:80 quando o Apache arranca. Outro servidor Web já está a utilizar a porta 80, muitas vezes um nginx abandonado de uma experiência anterior. Encontre-o com sudo ss -ltnp | grep :80 e, em seguida, pare e desative o outro serviço antes de iniciar o Apache.
FAQ
mod_php ou PHP-FPM - qual devo usar?
Use PHP-FPM. mod_php incorpora um interpretador em cada processo do Apache e força o MPM lento prefork, portanto o Apache mantém a sobrecarga do PHP mesmo ao servir uma imagem estática. O PHP-FPM executa o PHP em um pool separado e ajustado de forma independente, que o Apache acessa por um socket, funciona com o MPM encadeado mais rápido event e pode ser transferido sem alterações para o nginx posteriormente. Ele é o padrão moderno; mod_php só faz sentido para uma aplicação legada que dependa de algum comportamento no processo.
Por que meu navegador baixa o arquivo PHP em vez de executá-lo?
O Apache está tratando o arquivo .php como um download estático porque nenhum manipulador PHP está associado a ele. No Ubuntu 24.04 com FPM, isso significa que você não executou um dos comandos sudo a2enmod proxy_fcgi, sudo a2enconf php8.3-fpm ou não reiniciou o Apache depois disso. Execute os três comandos e recarregue a página. Em seguida, verifique com apache2ctl -M | grep fcgi se proxy_fcgi_module está listado. Até corrigir o problema, o servidor está expondo o código-fonte. Trate isso como urgente.
Por que o acesso de root é negado no MariaDB mesmo com a senha correta?
Como não existe uma senha, o MariaDB do Ubuntu autentica a conta root por unix_socket, vinculando-a ao usuário root do sistema operacional. mysql -u root executado em um shell normal retorna ERROR 1698 (28000): Access denied for user 'root'@'localhost' por design. Conecte-se usando sudo mysql e crie um usuário separado, autenticado por senha, para qualquer aplicação em vez de reutilizar root.
Como adiciono HTTPS ao meu site LAMP?
Instale certbot e python3-certbot-apache, aponte o registro A de um domínio para o servidor e execute sudo certbot --apache. O autenticador do apache comprova o controle do domínio por meio do Apache em execução, e o instalador reescreve o virtual host para a porta 443 e configura a renovação automática. O guia completo do Certbot e Apache explica o desafio, o temporizador de renovação e os modos comuns de falha.