能不依赖宝塔面板、PHPStudy这类集成环境,自己在Ubuntu上把LAMP一套从零装起来,是很多PHP开发者进阶路上的一道分水岭。装完你会突然明白Apache和Nginx到底差在哪、PHP-FPM是干什么的、MySQL的认证插件为什么偶尔会坑你一下。这篇文章主要面向已经会写PHP、但一直用集成环境、没碰过Linux服务器配置的开发者,也适合准备把项目部署到云服务器、但不想用面板的人。我会带你把Linux + Apache + MySQL/MariaDB + PHP这条链路完整走一遍,不只是敲命令,还会解释每一步为什么这么做,以及哪些地方最容易翻车。
1. 动手前的规划:Apache还是Nginx、PHP用哪个版本、MySQL还是MariaDB
很多人一上来就apt install apache2 php mysql-server一把梭,结果装完发现版本不对、扩展缺一堆、跑起来500错误,回头还得折腾半天。我先说清楚选型问题。
Apache在LAMP里依然是稳妥选择。 虽然Nginx在高并发静态文件场景下性能更强,但Apache的.htaccess支持、mod_php(虽然我后面会推荐PHP-FPM方式)、模块化配置对PHP开发者更友好,尤其是你需要在目录级别做权限控制、伪静态规则时,Apache的配置直觉性更强。教程场景、个人服务器、中小流量项目,Apache完全够用。
PHP版本选8.1以上。 Ubuntu 22.04默认软件源里的PHP是8.1,24.04是8.3。如果你还在跑老项目、依赖ThinkPHP 3.2.3这类远古框架,那确实得谨慎,那个生态停在PHP 5.6/7.0时代,真要跑老项目建议用Docker容器隔离,别直接裸装在系统里污染环境。新项目、新框架(ThinkPHP 6/8、Laravel 10+)一律用PHP 8.x,性能和安全都好很多。
MySQL还是MariaDB? 这俩在兼容性上几乎一样,Ubuntu软件源里默认的mysql-server包实际上就是MySQL 8.0二进制版,而MariaDB是社区分支。我的建议是:如果你不需要MySQL 8.0特有的功能(比如窗口函数、CTE这些其实MariaDB 10.6+也大部分有了),用哪个都行。但如果你的项目里用了mysql_native_password认证插件相关的老配置,MariaDB反而更省心一点。本文以MySQL 8.0为例,MariaDB的安装命令我会在结尾补充。
另外,提前确认你的Ubuntu版本和CPU架构。lsb_release -a查看版本,uname -m查看架构。Ubuntu 22.04和24.04的软件源、包版本差异很大。国内服务器还需要换源,不然apt update可能慢到让你怀疑人生。我用的是Ubuntu 22.04 LTS,下面所有命令都以这个版本为基础。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 从零到通:四件套安装与首次联通测试
2.1 先把系统源换成国内镜像
这一步不是必须的,但如果你在国内服务器上,做完能省大量等待时间。Ubuntu 22.04的源配置文件是/etc/apt/sources.list,但我更推荐直接改/etc/apt/sources.list.d/ubuntu.sources(新版格式)。先把原来的文件备份:
bash复制sudo cp /etc/apt/sources.list.d/ubuntu.sources /etc/apt/sources.list.d/ubuntu.sources.bak
然后编辑它,把archive.ubuntu.com和security.ubuntu.com替换为mirrors.aliyun.com或mirrors.tuna.tsinghua.edu.cn。替换后执行:
bash复制sudo apt update && sudo apt upgrade -y
2.2 安装Apache 2.4并验证启动
bash复制sudo apt install -y apache2
sudo systemctl enable apache2
sudo systemctl start apache2
装完访问http://你的服务器IP,看到Apache2 Ubuntu Default Page就说明Web服务已经起来了。此时注意防火墙——如果你开了UFW,需要放行:
bash复制sudo ufw allow in "Apache Full"
这里的Apache Full包含了80和443端口,比单独放行Apache更省事。
2.3 安装PHP 8.1和常用扩展
不要只装php包,那样PHP连MySQL驱动都没有。我建议一次性把常用扩展装齐,省得后面缺啥补啥:
bash复制sudo apt install -y php8.1 php8.1-fpm php8.1-mysql php8.1-gd php8.1-xml php8.1-mbstring php8.1-curl php8.1-zip php8.1-intl php8.1-bcmath php8.1-redis
这里我要解释一下为什么装php8.1-fpm。Apache处理PHP有两种方式:mod_php(把PHP作为Apache模块加载)和PHP-FPM(PHP独立进程,通过FastCGI协议与Apache通信)。我强烈推荐PHP-FPM方式,因为:
- PHP-FPM独立于Apache运行,PHP崩溃不会拖垮Web服务器
- 可以针对不同虚拟主机配置不同的PHP版本
- 高并发下资源管理更优秀
后面我会在配置里把Apache和PHP-FPM连接起来。
2.4 安装MySQL 8.0并做安全初始化
bash复制sudo apt install -y mysql-server
sudo systemctl enable mysql
sudo systemctl start mysql
MySQL 8.0在Ubuntu上安装后默认root用户使用auth_socket插件认证,也就是说你只能在系统root用户下免密登录MySQL。这个设计很安全,但对开发环境来说不方便。执行安全初始化脚本:
bash复制sudo mysql_secure_installation
它会让你设置密码强度、删除匿名用户、禁止root远程登录等。开发环境我建议你设置一个强度适中的密码、删除匿名用户、禁止root远程登录(这个必须禁);测试环境如果你无所谓,可以跳过这些。
2.5 配置Apache与PHP-FPM连接并测试联通
安装完成后,Apache其实还不知道怎么处理.php文件。需要启用代理模块并修改站点配置:
bash复制sudo a2enmod proxy_fcgi setenvif
sudo a2enconf php8.1-fpm
sudo systemctl reload apache2
这个php8.1-fpm.conf配置已经帮你建好了Apache到PHP-FPM的socket连接,所以执行完这三条命令Apache就能处理PHP请求了。
在/var/www/html下新建一个info.php:
php复制<?php
phpinfo();
浏览器访问http://你的服务器IP/info.php,如果能看到一个巨大的PHP信息页面,说明PHP解析已经通了。看到这里你已经完成了最核心的60%工作——Apache能跑,PHP能解析,接下来才是真正体现水平的部分。
3. 从“能跑”到“像样”:虚拟主机、目录权限与伪静态配置
很多教程到phpinfo()显示出来就结束了,但真实项目显然不能这么草率。这一节我们解决三个问题:多站点怎么隔离、文件权限怎么给、URL怎么美化。
3.1 用虚拟主机隔离多个项目
一个服务器上放多个项目是最常见的需求。Apache通过虚拟主机(VirtualHost)实现。我习惯的做法是每个站点一个配置文件,放在/etc/apache2/sites-available/下,然后通过sites-enabled软链接启用。
假设我有两个项目:blog.example.com和api.example.com,为它们分别创建配置:
apache复制# /etc/apache2/sites-available/blog.example.com.conf
<VirtualHost *:80>
ServerName blog.example.com
ServerAdmin admin@example.com
DocumentRoot /var/www/blog/public
<Directory /var/www/blog/public>
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/blog.error.log
CustomLog ${APACHE_LOG_DIR}/blog.access.log combined
</VirtualHost>
创建后启用:
bash复制sudo a2ensite blog.example.com
sudo a2dissite 000-default
sudo systemctl reload apache2
这里有几个细节值得注意:AllowOverride All意味着允许.htaccess覆盖目录配置,对ThinkPHP这类依赖伪静态的框架至关重要;DocumentRoot我直接指到了public目录而不是项目根目录,这样更安全,防止用户直接访问到框架的.env文件。
3.2 文件权限:别再chmod 777了
这是我在无数服务器上看到过的坏习惯。正确的权限模型是:文件的属主是你自己的用户(比如ubuntu或www-data),如果需要Web服务写入,把写权限给到www-data组,而不是所有人都能写。
bash复制sudo chown -R ubuntu:www-data /var/www/blog
sudo find /var/www/blog -type f -exec chmod 644 {} \;
sudo find /var/www/blog -type d -exec chmod 755 {} \;
sudo chmod -R 775 /var/www/blog/storage /var/www/blog/bootstrap/cache
最后一行针对的是Laravel这类框架需要写入的目录(storage、cache),只把写权限开放给组用户,比直接777安全得多。另外如果项目里有.env文件,记得确保它不能被Web访问到——放在DocumentRoot之外或者通过Apache配置禁止访问。
3.3 伪静态配置:ThinkPHP和WordPress的URL重写
这一步是新手最容易卡住的地方。以ThinkPHP 6为例,它在public/.htaccess里自带了规则:
apache复制<IfModule mod_rewrite.c>
Options +FollowSymlinks -Multiviews
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L]
</IfModule>
但想让这个文件生效,你还需要在Apache里启用mod_rewrite,并确保虚拟主机配置里AllowOverride All已经打开:
bash复制sudo a2enmod rewrite
sudo systemctl restart apache2
WordPress的伪静态规则类似,如果你装了WordPress,在后台设置固定链接为“文章名”后,如果出现404,基本都是因为mod_rewrite没启用或者.htaccess没生效。可以用apache2ctl -M | grep rewrite确认模块是否加载。
4. PHP与MySQL首次握手时最容易翻车的三件事
环境搭好、站点能访问、PHP能解析,很多人觉得万事大吉,结果一连数据库就报错。我在这里把最常见的三个翻车点一次性讲清楚。
4.1 MySQL 8.0的认证插件变了
这是最经典的一个坑。MySQL 8.0默认用caching_sha2_password认证插件,而老版本的PHP MySQL驱动(比如PHP 7.0以下、或者过时的mysqlnd版本)只认识mysql_native_password。如果你用老框架连MySQL 8.0,会直接报:
code复制The server requested authentication method unknown to the client
解决办法有两个:一是升级PHP到8.x(推荐,mysqlnd已经支持新插件);二是把用户的认证插件改回老的:
sql复制ALTER USER 'your_user'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_password';
这只是在兼容老代码时的救急方案,能升级PHP就别这么干。
4.2 连接超时或拒绝访问:socket和host的锅
PHP连MySQL时,localhost和127.0.0.1是有区别的。使用localhost时PHP会尝试通过Unix socket连接(默认路径是/var/run/mysqld/mysqld.sock),使用127.0.0.1时走TCP/IP走3306端口。
如果你改了MySQL的socket路径,或者socket文件权限不对,就会看到Connection refused或No such file or directory。排查命令:
bash复制sudo mysql -e "SHOW VARIABLES LIKE 'socket';"
sudo mysql -e "SHOW VARIABLES LIKE 'port';"
然后检查PHP的pdo_mysql.default_socket是否指向同一个路径:
bash复制php -i | grep pdo_mysql.default_socket
对不上就改/etc/php/8.1/cli/conf.d/20-pdo_mysql.ini或者/etc/php/8.1/fpm/conf.d/20-pdo_mysql.ini里的配置(两个都要改)。
4.3 3306端口没开:远程访问数据库老连不上
本地连接没问题,但用本地电脑上的Navicat或DataGrip连远程MySQL就连不上,绝大多数情况是MySQL只监听了127.0.0.1。默认MySQL为了安全只监听本机地址,远程连接需要改配置:
bash复制sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf
找到bind-address = 127.0.0.1,改成0.0.0.0,然后重启MySQL。还需要给用户授权远程访问:
sql复制CREATE USER 'remote_user'@'%' IDENTIFIED BY 'StrongPassword123!';
GRANT ALL PRIVILEGES ON your_database.* TO 'remote_user'@'%';
FLUSH PRIVILEGES;
但坦白说,生产环境我极其不推荐开放MySQL远程访问。正确做法是只允许应用服务器通过内网IP访问,或者用SSH隧道。%通配符意味着任何IP都能试你的密码,这不是开玩笑的事。
5. 跑起来之后:性能与安全层面的二次调优
环境通了、项目跑起来了,这才像是完成了一半。接下来这两个层面的调优,才是区分“能用”和“好用”的关键。
5.1 PHP-FPM进程管理调参
默认的PHP-FPM配置是保守的,/etc/php/8.1/fpm/pool.d/www.conf里的pm = dynamic,默认pm.max_children = 5,也就是说最多同时处理5个PHP请求。如果你的服务器是2核4G,这个数字能支撑的开发调试没问题,但一上线就等着502吧。
我给的参考配置(2核4G):
ini复制pm = dynamic
pm.max_children = 20
pm.start_servers = 5
pm.min_spare_servers = 3
pm.max_spare_servers = 8
pm.max_requests = 500
pm.max_requests = 500的意思是每个PHP-FPM子进程处理500个请求后自动重启,防止内存泄漏累积。你可以用free -h和ps -eo pid,ppid,cmd,%mem,%cpu --sort=-%mem | head来监控实际内存占用,再微调max_children。经验公式是max_children大约等于(可用内存/单个PHP进程平均内存),比如单个PHP-FPM进程占50MB,2GB内存留500MB给系统,那max_children设在30左右。
5.2 开启OPcache让PHP快一个档次
PHP是解释型语言,每次请求都要重新解析、编译代码。OPcache把编译后的字节码缓存到内存里,性能提升非常明显。Ubuntu的php8.1-fpm已经自带了OPcache扩展,但默认只开了opcache.enable=1的CLI模式,FPM模式需要手动确认。
在/etc/php/8.1/fpm/conf.d/10-opcache.ini里,确保以下配置存在:
ini复制opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.revalidate_freq=2
opcache.validate_timestamps=1
解释一下:memory_consumption是缓存内存大小,128MB对大多数项目够用;revalidate_freq=2表示每2秒检查一次文件时间戳,开发环境建议设成0(关闭缓存校验),生产环境设成60或更高减少IO开销。改完记得:
bash复制sudo systemctl restart php8.1-fpm
用phpinfo()页面搜索opcache.enable确认状态。
5.3 安全加固:禁用危险函数、隐藏版本号
基础加固里最简单的几件事:
禁用危险PHP函数,在/etc/php/8.1/fpm/php.ini里找到disable_functions,设置为:
ini复制disable_functions = exec,shell_exec,system,passthru,proc_open,popen,show_source
隐藏版本号:
ini复制expose_php = Off
Apache隐藏版本号,编辑/etc/apache2/conf-enabled/security.conf:
apache复制ServerTokens Prod
ServerSignature Off
这些都是最基础的操作,但能挡掉多少单纯靠扫描版本号漏洞的脚本机器人。
5.4 备份策略:不能等数据没了才后悔
这是很多人容易忽略的。我推荐用mysqldump做每日自动备份,配合cron定时任务:
bash复制mysqldump -u your_user -pYourPassword --single-transaction --quick --lock-tables=false your_database > /backup/your_database_$(date +\%Y\%m\%d\%H\%M\%S).sql
find /backup -name "*.sql" -mtime +7 -delete
第一行导数据,第二行只保留7天内的备份文件。把这两行写成一个backup.sh脚本,加入crontab:
bash复制crontab -e
# 每天凌晨2点执行备份
0 2 * * * /path/to/backup.sh >> /var/log/backup.log 2>&1
实际跑一次备份并测试恢复(mysql -u user -p database < backup.sql),确保备份不是摆设。我见过太多人配置了备份但从没恢复验证过,真到灾难发生时才发现备份文件是坏的。
6. 常见问题快查表与最终验证
6.1 一张表帮你快速定位故障
| 现象 | 大概率原因 | 排查命令 / 操作 |
|---|---|---|
| 访问IP显示Apache默认页,PHP文件不执行 | PHP-FPM连接未配置 | a2enmod proxy_fcgi setenvif + a2enconf php8.1-fpm |
| 访问PHP文件直接下载 | Apache未识别PHP类型 | 检查php8.1-fpm是否安装、配置是否加载 |
连接MySQL报Connection refused |
socket路径不一致或MySQL未启动 | systemctl status mysql + `php -i |
页面显示500 Internal Server Error |
PHP语法错误或目录权限 | tail -f /var/log/apache2/error.log |
| URL重写404 | mod_rewrite未启用或AllowOverride未开 |
a2enmod rewrite + 检查虚拟主机配置 |
| 提交大文件报413 | PHP上传限制太小 | 修改upload_max_filesize和post_max_size |
| 502 Bad Gateway | PHP-FPM崩溃或进程耗尽 | systemctl restart php8.1-fpm + 查看pm.max_children |
6.2 最终验证清单
在宣布环境搭建完成前,按这个清单逐项过一遍:
- Apache服务开机自启:
systemctl is-enabled apache2输出enabled - MySQL服务开机自启:
systemctl is-enabled mysql输出enabled - 删除
/var/www/html/info.php——这个文件太暴露服务器信息,任何扫描器都会发现它 - 测试数据库读写:写一个简单的PHP页面,执行一次INSERT和SELECT
- 从外网通过域名访问你的站点,确认DNS解析和防火墙都正常
- 重启一次服务器,确认所有服务能自动拉起,站点可正常访问
多一条避坑提醒:备份你的Apache和PHP配置
最后我特别想强调一点——搭建过程中一定要记录你改过的每个配置。我自己就有过一次惨痛教训:服务器重装后,凭着记忆重新配置环境,结果有两个配置细节记错了,一个导致PHP-FPM的socket路径对不上,另一个导致虚拟主机配错端口,花了整整一晚上排查。后来我养成了习惯:每改一个配置,就在文件头部注释里写明日期、修改原因,重要配置同步到Git仓库的docs/server-config.md里。
遇到问题先看日志,这是一个通用原则:Apache错误日志在/var/log/apache2/error.log,PHP错误在/var/log/apache2/下或PHP-FPM的log里(默认输出到/var/log/php8.1-fpm.log),MySQL日志在/var/log/mysql/error.log。日志会告诉你90%的真相,剩下的10%靠断点和调试。
如果你按这篇文章走一遍搭好了环境,我觉得你已经可以扔掉面板、自己掌控服务器了。第一次独立搭建环境的过程确实会踩不少坑,但这就是进阶路上必经的一步——踩过的坑才会变成你自己的经验。
