
如果你已经写了几年PHP可能早就习惯了集成环境一键装好或者干脆把项目丢进容器里跑。但说句实话能把经典的LAMP在Ubuntu上从零手动搭一遍的人和只会用管理面板的人遇到线上问题时的处理思路完全不一样。这篇不是给刚入门PHP的小白看的傻瓜式安装说明而是给有基础、想彻底搞明白Apache、MariaDB、PHP-FPM之间是怎么配合工作的进阶开发者准备的实操记录。整个搭建过程我会按真实项目的标准来走包括版本选择、目录规划、权限控制、安全初始化、虚拟主机配置、故障排查每一步都会解释为什么这么做。如果你之前只会在Windows上用某个集成软件这次试着把心态切换到Linux运维视角收获会大很多。1. LAMP依然值得手动搭建选型思路先理清1.1 LAMP四件套的角色分工LAMP是Linux、Apache、MariaDB/MySQL、PHP的组合缩写。四者各司其职Linux是操作系统底座Apache负责接收HTTP请求并把静态资源直接返回PHP模块或PHP-FPM进程负责运行动态脚本MariaDB负责数据持久化存储。理解这层分工很重要因为后续所有配置其实都是在解决四个组件如何正确对话的问题。举个例子当浏览器请求一个PHP页面时请求先到ApacheApache发现URI对应的是.php文件会把请求转交给PHP处理PHP执行过程中可能需要查询数据库拿到结果后生成HTML返回给Apache再由Apache返回给浏览器。任何一个环节配置不对链路就断。手动搭建的意义就在于你能亲眼看到每一条配置指令到底作用在哪个环节。1.2 为什么不用集成环境或容器不是否定集成环境和容器而是它们各有适用场景。集成环境把Apache、PHP、MySQL打包在一起确实省事但你在生产服务器上遇到问题时往往需要对每个组件单独调参这时候如果连组件之间怎么通信都不清楚排查效率会很低。容器的思路更先进但容器镜像默认配置通常偏宽松也屏蔽了系统层面的很多细节。我的建议是本地开发用集成环境或容器都没问题但如果你想成为能处理线上故障的开发者最好先在虚拟机或一台闲置Ubuntu机器上手动搭建一次LAMP。这篇文章就是以这种生产思维来写的下面的每一步都是按可持续维护的标准来做的。提示我这次的操作基于Ubuntu 22.04 LTS环境命令在其他LTS版本上基本通用。如果你用的是非LTS版本个别软件源地址可能有差异不影响整体流程。2. 动手前的准备Ubuntu版本确认与环境初始化2.1 版本选择与系统更新如果你手头还没有Ubuntu环境建议直接装22.04 LTS或24.04 LTSLTS版本有五年以上安全更新适合做服务器。已经装有旧版本的先确认系统版本再决定要不要升级lsb_release -a可以顺便看一下系统架构正常都是amd64这点后面选PHP扩展包时用得上。确认版本之后第一件事是更新软件源缓存和已有软件包sudo apt update sudo apt upgrade -y这里有一个值得养成的好习惯把系统更新和业务软件安装分开执行。因为生产环境上系统内核或者基础库的升级可能会影响正在运行的PHP程序贸然全部升级有风险。但你刚装的机器没有业务压力这时候一次性升到位最省心。2.2 创建专门的运行用户与目录规划很多教程一上来就直接用root操作这确实简单但存在安全隐患。PHP项目在root权限下运行的话一旦代码出现任意文件写入漏洞攻击者可以直接控制整台机器。正确的做法是创建一个普通用户用来部署和运行Web服务。sudo useradd -m -s /bin/bash webapp sudo passwd webapp然后规划目录。我习惯用以下结构sudo mkdir -p /var/www/myproject/{public,logs,storage}publicWeb根目录存放入口文件如index.phplogs项目专属日志独立于系统日志storage业务缓存、上传文件等运行期数据这个结构虽然简单但目录语义清晰后面配置虚拟主机和设置权限时非常直观。等系统里项目多了你会发现每个项目独立目录的人维护成本远低于把所有内容堆在某个默认目录下。3. Apache安装与配置的实操细节3.1 安装Apache与默认目录结构Ubuntu的Apache包名是apache2安装方式sudo apt install apache2 -y安装完成后服务默认就是启动的。检查一下状态sudo systemctl status apache2如果看到active (running)说明服务正常。Ubuntu下Apache的配置文件有自己的一套组织方式和源码编译安装的Apache不太一样新手很容易搞混。关键目录如下/etc/apache2/apache2.conf主配置文件/etc/apache2/ports.conf监听端口配置/etc/apache2/sites-available/可用站点配置/etc/apache2/sites-enabled/已启用站点配置通常是指向available目录的软链接/etc/apache2/mods-available/和/etc/apache2/mods-enabled/模块配置目录/var/log/apache2/日志目录Ubuntu这种可用/启用双目录设计本质上是把Apache的模块化配置进一步封装成了站点级别的开关。启用一个站点不是直接改配置文件而是执行a2ensite命令创建软链接禁用则用a2dissite。这个设计非常利于多站点管理。3.2 启用关键模块与配置虚拟主机Apache本身功能很克制很多能力都要通过模块启用。在LAMP场景下至少需要以下几个模块sudo a2enmod rewrite sudo a2enmod headers sudo a2enmod expiresrewriteURL重写绝大多数PHP框架的伪静态路由都依赖它headers设置HTTP响应头方便后续做安全加固expires静态资源缓存控制模块启用的原理就是创建软链接到mods-enabled所以修改后需要重启Apache生效sudo systemctl restart apache2接下来是最核心的一步配置虚拟主机。我们的目标是让Apache知道来自某个域名的请求要交给哪个目录处理。打开默认站点配置看看Apache的默认写法然后我们新建一个项目专属的配置文件sudo nano /etc/apache2/sites-available/myproject.conf一个最基础的PHP虚拟主机配置长这样VirtualHost *:80 ServerName myproject.local ServerAlias www.myproject.local DocumentRoot /var/www/myproject/public Directory /var/www/myproject/public Options Indexes FollowSymLinks AllowOverride All Require all granted /Directory ErrorLog ${APACHE_LOG_DIR}/myproject-error.log CustomLog ${APACHE_LOG_DIR}/myproject-access.log combined /VirtualHost这里有几个容易忽略的点AllowOverride All必须开启否则.htaccess文件不会生效很多PHP框架的路由会直接失效。DocumentRoot必须指向项目的public目录而不是项目根目录。这样可以让用户无法直接访问项目里的源码文件、配置文件这是PHP项目部署的基本安全习惯。ServerName在本地测试时可以用自定义域名但需要同步修改本机的/etc/hosts文件。3.3 Apache配置容易踩的坑第一个坑是端口被占用。如果你本机之前装过Nginx或者其他Web服务Apache启动时可能报Address already in use。排查方法sudo netstat -tlnp | grep :80找到占用端口的进程要么停掉它要么改Apache监听端口。第二个坑是改了配置但没生效。Apache的配置语法检查命令是apachectl configtest这个命令在修改完配置文件之后建议必跑sudo apachectl configtest输出Syntax OK才说明语法没问题然后sudo systemctl reload apache2重载配置。注意改动配置用reload增删模块或者改动监听端口才需要restart区分清楚可以减少很多无谓的抖动。第三个坑是对目录权限的误解。Apache默认以www-data用户运行如果项目的日志目录属主不是www-dataPHP写入日志时就会报权限错误。这一点在后面第6章会详细讲。4. 数据库层MariaDB安装、初始化与账号体系4.1 为什么选MariaDBMySQL和MariaDB在Ubuntu的软件源里都有但默认安装的往往是MariaDB。MariaDB是MySQL的开源分支API完全兼容PHP连接方式和MySQL一模一样但我们团队实际用下来MariaDB在并发写入场景下的表现更稳而且几个关键性能参数的开源实现更透明。对个人开发者来说选哪个都行但如果你希望长期维护MariaDB默认配置更省心。不要纠结所谓的是不是MySQLPHP的PDO扩展通过mysql驱动连接MariaDB没有任何问题这是完全兼容的。你在代码里的new PDO(mysql:hostlocalhost;dbnamemyproject, ...)写法不变。4.2 安装与安全初始化安装sudo apt install mariadb-server -yMariaDB在Ubuntu上的默认认证方式有时会让人困惑——root用户通过unix_socket插件认证也就是说只有系统root用户或带sudo的用户能直接登录数据库root账号。这样的设计其实是更安全的数据库root账号不依赖密码而是和操作系统用户强绑定避免了空密码或者弱密码暴露在网络上。查看服务状态sudo systemctl status mariadb接下来运行安全初始化脚本sudo mysql_secure_installation脚本会问你几个问题逐一说明Enter current password for root直接回车因为当前root走socket认证没有密码Switch to unix_socket authentication选择Y保持现有的安全认证方式Change the root password这个看情况。如果root只在本机通过sudo登录可以不设密码但如果你的应用确实需要远程管理数据库那就要单独创建账号而不是给root设密码Remove anonymous usersYDisallow root login remotelyY这是必须的Remove test databaseYReload privilege tablesY这套交互式初始化其实是在帮你清理数据库默认安装的隐患删除匿名用户、删除测试库、禁止root远程登录。每一项都是安全基线生产环境务必要做。4.3 创建业务数据库和最小权限账号接下来的操作习惯我强烈建议所有PHP开发者都遵守不要用root账号连接业务库而是单独创建一个权限受限的账号。登录数据库sudo mariadb执行以下SQLCREATE DATABASE myproject DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER myproject_userlocalhost IDENTIFIED BY 这里填一个高强度的密码; GRANT ALL PRIVILEGES ON myproject.* TO myproject_userlocalhost; FLUSH PRIVILEGES;两点说明myproject_userlocalhost表示该账号只能从本机连接这个限制能避免数据库账号从外网被暴力破解。如果你的PHP应用和数据库在同一台机器上这个配置是最优解。ON myproject.*表示权限范围只限myproject这个库。不要随手GRANT ALL ON *.*那等于给了账号整个数据库实例的权限一旦代码注入攻击者就能把整库拖走。还有一个细节字符集用utf8mb4而不是utf8。虽然MySQL的utf8字符集看起来差不多但它最多存3字节的字符像Emoji这类4字节字符存不进去会导致线上报错。utf8mb4是完整版的UTF-8从一开始就选它后面少很多麻烦。5. PHP的安装与扩展版本选择是重点5.1 PHP版本仓库配置Ubuntu默认软件源里带的PHP版本往往偏保守。如果只是写简单的脚本默认版本够用但如果你要跑现代PHP框架比如Laravel、Symfony就需要至少PHP 8.1以上的版本这时候建议添加第三方源。安装软件源管理工具和相关依赖sudo apt install software-properties-common -y sudo add-apt-repository ppa:ondrej/php -y sudo apt updateondrej/php源维护得很活跃包含多个PHP版本、所有官方扩展以及PHP-FPM。安装PHP 8.3及常用扩展sudo apt install php8.3-fpm php8.3-mysql php8.3-cli php8.3-curl php8.3-gd php8.3-mbstring php8.3-xml php8.3-zip php8.3-bcmath php8.3-intl -y这里每个扩展都有明确用途php8.3-mysql连数据库的驱动必装php8.3-curl请求外部接口的工具包很多SDK依赖它php8.3-gd图像处理验证码这类功能要用php8.3-mbstring多字节字符串中文场景必装php8.3-xmlXML解析很多框架的配置读取依赖它php8.3-zip压缩包处理Composer安装依赖时需要php8.3-bcmath高精度数学运算涉及金额计算必装php8.3-intl国际化组件做多语言站点需要安装完之后可以确认一下版本php -v5.2 PHP核心扩展怎么选很多人不知道PHP扩展是分两部分的一部分是编译进PHP核心或者以模块形式加载的另一部分是独立的php8.3-xxx软件包。上面的命令安装的是Debian系打包好的扩展它们会自动注册到PHP配置里。启用或禁用扩展有两个命令phpenmod mbstring # 启用 phpdismod gd # 禁用但注意这个命令只是生成或删除/etc/php/8.3/mods-available里的软链接。真正生效的配置会分三种上下文Apache模块、CLI命令行、PHP-FPM。它们的配置目录是分开的/etc/php/8.3/apache2/Apache mod_php的配置/etc/php/8.3/cli/命令行PHP的配置/etc/php/8.3/fpm/PHP-FPM进程的配置这解释了为什么很多新手改了php.ini却不起作用——你可能改的是CLI的配置而Web请求走的是FPM的配置两边完全是独立文件。进阶开发者一定要记住改哪个上下文就改哪个目录下的php.ini。查看当前加载的扩展php -m5.3 PHP-FPM与Apache的对接方式PHP运行模式有两种常见选择mod_phpApache模块方式和PHP-FPM独立进程管理器。mod_php简单把PHP直接编译进Apache性能在老场景下不错但它与Apache进程生命周期绑定PHP出问题可能拖垮Apache多项目多版本也不好隔离。PHP-FPM的方式是PHP作为独立进程池运行Apache通过FastCGI协议把请求转发给PHP-FPM两者之间通过Socket通信。这种方式隔离性更好也方便在同一台机器上为不同项目启用不同PHP版本。我在生产环境一律用PHP-FPM。启用Apache的FastCGI模块并配置PHP-FPMsudo a2enmod proxy_fcgi setenvif sudo a2enconf php8.3-fpm sudo systemctl restart apache2这里a2enconf是把PHP-FPM的Socket配置关联进Apache。Ubuntu在安装php8.3-fpm时其实已经自动生成了大部分配置你只需要确认模块和配置已启用。检查PHP-FPM状态sudo systemctl status php8.3-fpm默认情况下PHP-FPM监听的是/run/php/php8.3-fpm.sock这个Socket文件。在虚拟主机配置里我们需要告诉Apache把.php请求交给这个Socket。这一步留到第6章一起做。6. 让虚拟主机、PHP-FPM与权限体系协同工作6.1 虚拟主机配置详解在完成Apache、PHP、MariaDB的安装之后整个搭建的关键在于把它们串联。继续编辑myproject.confVirtualHost *:80 ServerName myproject.local ServerAlias www.myproject.local DocumentRoot /var/www/myproject/public Directory /var/www/myproject/public Options Indexes FollowSymLinks AllowOverride All Require all granted /Directory # 将.php请求转发给PHP-FPM FilesMatch \.php$ SetHandler proxy:unix:/run/php/php8.3-fpm.sock|fcgi://localhost /FilesMatch # 禁止访问隐藏文件 FilesMatch ^\. Require all denied /FilesMatch ErrorLog ${APACHE_LOG_DIR}/myproject-error.log CustomLog ${APACHE_LOG_DIR}/myproject-access.log combined /VirtualHost重点看FilesMatch里那行SetHandler proxy:unix:/run/php/php8.3-fpm.sock|fcgi://localhost这段配置的意思是当Apache发现请求的文件以.php结尾就把请求通过php8.3-fpm.sock这个Unix Socket转给PHP-FPM处理协议是FastCGI。如果你之前在别的教程里见过类似SetHandler proxy:fcgi://127.0.0.1:9000那是通过网络端口通信的写法两种都可以但Socket方式比TCP端口方式更快、更安全因为不需要在网络上暴露PHP端口。接下来启用这个站点并禁用默认站点sudo a2ensite myproject.conf sudo a2dissite 000-default.conf sudo apachectl configtest sudo systemctl reload apache26.2 目录权限与文件属主的处理权限配置是整个部署里最容易出问题的地方。前面提到Apache以www-data用户运行PHP-FPM默认也是www-data用户所以项目目录里需要让www-data可写的地方必须设置好属主。我推荐的做法是按目录职责区分权限sudo chown -R webapp:www-data /var/www/myproject/public sudo chmod 750 /var/www/myproject/public sudo chown -R www-data:www-data /var/www/myproject/logs /var/www/myproject/storagepublic的属主是webapp、属组是www-data这样你平时可以用webapp账号直接编辑代码而Apache和PHP-FPM也能读但不能随意写代码目录。logs和storage直接给www-data属主因为PHP运行时要写日志、写缓存、传文件。注意外层目录的权限比如/var/www/myproject如果完全不可读Apache也进不去子目录。目录逐级都要有执行权限也就是x这是Linux权限和Windows权限的一个差异点。6.3 验证整个LAMP链路在public目录写一个探针文件sudo -u webapp nano /var/www/myproject/public/index.php内容?php phpinfo();然后本机hosts指向正确后浏览器访问http://myproject.local如果能看到PHP探针页面说明Apache和PHP-FPM已连通。接下来测试数据库连接写一个临时脚本?php try { $pdo new PDO(mysql:hostlocalhost;dbnamemyproject;charsetutf8mb4, myproject_user, 你的密码); echo Database connection OK; } catch (PDOException $e) { echo Connection failed: . $e-getMessage(); }如果输出Database connection OK说明整个链路已经通了一半。到此LAMP四层组件就全部串联起来了。7. 上线前的安全加固与基础性能优化7.1 安全项逐项检查搭建完成不等于可以直接上线至少要把下面的安全检查做一遍1. 移除探针文件并使用生产配置刚才写的临时测试文件一定要删phpinfo()会把当前的PHP配置、模块、环境变量全部展示出来这是非常危险的信息泄露sudo rm /var/www/myproject/public/index.php同时把PHP的运行环境从development切换到productionsudo phpenmod -v 8.3 productionUbuntu的PHP包自带了不同环境的配置生产配置会关闭display_errors避免错误信息直接暴露给用户。2. 关闭目录列表看Apache配置里Options IndexesIndexes表示允许显示目录文件列表。如果你的站点某个目录下没有默认索引文件访问目录URL就会列出所有文件这显然不安全。建议把Indexes去掉Options FollowSymLinks3. 隐藏Apache版本号编辑/etc/apache2/conf-available/security.conf确认以下配置ServerTokens Prod ServerSignature Off这样响应头里不会暴露完整的Apache版本号也减少了攻击者探测服务器指纹的渠道。4. 数据库账号复查执行SELECT user, host, plugin FROM mysql.user;检查是否有奇怪的账号尤其要确认root的host不是%允许任意主机登录。如果发现异常及时清理权限。7.2 性能参数微调刚搭建完的LAMP默认配置是按通用场景给的针对PHP业务可以做几项微调。PHP-FPM的进程管理配置在/etc/php/8.3/fpm/pool.d/www.conf。最关键的是以下三个参数pm dynamic pm.max_children 20 pm.start_servers 5 pm.min_spare_servers 5 pm.max_spare_servers 10max_children需要根据内存估算。假设每个PHP-FPM进程平均占用约30-40MB内存一台2GB内存的机器还要给MariaDB和Apache留余量max_children设在20左右比较安全。设置太大内存不够会导致OOM设置太小并发上来后请求排队反而更慢。再说MariaDB默认配置适用小流量场景。如果你预期并发查询比较多建议至少调整/etc/mysql/mariadb.conf.d/50-server.cnf里的缓冲池大小[mysqld] innodb_buffer_pool_size 256Minnodb_buffer_pool_size是InnoDB引擎的缓冲池负责缓存表数据和索引。调大它能让热数据留在内存里减少磁盘IO。但也不是越大越好一般建议设为物理内存的50%-70%左右比如4G内存设2G。改完重启MariaDBsudo systemctl restart mariadb8. 故障排查与维护命令速查8.1 常见问题定位方法整个LAMP链路里故障可能出在任何一层但排查有个固定顺序先看进程状态再看日志最后验证配置。场景一网站返回500错误第一步看Apache错误日志sudo tail -f /var/log/apache2/myproject-error.log如果日志里出现关于/run/php/php8.3-fpm.sock的权限或连接错误多半是PHP-FPM没启动或者Socket路径不匹配。执行sudo systemctl status php8.3-fpm确认Socket文件存在且权限正确。ls -l /run/php/php8.3-fpm.sock如果属主不是www-data需要检查FPM配置里的listen.owner和listen.group。场景二MySQL连接被拒绝日志里出现SQLSTATE[HY000] [2002] Connection refused先确认MariaDB进程还活着sudo systemctl status mariadb再检查端口监听sudo netstat -tlnp | grep 3306如果服务正常但连接还是失败那就是账号权限的问题。回到第4章确认创建账号的host是localhost而不是其他值。场景三URL重写不生效访问非根路由时出现404多半是AllowOverride没开或者rewrite模块没启用。逐个确认sudo apachectl -M | grep rewrite如果没有输出重新执行sudo a2enmod rewrite并重启Apache。场景四PHP扩展加载但代码还是不认比如你明明装了php8.3-mbstring但PHP代码里调用mb_strlen()还是报未定义。此时要分清CLI和FPM两套独立环境php -m | grep mbstring # CLI环境 sudo php -i | grep Loaded Configuration # 确认CLI配置路径 curl -I http://myproject.local # 确认Web环境是否走了FPM用探针文件确认Web环境实际加载的扩展列表别只看命令行结果。8.2 日常维护命令清单把常用的系统操作整理成一张速查表会显著提高日常维护效率操作场景命令查看Apache状态sudo systemctl status apache2重载Apache配置sudo systemctl reload apache2检查Apache配置语法sudo apachectl configtest查看PHP-FPM状态sudo systemctl status php8.3-fpm重启PHP-FPMsudo systemctl restart php8.3-fpm查看MariaDB状态sudo systemctl status mariadb排查端口占用sudo netstat -tlnp | grep :80查看PHP已加载扩展php -m跟踪错误日志sudo tail -f /var/log/apache2/myproject-error.log清理apt缓存sudo apt clean还有一件事情值得做启用Ubuntu的自动安全更新。这条只对服务器有实际意义本地开发无所谓sudo dpkg-reconfigure --prioritylow unattended-upgrades选择启用即可。安全补丁日常不会打扰人但它能在漏洞公开后的第一时间自动修复系统组件这对长期运行的项目是很重要的保障。一点总结和我的个人习惯LAMP在今天的生态里看起来有点“老派”但它仍然承载着大量真实业务。手动搭建这套环境你收获的不只是一个能跑PHP的服务器而是对组件边界、通信方式、权限模型都建立起了整体认知。之后不管是排查性能瓶颈还是把服务迁移到容器或云主机你都能更快定位问题在哪一层。最后分享一个小经验我在配置文件里写注释的习惯是从手动搭建LAMP时养成的。每改一行注释里写清楚这行的作用和改动日期。三个月后再回去看你大概率会感谢当时的自己。如果你按这篇教程顺利跑通了建议再拿一台新机器闭卷练一遍过程中卡住的步骤就是你需要补的知识点。