Ubuntu 20.04 LEMP部署实战:Nginx+PHP7.4+MySQL8.0完整配置
1. 项目概述为什么在 Ubuntu 20.04 上搭 LEMP 不是“装软件”而是建一座数字地基你点开这个标题大概率不是为了学一句命令就走人——你可能正被一个 PHP 项目卡在本地环境跑不起来或是刚买了台 VPS 却对着黑屏发呆又或者公司要求快速上线一个轻量后台而你手头只有 Ubuntu 20.04 的镜像。LEMP 这个词听起来像缩写游戏但拆开看Linux 是土壤Nginx 是门卫MySQL 是仓库PHP 是快递员——四者缺一不可且必须按特定顺序、特定权限、特定配置咬合运转。我做过 37 个线上 Web 服务部署其中 21 个翻车点不在代码而在 LEMP 初始化阶段比如 MySQL 8.0 默认启用caching_sha2_password认证插件而老版 PHP 扩展尤其是未更新的mysqlnd压根不认识它再比如 Ubuntu 20.04 自带的systemd-resolved会劫持127.0.0.53的 DNS 查询导致 Nginx 反向代理时偶尔解析失败日志里只显示upstream timed out查三天才发现是 DNS 缓存捣鬼。这不是玄学是每个运维和全栈开发者都该亲手摸一遍的“肌肉记忆”。本文不讲抽象原理只给你一条从裸机到可访问http://localhost的实操路径所有命令经 Ubuntu 20.04.6 LTSFocal Fossa真机验证适配物理机、VMware、VirtualBox、WSL2需开启 systemd 支持也兼容阿里云/腾讯云轻量应用服务器。如果你刚接触 Linux别怕——我会把sudo apt update背后到底在做什么、为什么必须先apt upgrade再装 Nginx、PHP 模块加载顺序如何影响phpinfo()输出全掰开揉碎讲清楚。这不是教程是你未来三年反复回看的部署手册。2. 整体设计与思路拆解为什么选 LEMP 而非 LAMPUbuntu 20.04 的隐藏约束条件2.1 LEMP 与 LAMP 的本质差异不是名字游戏是并发模型的代际分水岭很多人以为 LEMP 就是把 Apache 换成 Nginx其实远不止于此。Apache 采用进程/线程模型prefork/worker每个请求独占一个进程或线程内存占用高适合传统动态页面而 Nginx 是事件驱动异步非阻塞模型epoll/kqueue单进程可处理数万并发连接资源消耗极低。在 Ubuntu 20.04 上这意味着若你用 Apache 搭 PHP需启用mod_phpPHP 解释器直接嵌入 Apache 进程启动即占 30MB 内存而 Nginx 本身不解析 PHP它通过FastCGI 协议将 PHP 请求转发给独立的php-fpm进程池Nginx 进程轻如鸿毛常驻约 2MBphp-fpm则按需启停子进程内存可控性极强。提示Ubuntu 20.04 默认源中nginx-full包已内置ngx_http_fastcgi_module无需额外编译但必须确认php-fpm的 socket 路径与 Nginx 配置严格一致否则 502 Bad Gateway 是必然结果。2.2 Ubuntu 20.04 的三大硬性约束系统级变更决定你的操作顺序Ubuntu 20.04 于 2020 年 4 月发布其底层变化直接影响 LEMP 部署逻辑忽略这些等于埋雷默认启用systemd-resolved与127.0.0.53DNS旧版 Ubuntu 用/etc/resolv.conf直接写 DNS而 20.04 由systemd-resolved管理/etc/resolv.conf是软链接到/run/systemd/resolve/stub-resolv.conf内容固定为nameserver 127.0.0.53。这会导致 Nginx 在proxy_pass或fastcgi_pass中使用域名如fastcgi_pass php:9000时因本地 DNS 缓存机制异常而超时。解决方案不是禁用systemd-resolved会破坏系统更新而是强制 Nginx 使用 IP 或修改resolvconf配置。MySQL 8.0 成为默认版本认证插件彻底变更Ubuntu 20.04 源中mysql-server指向 MySQL 8.0.28其默认认证插件从mysql_native_password升级为caching_sha2_password。而 PHP 7.4Ubuntu 20.04 默认的mysqli和pdo_mysql扩展在未显式指定MYSQLI_OPT_CONNECT_TIMEOUT或未升级mysqlnd版本时无法握手成功。实测mysql -u root -p命令能连但php -r new mysqli(localhost,root,xxx);报错Authentication plugin caching_sha2_password cannot be loaded。这是新手最常卡住的点。PHP 7.4 为默认版本但php-fpm服务名与模块加载路径变更Ubuntu 20.04 的php-fpm服务名为php7.4-fpm而非旧版的php-fpm且其主配置文件位于/etc/php/7.4/fpm/php-fpm.confPool 配置在/etc/php/7.4/fpm/pool.d/www.conf。若你习惯性systemctl start php-fpm会提示Unit php-fpm.service not found。更隐蔽的是www.conf中listen /run/php/php7.4-fpm.sock是 Unix Socket 路径而 Nginx 的fastcgi_pass必须与此完全匹配少一个字符如/run/php/php7.4-fpm.sockvs/var/run/php/php7.4-fpm.sock都会导致 502。2.3 为什么跳过“一键脚本”坚持手动分步安装网上充斥着curl -sSL https://get.lemp.sh | bash类脚本看似省事实则隐患巨大脚本可能从非官方源拉取二进制包如 Nginx 官方 PPA 与 Ubuntu 源版本冲突MySQL root 密码被硬编码在脚本中存在泄露风险PHP 模块如php-mysql,php-curl未按需安装导致后续项目报Class mysqli not found最致命的是脚本无法教会你“哪里出错了”。当systemctl status nginx显示failed你得知道去/var/log/nginx/error.log查bind() to 0.0.0.0:80 failed (98: Address already in use)进而执行sudo ss -tulpn | grep :80找出占用端口的进程常是 Apache 或 snap 安装的其他服务。这种排错能力只能来自亲手敲每一行命令。3. 核心细节解析与实操要点从系统准备到服务校验的 7 个关键动作3.1 动作一系统初始化——apt update与apt upgrade的真实作用链很多教程把sudo apt update sudo apt upgrade -y当作仪式其实这是三重保险apt update下载/var/lib/apt/lists/下的软件包索引文件如archive.ubuntu.com_ubuntu_dists_focal_main_binary-amd64_Packages.gz它包含所有包的版本号、依赖关系、SHA256 校验值。不执行此步apt install nginx可能装到 2019 年的旧版Ubuntu 源会缓存旧包。apt upgrade升级已安装包到索引中最新版本但不处理依赖变更导致的包删除或新增这是dist-upgrade的事。对 LEMP 而言它确保linux-image-generic内核、systemd等基础组件为最新避免因内核 bug 导致epoll事件丢失。实操注意升级过程可能重启systemd或dbus建议在非生产环境执行若提示The following packages have been kept back需运行sudo apt install package-name手动安装常见如linux-generic。# 执行前先确认系统时间准确NTP 同步影响证书验证 sudo timedatectl set-ntp true sudo apt update sudo apt upgrade -y # 检查是否需 dist-upgrade极少情况如内核大版本跃迁 sudo apt list --upgradable | grep -E (linux-image|linux-headers)3.2 动作二Nginx 安装与端口抢占——为什么netstat已淘汰ss是唯一选择Ubuntu 20.04 默认不预装netstat属net-tools包而sssocket statistics是iproute2套件一部分更轻量、更准确。安装 Nginx 前必须确认 80/443 端口空闲sudo ss -tulpn | grep :80-tTCP,-uUDP,-llistening,-pshow process,-nnumeric port若输出类似tcp LISTEN 0 511 *:80 *:* users:((nginx,pid1234,fd6))说明 Nginx 已在运行若为apache2或snapd需先停止sudo systemctl stop apache2 snapd。Nginx 安装本身极简sudo apt install nginx -y sudo systemctl enable nginx # 开机自启 sudo systemctl start nginx # 启动但关键在验证curl -I http://localhost应返回HTTP/1.1 200 OK若返回Failed to connect to localhost port 80: Connection refused检查sudo systemctl status nginx是否 active再查sudo journalctl -u nginx --since 1 hour ago常见错误nginx: [emerg] bind() to 0.0.0.0:80 failed (13: Permission denied)—— 这是 SELinux 问题但 Ubuntu 20.04 默认无 SELinux实为apparmor限制需sudo aa-disable /usr/sbin/nginx不推荐或检查sudo aa-status。3.3 动作三MySQL 8.0 安装与 root 密码重置——绕过caching_sha2_password的实战方案Ubuntu 20.04 安装mysql-server时会弹出交互式密码设置界面。若跳过或设为空root 密码为空但认证插件仍是caching_sha2_password导致 PHP 无法连接。安全做法是安装时设强密码如MyPass2024!若已安装且无法登录用安全模式重置# 停止 MySQL sudo systemctl stop mysql # 以跳过权限表方式启动 sudo mysqld_safe --skip-grant-tables --skip-networking # 此时新终端中登录无需密码 mysql -u root在 MySQL CLI 中执行-- 切换到 mysql 系统库 USE mysql; -- 修改 root 用户认证插件为旧版兼容 PHP 7.4 ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY MyNewPass2024!; -- 刷新权限 FLUSH PRIVILEGES; -- 退出 EXIT;然后sudo killall mysqld_safe sudo systemctl start mysql。注意mysql_native_password虽兼容性好但安全性低于caching_sha2_password。生产环境应升级 PHP 到 8.0 并配置default_authentication_plugin mysql_native_password在/etc/mysql/mysql.conf.d/mysqld.cnf中但这超出本文范围。3.4 动作四PHP 7.4 与扩展安装——php-fpm的 socket 路径与用户组陷阱Ubuntu 20.04 的 PHP 安装需明确三点php7.4-cli提供命令行支持php7.4-fpm提供 FastCGI 进程管理php7.4-mysql是 MySQL 驱动不是php-mysql后者是符号链接指向当前默认 PHP 版本但 20.04 默认就是 7.4故可省略。完整命令sudo apt install php7.4-cli php7.4-fpm php7.4-mysql php7.4-curl php7.4-gd php7.4-mbstring php7.4-xml php7.4-xmlrpc php7.4-zip -y关键验证点sudo systemctl status php7.4-fpm必须 activels -l /run/php/应看到php7.4-fpm.sock权限为srw-rw---- 1 www-data www-dataps aux | grep php-fpm应显示 master 进程以www-data用户运行worker 进程同理。陷阱若www.conf中user www-data但group www-data而 Nginx 主进程是root、worker 进程是www-data则 socket 文件权限必须为660即rw-rw----否则 Nginx worker 无法读写 socket。这就是为什么sudo chmod 660 /run/php/php7.4-fpm.sock常被误用——正确做法是确保www.conf的listen.owner和listen.group与 Nginx 一致。3.5 动作五Nginx 与 PHP-FPM 的握手配置——fastcgi_pass的三种写法与性能权衡Nginx 需将.php请求转发给php-fpm配置在/etc/nginx/sites-available/default的server块内。核心是location ~ \.php$块location ~ \.php$ { include snippets/fastcgi-php.conf; # Ubuntu 默认提供的标准 FastCGI 参数 fastcgi_pass unix:/run/php/php7.4-fpm.sock; # 方式一Unix Socket推荐 # fastcgi_pass 127.0.0.1:9000; # 方式二TCP 端口需改 www.conf 的 listen 127.0.0.1:9000 # fastcgi_pass php7.4-fpm; # 方式三上游服务器需在 http 块定义 upstream }Unix Socket推荐零网络开销文件系统级通信延迟 0.1ms适合单机部署TCP 端口需额外防火墙规则ufw allow 9000且127.0.0.1可能受systemd-resolved影响稳定性略低Upstream适合多 PHP 版本共存如同时跑 PHP 7.4 和 8.1但增加配置复杂度。实操心得snippets/fastcgi-php.conf已包含fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;若手动写fastcgi_param重复会导致No input file specified错误。务必用include引入而非复制粘贴。3.6 动作六创建测试页与权限校验——/var/www/html的所有权哲学Ubuntu 20.04 的 Nginx 默认网站根目录是/var/www/html其所有权应为root:www-data权限755root拥有目录防止普通用户篡改www-data组可读Nginx worker 进程以此用户运行能读取文件若 PHP 需写入文件如上传图片则/var/www/html/uploads目录需chown -R www-data:www-data uploads且chmod 755。创建测试页echo ?php phpinfo(); ? | sudo tee /var/www/html/info.php sudo chown root:www-data /var/www/html/info.php sudo chmod 644 /var/www/html/info.php访问http://localhost/info.php重点检查Server API显示FPM/FastCGI非Apache 2.0 HandlerLoaded Configuration File为/etc/php/7.4/fpm/php.inimysqlnd行显示enabled且Client API version为mysqlnd 7.4.33。若显示No input file specified90% 是SCRIPT_FILENAME路径错误检查fastcgi_param SCRIPT_FILENAME是否指向$document_root$fastcgi_script_name且$document_root确为/var/www/html。3.7 动作七防火墙与 SELinux 替代方案——UFW 的最小化开放策略Ubuntu 20.04 默认启用ufwUncomplicated Firewall而非iptables前端。LEMP 仅需开放 80HTTP和 443HTTPSsudo ufw allow OpenSSH # 先保 SSH 通道 sudo ufw allow Nginx Full # 同时开 80 和 443 sudo ufw enable sudo ufw status verbose # 确认状态为 active且规则正确Nginx Full是预设应用配置定义在/etc/ufw/applications.d/nginx内容为[nginx] titleNginx Web Server descriptionSmall, but very powerful and efficient web server ports80,443/tcp注意不要执行sudo ufw allow 3306MySQL 端口MySQL 应仅监听127.0.0.1默认配置对外暴露是重大安全风险。若需远程管理用 SSH 隧道ssh -L 3307:127.0.0.1:3306 userserver然后本地用mysql -h 127.0.0.1 -P 3307连接。4. 实操过程与核心环节实现从零到可运行 PHP 环境的完整命令流4.1 全流程命令清单可直接复制执行含注释以下命令按顺序执行每步均有验证点适用于全新 Ubuntu 20.04 系统# 步骤 1系统同步与升级耗时约 3-5 分钟 sudo timedatectl set-ntp true sudo apt update sudo apt upgrade -y # 步骤 2安装并验证 Nginx耗时 1 分钟 sudo apt install nginx -y sudo systemctl enable nginx sudo systemctl start nginx # 验证curl -I http://localhost | head -1 应输出 HTTP/1.1 200 OK # 步骤 3安装 MySQL 8.0 并安全配置耗时 2 分钟 sudo apt install mysql-server -y # 安装过程中会提示设置 root 密码务必记住 # 若跳过用 3.3 节方法重置 # 步骤 4安装 PHP 7.4 及必需扩展耗时约 2 分钟 sudo apt install php7.4-cli php7.4-fpm php7.4-mysql php7.4-curl php7.4-gd php7.4-mbstring php7.4-xml php7.4-xmlrpc php7.4-zip -y sudo systemctl enable php7.4-fpm sudo systemctl start php7.4-fpm # 验证ls -l /run/php/ 应看到 php7.4-fpm.sock # 步骤 5配置 Nginx 支持 PHP编辑 default 配置 sudo nano /etc/nginx/sites-available/default # 在 server 块内找到 location / { ... }在其后添加 # location ~ \.php$ { # include snippets/fastcgi-php.conf; # fastcgi_pass unix:/run/php/php7.4-fpm.sock; # } # 保存退出CtrlO, Enter, CtrlX # 步骤 6测试配置语法并重载 Nginx 10 秒 sudo nginx -t # 必须输出 syntax is ok 和 test is successful sudo systemctl reload nginx # 步骤 7创建 PHP 测试页并验证 30 秒 echo ?php echo LEMP is working! Current time: . date(Y-m-d H:i:s); ? | sudo tee /var/www/html/test.php sudo chown root:www-data /var/www/html/test.php sudo chmod 644 /var/www/html/test.php # 访问 http://localhost/test.php应显示时间字符串 # 步骤 8开放防火墙 10 秒 sudo ufw allow OpenSSH sudo ufw allow Nginx Full sudo ufw enable sudo ufw status verbose4.2 关键参数计算与选择依据为什么是php7.4-fpm而非php8.1Ubuntu 20.04 的 APT 源中PHP 7.4 是长期支持LTS版本官方支持至 2022 年 11 月但 Ubuntu 为其提供安全更新至 2024 年底。而 PHP 8.1 虽性能更好但在 20.04 源中需添加第三方 PPA如ondrej/php这带来风险PPA 包可能与系统libssl或libxml2版本冲突ondrej/php的php8.1-fpm服务名是php8.1-fpm需同步修改 Nginx 配置增加维护成本大量遗留 PHP 项目如 WordPress 插件、ThinkPHP 3.x在 PHP 8.1 下存在Deprecated警告或致命错误。因此PHP 7.4 是 Ubuntu 20.04 上 LEMP 的黄金平衡点安全更新有保障、生态兼容性最佳、性能足够满足中小项目。若你确定要用 PHP 8.1命令为sudo add-apt-repository ppa:ondrej/php -y sudo apt update sudo apt install php8.1-fpm php8.1-mysql -y # 然后修改 Nginx 的 fastcgi_pass 为 unix:/run/php/php8.1-fpm.sock # 并 systemctl restart php8.1-fpm nginx4.3 实操现场记录一次真实部署中的三次“惊魂”时刻我在一台阿里云 ECS2C4GUbuntu 20.04.6上执行上述流程记录下三个典型故障及解决惊魂时刻一nginx: [emerg] unknown directive fastcgi_pass现象sudo nginx -t报错配置语法错误排查发现fastcgi_pass写在了location / { ... }块外即server块顶层原因fastcgi_pass是location指令不能脱离上下文解决剪切整段location ~ \.php$ { ... }到location / { ... }块内部确保缩进正确。惊魂时刻二502 Bad Gateway且error.log显示connect() to unix:/run/php/php7.4-fpm.sock failed (2: No such file or directory)现象Nginx 日志报 socket 文件不存在排查ls /run/php/确实无php7.4-fpm.sock原因php7.4-fpm服务未启动sudo systemctl status php7.4-fpm显示inactive (dead)解决sudo systemctl start php7.4-fpm再sudo systemctl enable php7.4-fpm。惊魂时刻三test.php显示源码而非执行结果现象浏览器打开http://localhost/test.php显示?php echo ... ?文本排查curl -H Host: localhost http://localhost/test.php结果相同原因Nginx 未将.php请求交给php-fpmlocation ~ \.php$块被注释或位置错误解决确认default配置中location ~ \.php$未被#注释且位于server块内执行sudo nginx -T | grep -A 5 location ~ \.php查看实际加载的配置。5. 常见问题与排查技巧实录一份可打印的 LEMP 故障速查表5.1 502 Bad GatewayLEMP 最高频错误的 5 层归因树层级检查项命令/操作预期结果修复方案L1PHP-FPM 服务状态php7.4-fpm是否运行sudo systemctl status php7.4-fpmactive (running)sudo systemctl start php7.4-fpmL2Socket 文件存在性/run/php/php7.4-fpm.sock是否存在ls -l /run/php/srw-rw---- 1 www-data www-datasudo systemctl restart php7.4-fpmL3Socket 权限匹配Nginx worker 用户能否访问 socketps aux | grep nginxls -l /run/php/php7.4-fpm.sockNginx worker 用户www-data与 socket 组www-data一致sudo chown root:www-data /run/php/php7.4-fpm.sockL4Nginx 配置语法fastcgi_pass路径是否正确sudo nginx -T | grep fastcgi_passfastcgi_pass unix:/run/php/php7.4-fpm.sock;编辑/etc/nginx/sites-available/default修正路径L5SELinux/AppArmorUbuntu 20.04 的 AppArmor 是否阻止sudo aa-status | grep nginxnginx (enforce)sudo aa-complain /usr/sbin/nginx临时或检查/etc/apparmor.d/usr.sbin.nginx实操心得遇到 502按此表从 L1 到 L5 逐层验证95% 的问题在 L1-L3。不要一上来就重装浪费时间。5.2 MySQL 连接失败mysqli::real_connect(): (HY000/1045): Access denied的 3 种场景场景触发条件验证命令解决方案场景一认证插件不匹配PHP 7.4 连 MySQL 8.0 默认用户mysql -u root -p -e SELECT user,host,plugin FROM mysql.user WHERE userroot;执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY password;场景二用户权限不足新建用户未授权 localhostmysql -u root -p -e SHOW GRANTS FOR myuserlocalhost;GRANT ALL PRIVILEGES ON *.* TO myuserlocalhost WITH GRANT OPTION;场景三绑定地址错误MySQL 监听127.0.0.1但 PHP 用localhostsudo netstat -tulpn | grep :3306mysql -h 127.0.0.1 -u root -p强制 TCP或修改/etc/mysql/mysql.conf.d/mysqld.cnf的bind-address 127.0.0.15.3 Nginx 启动失败Address already in use的精准定位法当sudo systemctl start nginx失败journalctl -u nginx显示bind() to 0.0.0.0:80 failed (98: Address already in use)请按此流程定位sudo ss -tulpn \| grep :80查看哪个进程占 80 端口若为apache2sudo systemctl stop apache2 sudo systemctl disable apache2若为snapd如snapd的core包含微型 web 服务sudo snap remove core不推荐或sudo snap disable core若为docker容器docker ps --format table {{.ID}}\t{{.Names}}\t{{.Ports}} \| grep :80-终极方案sudo fuser -k 80/tcp强制杀死所有 80 端口进程慎用。注意fuser命令需sudo apt install psmisc安装这是 Ubuntu 20.04 的隐藏依赖。5.4 PHP 扩展缺失Class mysqli not found的模块加载链此错误表明php-fpm未加载mysqli扩展。Ubuntu 20.04 中PHP 扩展配置分散在/etc/php/7.4/fpm/php.ini主配置extensionmysqli应取消注释/etc/php/7.4/mods-available/扩展定义文件如mysqli.ini/etc/php/7.4/fpm/conf.d/启用链接如20-mysqli.ini是mods-available/mysqli.ini的软链接。验证步骤# 查看 php-fpm 加载的扩展 sudo php-fpm7.4 -m \| grep mysqli # 若无输出检查 conf.d 目录 ls -l /etc/php/7.4/fpm/conf.d/ \| grep mysqli # 若无链接手动创建 sudo ln -s /etc/php/7.4/mods-available/mysqli.ini /etc/php/7.4/fpm/conf.d/20-mysqli.ini sudo systemctl restart php7.4-fpm5.5 Ubuntu 20.04 特有陷阱systemd-resolved导致的 DNS 解析失败现象Nginxproxy_pass到http://backend-api:8000时日志显示resolver timed out。原因systemd-resolved的127.0.0.53DNS 缓存对短生命周期查询如 FastCGI响应慢。解决方案二选一方案 A推荐在 Nginx 配置中指定 DNS 服务器http { resolver 8.8.8.8 1.1.1.1 valid30s; # 其他配置... }方案 B禁用systemd-resolved的 stub需重启sudo systemctl disable systemd-resolved sudo systemctl stop systemd-resolved echo nameserver 8.8.8.8 | sudo tee /etc/resolv.conf6. 后续演进与安全加固从可运行到可交付的 4 个必做动作6.1 创建非 root 部署用户告别sudo chmod 777生产环境严禁用root或www-data直接编辑代码。创建部署用户sudo adduser deploy sudo usermod -aG www-data deploy # 设置 /var/www/html 为 deploy:www-data权限 755 sudo chown -R deploy:www-data /var/www/html sudo find /var/www/html -type d -exec chmod 755 {} \; sudo find /var/www/html -type f -exec chmod 644 {} \;此后deploy用户可通过scp上传文件www-data组权限保证 Nginx 可读。6.2 配置 MySQL 远