Ubuntu 24.04 安装 LAMP:Apache、MariaDB 与 PHP-FPM
在 Ubuntu 24.04 上部署 Apache、MariaDB 和 PHP 8.3 PHP-FPM,配置按名称虚拟主机、unix_socket 认证及 Certbot 免费 HTTPS,并排查空白页和源码下载问题。
构建目标
LAMP 堆栈由 Ubuntu 24.04 服务器上的 4 个部分组成:底层是 Linux,Apache 负责响应 HTTP 请求,MariaDB 负责存储数据,PHP 8.3 负责运行代码。完成后,您将拥有一个按名称匹配的虚拟主机,用于提供真实的应用目录;一个配置了专用最小权限用户的数据库;通过 PHP-FPM 接入 Apache 的 PHP;以及由 Let's Encrypt 提供的免费证书。
安装本身只需执行 4 条 apt 命令。本指南的大部分内容都用于配置这些组件之间的连接,以及说明一些会导致新建堆栈提供空白页面、将源代码作为下载内容交给浏览器,或拒绝您访问刚安装的数据库的常见错误。每种问题都有可识别的特征,下面会分别列出,并提供您实际看到的确切文本。
前置条件和需要提前说明的问题
假设您使用的是全新的 Ubuntu 24.04 KVM VPS,并且拥有 sudo 用户或 root,以及一个公网 IPv4 地址。运行最小化堆栈需要 1 GB 内存;在部署真正使用数据库的应用前,请将内存增加到 2 GB,因为 MariaDB 的默认缓冲区加上少量 PHP-FPM 工作进程,很快就会占用第一个 GB。
Certbot 在最后一步正常工作需要满足两个条件,请现在先完成配置。您需要一个域名,并为其添加指向 VPS 公网 IP 的 A 记录。Let's Encrypt 会通过 HTTP 验证该域名,裸 IP 地址无法获取证书。此外,端口 80 和 443 必须能从互联网访问。对于许多服务商,这意味着既要在控制面板的网络防火墙中放行端口,也要在服务器上的 ufw 中放行端口。DNS 更改最多可能需要 1 小时才能传播,因此请先设置 A 记录;到需要使用时,它通常已经生效。
步骤 1 - 安装 Apache 并确认默认页面
sudo apt update
sudo apt install -y apache2apt 会为您启动并启用该服务。检查状态:
systemctl status apache2您应看到一行内容为 active (running)。现在在浏览器中打开 http://YOUR_SERVER_IP/。显示带有醒目“It works!”横幅的 Apache2 Ubuntu Default Page,说明结果正确,表示 Apache 正在提供服务,并非配置错误。该页面位于 /var/www/html/index.html,由随软件提供的默认虚拟主机 000-default.conf 提供服务。稍后您会停用这两项;目前看到它们正是预期结果。
如果页面完全无法加载,但 systemctl 显示进程正在运行,则说明请求被防火墙阻止。下一步将处理此问题。
步骤 2 - 为 HTTP 和 HTTPS 开放防火墙
apache2 软件包注册了 3 个 ufw 应用配置文件。列出这些配置文件:
sudo ufw app list您将看到 Apache、Apache Full 和 Apache Secure。Apache 仅对应端口 80,Apache Secure 仅对应端口 443,Apache Full 同时对应两者。您需要使用 Apache Full,因为将在最后添加 TLS。
sudo ufw allow OpenSSH
sudo ufw allow "Apache Full"
sudo ufw enable运行 ufw enable 之前,先允许 OpenSSH。ufw 默认拒绝所有入站流量。如果未配置 SSH 规则就启用它,防火墙激活后会立即切断当前连接。您可以保留当前会话,但之后无法重新连接。使用 sudo ufw status 确认;您需要看到 OpenSSH、Apache Full 及其 v6 对应项全部显示为 ALLOW。
第 3 步 - 安装 MariaDB 并完成安全配置
sudo apt install -y mariadb-server
systemctl status mariadbUbuntu 24.04 随附 MariaDB 10.11,这是一个长期支持版本,因此无需添加外部软件源。服务运行后,执行安全加固:
sudo mysql_secure_installation请认真阅读提示,不要直接连续按 Enter。当提示输入当前 root 密码时,按 Enter,因为目前还没有密码。当提示 “Switch to unix_socket authentication?” 时,回答不会改变任何设置,因为此软件包已启用该认证方式,因此按 n。对于 “Change the root password?”,请根据下一段的说明回答 n;其余提示都回答 Y:删除匿名用户、禁止 root 远程登录、删除测试数据库,并重新加载权限表。
下面是最容易让人困惑的部分。在 Ubuntu 的 MariaDB 中,root 数据库账户使用 unix_socket 认证,而不是密码认证。这意味着数据库信任您已经完成身份验证的操作系统用户。因此,以下命令可在 root shell 中运行:
sudo mysql……随后会进入 MariaDB [(none)]> 提示符,且不会要求输入密码。非特权用户运行同一命令时会被拒绝,这正是该机制的作用:访问数据库 root 账户的权限绑定到主机上的 sudo,不存在可被窃取、钓鱼或暴力破解的密码。这比使用密码更安全,因此不要修改它。由此得出的规则是:绝不要让应用使用 root 账户。请为每个应用创建专用用户(第 7 步),因为通过 TCP 使用用户名和密码连接的应用无法使用 socket 认证;同时,应将每个应用的权限限制在自己的数据库内。
步骤 4 - 安装 PHP 8.3 和 PHP-FPM
Ubuntu 24.04 的默认 PHP 版本是 8.3。安装 FPM 进程管理器,以及典型应用所需的扩展:
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请注意,列表中没有 libapache2-mod-php。这个旧软件包会在每个 Apache 进程中嵌入 PHP 解释器。它配置简单,但每个工作进程都会携带一份 PHP 副本,无论当前处理的是脚本还是静态图片;二者的生命周期也相互绑定。此外,它只能与 Apache 的 prefork MPM 配合使用,而这是效率最低的 MPM。PHP-FPM 则将 PHP 作为独立的进程池运行,Apache 通过套接字与其通信。这样,Apache 可使用线程化的 event MPM 处理静态文件,只将 PHP 请求转发给 FPM;进程池也可以独立于 Web 服务器调优。以后即使在前面改用 nginx,完全相同的 FPM 配置仍然适用。它成为当前默认方案是有原因的。
Apache 通过 proxy_fcgi 模块访问 FPM。启用该模块,并启用 FPM 软件包安装的配置,然后重启:
sudo a2enmod proxy_fcgi setenvif
sudo a2enconf php8.3-fpm
sudo systemctl restart apache2a2enconf php8.3-fpm 会启用 /etc/apache2/conf-available/php8.3-fpm.conf,其中包含将 PHP 文件路由到 FPM 套接字的规则。该规则匹配任何 .php 文件,并将其转发到 /run/php/php8.3-fpm.sock 处的套接字:
<FilesMatch ".+\.ph(ar|p|tml)$">
SetHandler "proxy:unix:/run/php/php8.3-fpm.sock|fcgi://localhost"
</FilesMatch>不要编辑该文件;软件包提供的内容已经正确。但了解套接字路径有助于排查之后出现的“PHP 被下载而不是执行”和“Primary script unknown”错误。这两类错误最终都源于 Apache 与 FPM 对该套接字或其背后文件的配置不一致。
步骤 5 - 为应用配置基于域名的虚拟主机
基于域名的虚拟主机允许一个 IP 提供多个网站;Apache 根据请求中的 Host: 标头选择网站。为应用创建一个目录,并与默认的 /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所有权很重要。在 Ubuntu 上,Apache 和 PHP-FPM 都以 www-data 用户运行。因此,Web 服务器必须读取的文件,以及应用必须写入的目录(例如 uploads 目录),应归 www-data 所有。如果您还需要使用登录用户编辑文件,通常可以让文件归您所有,并将您的用户加入 www-data 组;对于普通部署,www-data:www-data 最不容易引发问题。
在 /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>将 ServerName 设置为您的实际域名。Options -Indexes 会阻止 Apache 在没有索引文件时列出目录,否则访问者可以浏览您的源代码目录。AllowOverride All 允许 .htaccess 文件生效,大多数 PHP 应用都需要它来支持美化 URL;如果应用不需要此功能,可将其降为 None,以获得小幅性能提升。启用此网站,禁用默认网站,检查配置,然后重新加载:
sudo a2ensite testapp
sudo a2dissite 000-default
sudo apache2ctl configtest
sudo systemctl reload apache2apache2ctl configtest 应输出 Syntax OK。a2dissite 000-default 行是人们最容易遗漏的配置,这也是默认页面后来看起来一直未被替换的原因,失败排查部分会对此进行说明。
步骤 6 - 验证 PHP 正常运行,然后删除验证文件
在应用根目录中放置一个只有一行内容的 PHP 文件:
echo "<?php phpinfo();" | sudo tee /var/www/testapp/info.php访问 http://app.example.com/info.php。正确结果是一个较长的紫色和灰色 PHP Version 8.3.x 表格,其中 Server API 行显示 FPM/FastCGI。最后一行确认请求正在通过 PHP-FPM 处理,而不是 mod_php。
现在立即删除该文件:
sudo rm /var/www/testapp/info.phpphpinfo() 会暴露确切的 PHP 版本、所有已加载的扩展、文件路径和环境详细信息。这些信息会帮助探测服务器的人找到存在已知漏洞的版本。它只是测试文件,不是功能组件。看到页面后立即删除它。如果浏览器没有显示表格,而是提示您 下载 info.php,说明 PHP 尚未接入 Apache;先跳转到故障排查部分,再执行其他操作。
第 7 步 - 创建应用数据库和最小权限用户
以通过套接字身份验证的 root 身份打开数据库:
sudo mysql然后创建一个数据库和一个仅限访问该数据库的用户:
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;这里有 3 个明确的选择。utf8mb4 才是真正的 4 字节 UTF-8,旧的 utf8 别名会静默截断表情符号和部分 CJK 字符,因此应始终使用 utf8mb4。授权针对的是 appdb.*,而不是 *.*:该用户只能访问自己的数据库,不能访问其他内容。因此,即使应用存在 SQL 注入漏洞,也无法读取其他站点的所有表。'appuser'@'localhost' 会将该账户限制为只能接受来自本机的连接。
以该用户身份进行测试:
mysql -u appuser -p appdb该命令会要求输入密码,然后显示 MariaDB [appdb]> 提示符。注意,这里没有 -h 标志。请不要添加它,这样客户端会通过本地 Unix 套接字连接,这正是 MariaDB 所称的 localhost。需要注意的一点是:对于 MySQL 和 MariaDB,localhost 表示 Unix 套接字,127.0.0.1 表示 TCP 连接。在标准 Ubuntu 24.04 MariaDB 中,服务器仍会将来自 127.0.0.1 的 TCP 连接解析回 localhost,因此两者都能匹配该账户。但在启用了 skip-name-resolve 的服务器上(这是一种常见的性能优化,在许多容器镜像中也很常见),两种连接会按不同主机进行匹配。此时,应用连接到 127.0.0.1 会因 ERROR 1045 (28000): Access denied for user 'appuser'@'127.0.0.1' (using password: YES) 被拒绝,即使密码正确。
因此,请将应用配置为主机 localhost、用户 appuser 和数据库 appdb,不要使用 root。当主机是字面字符串 localhost 时,PHP 的 mysqli 和 PDO 都会切换为 Unix 套接字,从而匹配刚刚创建的账户。如果框架要求使用数字形式的 TCP 主机,请根据其实际连接方式创建匹配的用户:使用 'appuser'@'127.0.0.1';只有在必须从其他机器访问数据库时,才使用 @'%'(并配合防火墙规则)。
第 8 步 - 使用 Certbot 添加 HTTPS
通过纯 HTTP 提供登录表单会以明文传输密码,所有现代浏览器都会将该页面标记为“不安全”。Certbot 可通过一条命令解决此问题。使用 Apache 插件安装:
sudo apt install -y certbot python3-certbot-apache
sudo certbot --apacheCertbot 在这里使用两个插件。apache authenticator 会通过运行中的 Apache 短暂提供质询文件,以证明您控制该域名;随后,apache installer 会重写虚拟主机配置,添加 443 块,将其指向新证书,并默认将所有 HTTP 流量重定向到 HTTPS。自 Certbot 2.0 起,不再询问是否重定向;如果需要继续提供纯 HTTP,请传入 --no-redirect。由于您已在第 5 步设置了真实的 ServerName,Certbot 会自动检测域名。证书有效期为 90 天,该软件包会安装一个用于自动续期的 systemd timer;使用 sudo certbot renew --dry-run 验证该 timer,其输出末尾应为 Congratulations, all simulated renewals succeeded。
如需完整了解质询流程、续期 timer 以及 DNS 和防火墙要求,请参阅配套指南:在 Apache 上使用 Certbot 签发免费的 Let's Encrypt TLS 证书。
备份、升级与加固
备份保存状态的两类数据:数据库和 Web 根目录。每晚执行一次逻辑转储是最简单且可靠的方法,sudo sh -c 'mysqldump --all-databases --single-transaction | gzip > /root/db-$(date +%F).sql.gz',然后将文件复制到服务器之外。使用 sudo sh -c 包装整个流程很重要:否则 shell 会以当前用户身份执行 > /root/... 重定向,并因 Permission denied 而失败,因为只有 mysqldump 继承了 sudo。--single-transaction 可以在不锁定 InnoDB 表的情况下创建一致性快照。再配合 tar、/var/www 和 /etc/apache2/sites-available,即可使用这些文件在全新的 VPS 上重建整个堆栈。
升级属于常规的 sudo apt update && sudo apt upgrade。最容易出问题的是 PHP 版本升级:未来的 Ubuntu 将默认 PHP 版本提升到 PHP 8.4 时,apt 可能会将 php8.4-fpm 与 8.3 并行安装,套接字变为 /run/php/php8.4-fpm.sock,而 Apache 配置仍指向 8.3 的套接字。启用新的配置(sudo a2enconf php8.4-fpm)并禁用旧配置,否则网站会在一次原本常规的升级后开始返回 Primary script unknown。由于 PHP 的发布速度快于 LTS 发行版,请查看当前 PHP 发布说明,不要固定到某个补丁版本。
第一天建议完成两项加固措施。首先,在服务器上配置 让 Fail2Ban 监控 SSH。公网 VPS 往往在几分钟内就会遭遇自动化登录尝试,小型 jail 可在封禁之前将数千次尝试减少到少数几次。其次,如果您更愿意通过浏览器管理 Apache 虚拟主机、MariaDB 数据库和用户,而不是手动编辑文件,可以使用 基于 Web 的 Webmin 控制面板。它运行在这套完全相同的堆栈之上,并操作您刚刚编写的同一组配置文件。这两者都不能替代对各组件的理解,但都能减少日常管理的操作成本。
故障模式及其对应的提示信息
默认页面不会消失。您编辑了虚拟主机并重新加载配置,但浏览器仍显示 “Apache2 Ubuntu Default Page” 及其 “It works!” 横幅。Apache 会提供第一个匹配的虚拟主机;如果请求不匹配任何 ServerName,则按字母顺序排在最前面的配置生效,000-default.conf 排在 testapp.conf 之前。原因可能是请求中的主机名与您的 ServerName 不匹配,也可能是您从未运行 sudo a2dissite 000-default。禁用默认配置,执行 sudo systemctl reload apache2,然后使用 apache2ctl -S 确认。该命令会输出虚拟主机映射,并显示哪个配置拥有默认虚拟主机。还应清除浏览器缓存;旧页面缓存的 200 响应可能仍会继续显示。
.php 文件被下载而不是执行。您打开 info.php 后,浏览器下载了包含原始 <?php 源码的文件,或将其显示为纯文本,而不是执行它。Apache 将该文件作为静态资源提供,因为未附加 PHP 处理程序、您跳过了 sudo a2enmod proxy_fcgi 或 sudo a2enconf php8.3-fpm,或者之后没有重启 Apache。执行这三个操作(步骤 4),然后重新加载页面。使用 apache2ctl -M | grep fcgi 确认模块已加载;输出中应包含 proxy_fcgi_module。这是源代码泄露,不是显示问题。将真实内容放到服务器前必须修复。
ERROR 1698 (28000): Access denied for user 'root'@'localhost'。您在没有 sudo 的情况下运行了 mysql -u root 或 mariadb -u root。root 账户使用 unix_socket 身份验证,因此只有当您的操作系统用户实际是 root 时才会接受连接。修复方法是 sudo mysql,不需要 -u root,也不需要密码。该提示表示套接字身份验证按预期工作,并不表示安装损坏。
应用使用正确密码时出现 ERROR 1045 (28000): Access denied for user 'appuser'@'127.0.0.1'。该账户以 'appuser'@'localhost' 的形式存在,但应用通过 TCP 连接到服务器上的 127.0.0.1,而该服务器已禁用主机名解析(skip-name-resolve)。因此,MariaDB 会将两者视为不同的主机:localhost 是 Unix 套接字,127.0.0.1 是 TCP。将应用的主机设置为 localhost,使其使用套接字并匹配该账户;如果框架只能使用 TCP,则创建第二个账户 'appuser'@'127.0.0.1'。
在 /var/log/apache2/testapp-error.log 中出现 AH01071: Got error 'Primary script unknown',浏览器显示 File not found.。Apache 已将请求交给 PHP-FPM,但 FPM 找不到 Apache 提供的脚本路径。常见原因有两个:配置中的 FPM 套接字指向未安装的 PHP 版本(升级后配置仍指向 php8.4 套接字,但实际只有 8.3 在运行);或者 DocumentRoot 与实际目录不一致,导致文件确实不存在。使用 ls -l /run/php/ 检查套接字是否存在,确认 DocumentRoot 与文件实际位置一致,然后重启 php8.3-fpm 和 apache2。
每次重启都会出现 AH00558: apache2: Could not reliably determine the server's fully qualified domain name。这是无害警告,不是错误。Apache 只是提示未设置全局 ServerName。将 ServerName your.domain 写入 /etc/apache2/conf-available/servername.conf,然后运行 sudo a2enconf servername,即可消除该警告。
Apache 启动时出现 (98)Address already in use: AH00072: make_sock: could not bind to address 0.0.0.0:80。另一个 Web 服务器已经占用 80 端口,通常是上次实验遗留的 nginx。使用 sudo ss -ltnp | grep :80 查找占用进程,然后停止并禁用另一个服务,再启动 Apache。
FAQ
应使用 mod_php 还是 PHP-FPM?
使用 PHP-FPM。mod_php 会在每个 Apache 进程中嵌入解释器,并强制使用较慢的 prefork MPM,因此即使提供静态图片,Apache 也会承担 PHP 的开销。PHP-FPM 将 PHP 作为独立的、可单独调优的进程池运行,Apache 通过套接字访问它;它支持更快的线程化 event MPM,之后迁移到 nginx 时也无需改变。PHP-FPM 是现代默认方案;只有依赖某些进程内行为的旧版应用才适合使用 mod_php。
为什么浏览器会下载 PHP 文件,而不是执行它?
Apache 将 .php 文件作为静态文件下载,因为没有为它关联 PHP 处理程序。在 Ubuntu 24.04 的 FPM 配置中,这表示您遗漏了 sudo a2enmod proxy_fcgi、sudo a2enconf php8.3-fpm 或之后的 Apache 重启步骤。执行这三项操作并重新加载,然后使用 apache2ctl -M | grep fcgi 验证是否列出了 proxy_fcgi_module。修复之前,服务器会泄露源代码,因此必须立即处理。
为什么即使密码正确,MariaDB 仍拒绝 root 访问?
因为 Ubuntu 中的 MariaDB 默认不为 root 账户设置密码,而是通过 unix_socket 进行身份验证,将该账户绑定到操作系统的 root 用户。在普通 shell 中执行 mysql -u root 会按设计返回 ERROR 1698 (28000): Access denied for user 'root'@'localhost'。请改用 sudo mysql 连接,并为应用单独创建使用密码验证的用户,不要重复使用 root。
如何为 LAMP 网站添加 HTTPS?
安装 certbot 和 python3-certbot-apache,将域名的 A 记录指向服务器,然后运行 sudo certbot --apache。apache 身份验证器会通过正在运行的 Apache 证明您对域名的控制权,安装程序会将虚拟主机改写为使用 443 端口,并配置自动续期。完整的 Certbot 与 Apache 配置指南介绍了验证、续期计时器和常见故障模式。