尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

Ubuntu 20.04 LAMP 部署排错指南:Apache PHP MySQL 协同配置

Ubuntu 20.04 LAMP 部署排错指南:Apache PHP MySQL 协同配置
📅 发布时间:2026/6/22 0:36:03

1. 为什么 Ubuntu 20.04 上的 LAMP 不是“装完就跑”,而是要亲手调教每一层

你搜到这篇标题时,大概率正卡在某个环节:Apache 启动失败、PHP 页面显示源码不解析、MySQL 连接被拒绝,或者更糟——整个服务跑起来了,但一上传 PHP 文件就报 500 错误,日志里只有一行AH00027: No MPM loaded。这不是你的问题,而是 Ubuntu 20.04 的 LAMP 安装逻辑和十年前完全不同了。它不再是一个apt install lamp-server^就能一键封神的黑盒,而是一套需要你理解各组件职责边界、明确依赖链路、并主动干预配置细节的协作系统。

关键词里反复出现的Linux, Apache, MySQL, PHP, LAMP,表面看是四个独立软件,实则构成一个精密咬合的齿轮组:Linux 是底盘,Apache 是前台接待员,MySQL 是后台档案室,PHP 是前台与档案室之间的翻译兼办事员。任何一个齿轮打滑,整个业务流就中断。而 Ubuntu 20.04 的默认策略,恰恰把最关键的“咬合校准”工作留给了你——比如 Apache 默认不启用mpm_prefork模块(PHP-CGI 依赖它),MySQL 默认禁用远程连接且 root 密码策略极严,PHP 的opcache和gd扩展默认未启用,这些都不是 bug,而是安全优先的设计取舍。

我试过三次重装:第一次照着老教程走,卡在 PHP 不解析;第二次全用snap安装,结果 Apache 配置路径和传统/etc/apache2/完全错位;第三次才真正静下心来,逐层验证每个组件的“健康状态”——不是看进程是否 running,而是看它是否在正确端口监听、是否加载了必要模块、是否能与上下游组件完成一次最小闭环通信。这篇文章就是那次排错过程的完整复刻。它不教你“复制粘贴三行命令”,而是带你亲手拆开这个四件套,看清每颗螺丝的拧紧方向。适合正在部署个人博客、测试环境、或刚从 Windows WAMP 转过来、对 Linux 服务模型尚不熟悉的开发者。你不需要是系统管理员,但得愿意为每一行systemctl status的输出多停留三秒。

2. Apache:从“启动成功”到“真正接管请求”的三道关卡

很多人以为sudo systemctl start apache2返回active (running)就万事大吉。但实际中,90% 的 PHP 不解析问题,根源都在 Apache 这一层没真正“上岗”。Ubuntu 20.04 的 Apache 2.4.41 默认采用eventMPM(多路复用模型),它轻量高效,但不兼容传统的mod_php方式运行 PHP——而绝大多数 PHP 教程默认假设你用的就是mod_php。这是第一个必须跨过的认知鸿沟。

2.1 MPM 模块的强制切换:为什么prefork是 PHP 的刚需

eventMPM 为高并发 HTTP 请求优化,但它让每个工作进程能同时处理多个连接,而 PHP 的传统运行模式(尤其是mod_php)要求每个请求独占一个进程,否则变量、会话、内存状态会互相污染。因此,我们必须显式禁用event,启用prefork:

# 先确认当前启用的 MPM sudo a2query -M # 禁用 event,启用 prefork(注意:必须先禁用再启用,顺序不能反) sudo a2dismod mpm_event sudo a2enmod mpm_prefork # 重启 Apache 生效 sudo systemctl restart apache2

提示:执行a2dismod mpm_event时,如果提示Module mpm_event is not enabled,说明系统已默认使用prefork,可跳过此步。但务必用a2query -M验证,别凭感觉。

启用prefork后,还需检查其核心参数是否合理。打开/etc/apache2/mods-available/mpm_prefork.conf,重点关注三个值:

参数默认值推荐值(开发机)说明
StartServers53启动时创建的子进程数,开发环境无需过多
MinSpareServers52最小空闲进程数,低于此数会自动创建新进程
MaxSpareServers105最大空闲进程数,高于此数会自动杀死多余进程
MaxRequestWorkers15050最关键!同时处理的最大请求数,直接决定并发能力。设太高会耗尽内存,太低则请求排队

修改后保存,执行sudo systemctl reload apache2(reload 比 restart 更轻量,只重载配置)。

2.2 PHP 模块的加载:不是安装了就能用,而是要“注册”进 Apache

Ubuntu 20.04 的 PHP 包(如php7.4)安装后,并不会自动将libphp7.4.so加载到 Apache。你必须手动启用php7.4模块:

# 启用 PHP 模块(以 PHP 7.4 为例,若装的是 8.1 则替换为 php8.1) sudo a2enmod php7.4 # 重启 Apache sudo systemctl restart apache2

验证是否生效:创建一个测试文件/var/www/html/test.php,内容为<?php phpinfo(); ?>,然后在浏览器访问http://localhost/test.php。如果看到 PHP 信息页,说明模块加载成功;如果显示纯文本<?php phpinfo(); ?>,说明模块未加载或 MPM 不匹配。

注意:a2enmod php7.4命令本质是在/etc/apache2/mods-enabled/下创建指向/etc/apache2/mods-available/php7.4.load的软链接。你可以用ls -l /etc/apache2/mods-enabled/ | grep php查看是否已存在。如果手动创建过链接但未生效,请检查/etc/apache2/mods-available/php7.4.load文件内容是否为LoadModule php7_module /usr/lib/apache2/modules/libphp7.4.so——路径错误是常见原因。

2.3 MIME 类型与处理器绑定:让.php后缀真正“活”起来

即使模块加载了,Apache 仍需知道“遇到.php文件该交给谁处理”。这由AddType和SetHandler指令控制。Ubuntu 20.04 的php7.4.load模块默认已包含以下配置(位于/etc/apache2/mods-available/php7.4.conf):

<FilesMatch ".+\.ph(p[3456789]?|t|tml)$"> SetHandler application/x-httpd-php </FilesMatch>

这段正则表达式匹配.php,.php3,.phtml等后缀,并将其处理器设为application/x-httpd-php。但如果你曾手动修改过 Apache 配置,或使用了自定义虚拟主机,可能覆盖了此规则。最稳妥的验证方式是检查默认站点配置:

# 查看默认站点的配置文件 sudo nano /etc/apache2/sites-enabled/000-default.conf

确保其中没有RemoveHandler .php或AddType text/plain .php这类冲突指令。如果存在,注释掉它们。然后执行sudo systemctl reload apache2。

实测心得:有一次我部署 Laravel 项目,首页正常,但所有路由都 404。排查发现是.htaccess中的RewriteRule规则被AllowOverride None禁用了。在<Directory /var/www/html>块内添加AllowOverride All并reload后解决。这提醒我们:Apache 的“请求处理链”是层层过滤的,前端的.htaccess、中间的Directory指令、后端的MPM和PHP模块,缺一不可。

3. MySQL:从“能连上”到“能安全存数据”的权限与配置重构

Ubuntu 20.04 的 MySQL 8.0.28 默认启用了caching_sha2_password认证插件,这与 PHP 的mysqlnd驱动(尤其旧版本)存在兼容性问题,表现为mysqli_connect()报错Client does not support authentication protocol requested by server。这不是密码错了,而是“握手协议”不匹配。此外,MySQL 默认绑定127.0.0.1,禁止外部访问,这对本地开发影响不大,但若你用 Docker 或 WSL,就需要调整。

3.1 认证插件降级:为 PHP 驱动铺平道路

登录 MySQL(首次安装后,root 密码为空或需用sudo mysql无密码登录):

sudo mysql

执行以下 SQL,将 root 用户的认证方式改为向后兼容的mysql_native_password:

-- 查看当前 root 用户的认证插件 SELECT user, host, plugin FROM mysql.user WHERE user='root'; -- 修改认证插件(将 'your_strong_password' 替换为你想设的密码) ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_strong_password'; -- 刷新权限 FLUSH PRIVILEGES;

退出 MySQL,用新密码测试连接:

mysql -u root -p

如果能成功进入,说明认证问题已解决。PHP 连接时,即可使用标准的mysqli_connect('localhost', 'root', 'your_strong_password')。

提示:caching_sha2_password是更安全的插件,生产环境应保留。但开发阶段,兼容性优先。若坚持用它,需升级 PHP 到 7.4+ 并确保mysqlnd驱动版本足够新,但这会引入更多不确定性。

3.2 绑定地址与防火墙:让服务“看得见”也“连得上”

MySQL 默认配置文件/etc/mysql/mysql.conf.d/mysqld.cnf中,bind-address设为127.0.0.1,意味着只接受本机连接。如果你想从宿主机(如 Windows)通过 WSL 访问 Ubuntu 的 MySQL,或从 Docker 容器访问,必须改为0.0.0.0:

sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf

找到bind-address = 127.0.0.1,修改为:

bind-address = 0.0.0.0

保存后重启 MySQL:

sudo systemctl restart mysql

但此时,Ubuntu 的ufw防火墙会拦截 3306 端口。必须放行:

sudo ufw allow 3306 # 或者,如果只想允许特定 IP(如 WSL 的宿主机 IP),用: # sudo ufw allow from 192.168.1.100 to any port 3306

验证是否生效:

# 查看 MySQL 是否监听 0.0.0.0:3306 sudo ss -tlnp | grep :3306 # 输出应类似:LISTEN 0 70 *:3306 *:* users:(("mysqld",pid=1234,fd=25)) # 注意 *:3306 表示监听所有地址,而非 127.0.0.1:3306

3.3 创建专用数据库与用户:告别 root 全权操作

用 root 连接 MySQL 后,立即创建一个专用于 Web 应用的数据库和用户,这是安全基线:

-- 创建数据库(指定 UTF-8mb4 字符集,支持 emoji) CREATE DATABASE myapp CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; -- 创建用户('myuser'@'localhost' 表示只能本机连接) CREATE USER 'myuser'@'localhost' IDENTIFIED BY 'strong_password_here'; -- 授予该用户对 myapp 数据库的所有权限 GRANT ALL PRIVILEGES ON myapp.* TO 'myuser'@'localhost'; -- 刷新权限 FLUSH PRIVILEGES;

现在,你的 PHP 代码应使用'myuser'而非'root'连接:

$host = 'localhost'; $dbname = 'myapp'; $user = 'myuser'; $pass = 'strong_password_here'; $conn = new mysqli($host, $user, $pass, $dbname);

实操心得:我曾因忘记执行FLUSH PRIVILEGES;,导致新用户始终无法登录,浪费两小时排查网络和密码。记住:CREATE USER和GRANT只是写入内存,FLUSH才是“提交事务”。

4. PHP:不只是安装,而是要激活关键扩展与优化运行时

Ubuntu 20.04 的php7.4包安装后,核心功能可用,但大量 Web 开发必需的扩展(如mysqli,pdo_mysql,gd,opcache)默认是禁用的。PHP 的配置文件/etc/php/7.4/apache2/php.ini也需根据开发需求微调。更重要的是,PHP-FPM(FastCGI Process Manager)作为现代 PHP 运行方式,在 Ubuntu 20.04 上已成主流,但mod_php依然有效——你需要知道何时该用哪个。

4.1 必备扩展的启用与验证:让 PHP 真正“有手有脚”

检查已启用的扩展:

php -m | grep -E "(mysqli|pdo|gd|opcache)"

如果输出为空或缺少某项,启用它:

# 启用 mysqli(MySQLi 扩展) sudo phpenmod mysqli # 启用 PDO 和 PDO MySQL 驱动 sudo phpenmod pdo sudo phpenmod pdo_mysql # 启用 GD(图像处理,如验证码、缩略图) sudo phpenmod gd # 启用 Opcache(字节码缓存,大幅提升性能) sudo phpenmod opcache

phpenmod命令会在/etc/php/7.4/apache2/conf.d/下创建符号链接(如20-mysqli.ini),指向/etc/php/7.4/mods-available/mysqli.ini。重启 Apache 使扩展生效:

sudo systemctl restart apache2

验证:在test.php中加入<?php print_r(get_loaded_extensions()); ?>,刷新页面,确认列表中包含mysqli,pdo_mysql,gd,opcache。

4.2 php.ini 关键参数调优:开发友好 vs 生产安全

打开/etc/php/7.4/apache2/php.ini,修改以下几处(搜索关键词快速定位):

参数默认值推荐值(开发)说明
display_errorsOffOn开发时显示错误,方便调试;生产环境必须 Off
error_reportingE_ALL & ~E_DEPRECATED & ~E_STRICTE_ALL显示所有错误,包括严格标准
log_errorsOnOn错误日志必须开启,路径见error_log参数
error_log/var/log/php/error.log/var/log/php/error.log确保该目录存在且 Apache 有写入权限:
sudo mkdir -p /var/log/php && sudo chown www-data:www-data /var/log/php
upload_max_filesize2M64M上传大文件(如图片、备份)时需调大
post_max_size8M128M必须 >=upload_max_filesize,否则上传表单会失败
memory_limit128M512M处理大数组、图片时易超限,开发机可设高些

修改后,必须重启 Apache(php.ini是 Apache 模块的一部分,reload无效):

sudo systemctl restart apache2

4.3 mod_php vs PHP-FPM:两种模式的抉择与切换

Ubuntu 20.04 同时支持mod_php(Apache 内置)和PHP-FPM(独立 FastCGI 服务)。前者简单,后者更灵活、资源隔离更好。

  • 选择mod_php:如果你追求最简部署,且只运行一个 PHP 应用,mod_php是首选。它已通过a2enmod php7.4启用。
  • 选择PHP-FPM:如果你计划运行多个 PHP 版本(如 7.4 和 8.1)、或需要精细控制 PHP 进程(如内存限制、超时)、或未来要迁移到 Nginx,PHP-FPM是更好的起点。

启用 PHP-FPM:

# 安装 PHP-FPM sudo apt install php7.4-fpm # 启用 proxy_fcgi 和 rewrite 模块(Apache 代理到 FPM 所需) sudo a2enmod proxy_fcgi rewrite # 禁用 mod_php,启用 FPM 处理 sudo a2dismod php7.4 sudo systemctl restart apache2

然后,编辑默认站点配置/etc/apache2/sites-enabled/000-default.conf,在<VirtualHost *:80>内添加:

<FilesMatch \.php$> SetHandler "proxy:unix:/run/php/php7.4-fpm.sock|fcgi://localhost" </FilesMatch>

最后重启 Apache。此时,PHP 请求将通过 Unix Socket 交由php7.4-fpm进程池处理。

踩坑实录:我第一次切 PHP-FPM 时,test.php页面空白,error.log里只有Premature end of script headers。排查发现是/run/php/php7.4-fpm.sock权限问题——FPM 进程以www-data用户运行,但 socket 文件属主是root。解决方案:编辑/etc/php/7.4/fpm/pool.d/www.conf,将listen.owner和listen.group改为www-data,然后sudo systemctl restart php7.4-fpm。

5. 四层联调:用一个真实请求验证整个 LAMP 链路是否畅通

当 Apache、MySQL、PHP 各自“健康”后,真正的考验是它们能否协同完成一个端到端任务。我们用一个极简的“查询数据库并显示”脚本,作为最终验收。

5.1 创建测试数据库与表

用之前创建的myuser登录 MySQL:

mysql -u myuser -p

执行:

USE myapp; CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, email VARCHAR(100) UNIQUE NOT NULL ); INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com'), ('Bob', 'bob@example.com');

5.2 编写联调脚本db_test.php

在/var/www/html/下创建db_test.php:

<?php // 数据库配置 $host = 'localhost'; $dbname = 'myapp'; $user = 'myuser'; $pass = 'strong_password_here'; // 创建连接 $conn = new mysqli($host, $user, $pass, $dbname); // 检查连接 if ($conn->connect_error) { die("连接失败: " . $conn->connect_error); } // 查询数据 $sql = "SELECT id, name, email FROM users"; $result = $conn->query($sql); if ($result->num_rows > 0) { echo "<h2>用户列表</h2>"; echo "<table border='1'><tr><th>ID</th><th>姓名</th><th>邮箱</th></tr>"; while($row = $result->fetch_assoc()) { echo "<tr><td>" . $row["id"] . "</td><td>" . $row["name"] . "</td><td>" . $row["email"] . "</td></tr>"; } echo "</table>"; } else { echo "0 结果"; } $conn->close(); ?>

5.3 执行联调并解读每一步反馈

在浏览器访问http://localhost/db_test.php。理想结果是显示一个带边框的表格,列出 Alice 和 Bob。

如果失败,按以下顺序排查:

  1. 页面空白或 500 错误:检查 Apache 错误日志sudo tail -f /var/log/apache2/error.log。常见原因:PHP 语法错误、扩展未启用、php.ini路径错误。
  2. 显示Fatal error: Class 'mysqli' not found:mysqli扩展未启用,执行sudo phpenmod mysqli并重启 Apache。
  3. 显示Connection refused或Access denied:MySQL 连接参数错误,或用户权限不足。用mysql -u myuser -p命令行测试能否登录。
  4. 显示0 结果,但数据库里有数据:检查 SQL 语句是否拼写错误,或表名/字段名大小写(Linux 文件系统区分大小写,MySQL 表名默认也区分)。
  5. 表格显示但中文乱码:MySQL 字符集未设为utf8mb4。执行ALTER DATABASE myapp CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;并重启 Apache。

个人经验:我习惯在db_test.php开头加一行error_reporting(E_ALL); ini_set('display_errors', 1);,确保所有警告都可见。上线前再注释掉。另外,mysqli的query()方法返回false时,一定要用$conn->error获取具体错误,而不是笼统地die("查询失败")。

6. 安全加固与日常维护:让 LAMP 环境不止于“能用”

LAMP 栈在 Ubuntu 20.04 上跑通只是第一步。一个可持续维护的开发环境,必须考虑安全更新、日志监控、备份策略和性能基线。这些不是“锦上添花”,而是避免未来凌晨三点被报警电话叫醒的关键。

6.1 自动化安全更新:堵住已知漏洞的最快方式

Ubuntu 的unattended-upgrades服务可自动安装安全补丁。启用它:

# 安装并启用 sudo apt install unattended-upgrades sudo dpkg-reconfigure -plow unattended-upgrades # 编辑配置,确保只升级安全更新 sudo nano /etc/apt/apt.conf.d/50unattended-upgrades

确认文件中有:

Unattended-Upgrade::Allowed-Origins { "${distro_id}:${distro_codename}-security"; // "${distro_id}:${distro_codename}-updates"; // 注释掉此行,避免非安全更新 };

然后启用服务:

sudo systemctl enable --now unattended-upgrades

提示:unattended-upgrades默认每天凌晨 6 点运行。你可以用sudo systemctl status unattended-upgrades查看最近一次执行日志。

6.2 日志集中管理:当问题发生时,你知道去哪里找线索

Apache、MySQL、PHP 的日志分散在不同位置,手动翻查效率极低。建立一个简单的日志查看脚本/usr/local/bin/lamp-log:

#!/bin/bash echo "=== Apache Error Log (last 20 lines) ===" sudo tail -20 /var/log/apache2/error.log echo -e "\n=== MySQL Error Log (last 20 lines) ===" sudo tail -20 /var/log/mysql/error.log echo -e "\n=== PHP Error Log (last 20 lines) ===" sudo tail -20 /var/log/php/error.log

赋予执行权限:

sudo chmod +x /usr/local/bin/lamp-log

以后只需输入lamp-log,三份关键日志的最新片段就一目了然。

6.3 数据库定期备份:一句命令,救回丢失的三天工作

创建备份脚本/usr/local/bin/backup-mysql.sh:

#!/bin/bash # 备份目录 BACKUP_DIR="/home/ubuntu/backups" DATE=$(date +%Y%m%d_%H%M%S) DB_NAME="myapp" DB_USER="myuser" DB_PASS="strong_password_here" # 创建备份目录 mkdir -p $BACKUP_DIR # 执行 mysqldump mysqldump -u $DB_USER -p$DB_PASS $DB_NAME > "$BACKUP_DIR/${DB_NAME}_backup_${DATE}.sql" # 压缩并删除 7 天前的备份 gzip "$BACKUP_DIR/${DB_NAME}_backup_${DATE}.sql" find $BACKUP_DIR -name "${DB_NAME}_backup_*.sql.gz" -mtime +7 -delete echo "Backup completed: ${DB_NAME}_backup_${DATE}.sql.gz"

设置定时任务(每天凌晨 2 点):

# 编辑 root 的 crontab sudo crontab -e # 添加一行: 0 2 * * * /usr/local/bin/backup-mysql.sh

注意:脚本中明文密码有安全风险。生产环境应使用 MySQL 配置文件~/.my.cnf存储凭证,并设置chmod 600 ~/.my.cnf。但开发机为求简便,此方案可接受。

6.4 性能基线测试:量化你的 LAMP 有多“快”

用ab(Apache Bench)工具测试 Apache 的基础吞吐:

# 安装 ab sudo apt install apache2-utils # 测试静态 HTML(基准线) ab -n 1000 -c 100 http://localhost/index.html # 测试 PHP Info(含 PHP 解析开销) ab -n 1000 -c 100 http://localhost/test.php

记录Requests per second数值。例如,我的 8GB 内存开发机,静态页约 4500 req/s,PHP Info 页约 1200 req/s。这个数字是你后续优化的起点。如果 PHP 页低于 500 req/s,就要检查opcache是否启用、mysql连接是否复用、php.ini的realpath_cache_size是否过小。

最后分享一个小技巧:Ubuntu 20.04 的systemd服务管理非常强大。当你不确定某个服务(如apache2,mysql,php7.4-fpm)为何启动失败时,不要只看systemctl status的摘要,而是用sudo journalctl -u servicename -n 50 -f实时跟踪日志流。-n 50显示最近 50 行,-f表示跟随(类似tail -f)。这是定位服务级故障最直接的手段,比翻查/var/log/下的各个日志文件高效得多。

相关新闻

  • biliTickerBuy:告别抢票焦虑的B站会员购终极助手
  • 第4章 线下会议管理
  • 高级Schema标记部署

最新新闻

  • 2026年贵阳工伤维权律师怎么挑?3个判断标准不踩雷 - 本地品牌推荐
  • D3.js Selection 原理与本质:数据驱动DOM的声明式范式
  • WPF 从选品到扫码付 支付链路与 异步实践
  • 大语言模型内在可解释性:从黑箱到透明推理的架构设计原则与实践路径
  • 基于MLLM+DSL的可视化图表逆向解析:从图像到可执行代码
  • NVBench:语音合成评测新基准,如何量化评估非语言发声与情感表现力

日新闻

  • 2026速览惠州叛逆青少年学校前十大排名名单出炉 - 武汉中职最新信息发布
  • 2026上饶白蚁消杀哪家好?15年本土2大权威白蚁防治公司推荐(金盾虫控/青蚁卫士) - 我叫一
  • 天龙八部单机版终极数据管理工具:5个技巧快速掌握游戏数据编辑

周新闻

  • Visual C++运行库修复终极指南:5分钟快速解决Windows软件启动错误
  • 手把手教你构建统计局地区经济数据爬虫:从环境搭建到数据持久化全指南
  • 2026多Agent深度解析:用AI团队替代单一模型,四种架构实战落地

月新闻

  • 【总结】入门篇:50句话让你记住架构核心概念
  • WeChatMsg技术方案解析:实现Mac微信数据自主管理的完整解决方案
  • WeChatMsg:革新性微信数据备份方案,打造你的专属数字记忆库

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号