拓冰建站拓冰建站
首页 / 资讯中心 / 正文

LNMP架构实战:Nginx+MySQL+PHP搭建与动静分离配置详解

最近在折腾服务器的时候把 LNMP 这套组合从头到尾重新梳理了一遍从安装、配置到动静分离的实现方式踩了不少坑也把很多之前知其然不知其所以然的细节彻底搞明白了。这篇就拿实际搭建过程为线索把 Nginx MySQL PHP 这套环境的搭建思路、配置要点和动静分离原理一次讲透希望能帮你少走弯路。这篇文章适合刚接触服务端部署的开发者也适合那些已经在用集成环境、但对底层配置比较陌生的朋友。我会从架构选型讲起再到核心配置逐行拆解最后用真实踩坑记录收尾你可以直接照着操作也可以当配置手册随时翻查。1. LNMP 架构认知与部署思路1.1 什么是 LNMP为什么选这套组合LNMP 是 Linux Nginx MySQL PHP 的缩写是目前 Web 服务端最常见的技术栈之一。它的核心工作模式是Nginx 作为前端入口接收所有 HTTP 请求当请求是静态资源图片、CSS、JS文件时Nginx 直接处理并返回当请求是动态脚本PHP 文件时Nginx 把请求转发给 PHP-FPM 进程去执行PHP 再按需读写 MySQL 数据库最终把结果返回给客户端。这个架构里Nginx 承担的是门卫和静态文件服务器角色PHP-FPM 是动态处理器MySQL 是数据仓库。三者各司其职配合非常顺畅。为什么选这套而不是传统的 LAMPApache MySQL PHP最直接的原因是高并发场景下的表现差异。Apache 通常把动态请求和静态请求都交给自身模块处理每个连接会占用不少进程或线程资源而 Nginx 的事件驱动模型让它能用非常少的系统资源支撑大量并发连接。静态文件这块Nginx 的处理效率也明显高于 Apache。实测中同样的静态资源请求Nginx 的 QPS 往往能达到 Apache 的数倍而内存占用却低得多。还有一个实际原因现在前端工程化之后Vue、React 这类项目的构建产物就是一堆纯静态文件Nginx 处理起来极其轻松同时反向代理能力可以把 /api 这类动态请求转发到后端服务天然契合前后端分离的部署需求。1.2 环境准备与组件版本选型先说系统环境。我这次用的是 CentOS 7.x 最小化安装这也是生产环境里非常常见的系统版本。如果你用 Ubuntu/Debian命令会有差异但架构思路完全一致。版本搭配上我选了相对经典稳定的一组Nginx 1.20.x、MySQL 5.7、PHP 7.4。这几个版本都是各自产品线里经过大量生产验证的版本生态成熟、资料丰富、兼容性好。如果你的项目特别新可以考虑 PHP 8.x MySQL 8.0但没必要盲目追新——稳定压倒一切尤其是在生产环境。组件安装方式有两条路线一是用系统包管理器yum/apt安装优点是快速省事、升级方便、依赖自动处理二是源码编译安装优点是参数可控、可以针对 CPU 指令集做优化但耗时长、依赖坑多。我的建议很明确非特殊需求直接走包管理器。很多人一提 LNMP 就联想到编译安装半小时其实根本没有必要yum 安装的 Nginx 和 PHP 性能差异微乎其微省下的时间拿来调配置更值。动手之前还有两个前置事项需要确认。第一提前关闭 SELinux 或者把它设为 permissive 模式否则大概率会在访问 PHP 时遇到 Permission denied 这类诡异问题。第二防火墙放行 80/443 端口或者临时关闭 firewalld。这一步经常被忽略结果环境搭建好了浏览器却死活访问不通其实请求根本没到 Nginx。# 关闭 SELinux修改配置文件后重启生效 sed -i s/SELINUXenforcing/SELINUXdisabled/ /etc/selinux/config # 临时放行 80 端口当前会话生效 firewall-cmd --permanent --add-port80/tcp firewall-cmd --reload这些准备工作做完就可以正式进入安装环节了。2. 三大核心组件的安装与联动配置2.1 Nginx 安装与基础调优CentOS 自带的 yum 源里 Nginx 版本通常偏旧我习惯直接使用 Nginx 官方源安装最新稳定版。新建/etc/yum.repos.d/nginx.repo写入如下内容[nginx-stable] namenginx stable repo baseurlhttp://nginx.org/packages/centos/$releasever/$basearch/ gpgcheck1 enabled1 gpgkeyhttps://nginx.org/keys/nginx_signing.key module_hotfixestrue然后执行yum install -y nginx装完后 Nginx 的目录结构是这样的配置文件目录/etc/nginx/主配置文件/etc/nginx/nginx.conf站点配置目录/etc/nginx/conf.d/默认站点根目录/usr/share/nginx/html/日志目录/var/log/nginx/这里要特别强调一下 conf.d 目录的使用习惯。主配置文件 nginx.conf 里默认会include /etc/nginx/conf.d/*.conf;也就是说这个目录下的所有 .conf 文件都会被自动加载。一个站点对应一个 conf 文件结构清晰出问题也好排查。千万别把所有站点配置全塞进 nginx.conf那会非常混乱。装完后先对 nginx.conf 里几个核心参数做调整。worker_processes建议设为 CPU 核心数可以用grep processor /proc/cpuinfo | wc -l查看worker_connections表示每个 worker 进程的最大连接数默认 1024 可以适当提高到 4096。这两个参数决定了 Nginx 的并发处理上限但也不是越大越好——如果你只是个小站点没必要一上来就把连接数拉到 65535反而可能因为文件描述符限制导致启动报错。2.2 MySQL 安装要点MySQL 的安装同样走 yum 路线。先安装官方仓库yum install -y https://repo.mysql.com/mysql57-community-release-el7.rpm然后yum install -y mysql-community-server。装完之后启动服务systemctl start mysqld systemctl enable mysqldMySQL 5.7 首次启动时会生成一个临时 root 密码可以在错误日志里找到grep temporary password /var/log/mysqld.log拿到临时密码后执行mysql_secure_installation进行安全初始化。这个交互式脚本会引导你完成设置 root 密码、删除匿名用户、禁止 root 远程登录、删除测试库等操作按提示一步步走就行。接下来就是创建业务库和专用账号。注意生产环境绝对不要用 root 账号直接连应用给应用分配一个权限最小化的专用账号是好习惯CREATE DATABASE myapp DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; CREATE USER myapp_userlocalhost IDENTIFIED BY 你的强密码; GRANT ALL PRIVILEGES ON myapp.* TO myapp_userlocalhost; FLUSH PRIVILEGES;字符集我习惯用 utf8mb4 而不是 utf8因为 utf8 在 MySQL 里最多只支持 3 个字节像 emoji 这类 4 字节字符会存不进去导致报错或乱码。这个问题在新手阶段很常见与其后期改库不如一开始就选对。2.3 PHP-FPM 安装与 Nginx 联动PHP 的安装稍微复杂一些因为需要装不少扩展。CentOS 自带的源 PHP 版本太老我同样建议先装 EPEL 和 REMI 源然后把 PHP 切换到 7.4yum install -y epel-release yum install -y https://rpms.remirepo.net/enterprise/remi-release-7.rpm yum install -y yum-utils yum-config-manager --enable remi-php74 yum install -y php php-fpm php-mysql php-gd php-xml php-mbstring php-jsonphp-fpm 是 PHP 的 FastCGI 进程管理器Nginx 本身不解析 PHP 代码它只负责把 PHP 请求转交给 php-fpmphp-fpm 执行完再把结果返回给 Nginx。这个机制必须首先搞清楚否则后面遇到PHP 文件变成下载这类问题时会一脸懵。安装完成后核心配置在/etc/php-fpm.d/www.conf。重点检查这几个参数user nginx group nginx listen /run/php-fpm/www.sockuser和group建议改成 nginx这样 php-fpm 进程与 nginx 运行身份一致不容易出现权限问题。listen有两种模式IP:端口比如 127.0.0.1:9000和 Unix Socket比如 /run/php-fpm/www.sock。Socket 方式少了 TCP 协议栈开销性能略好我用的是 socket 方式。PHP 装好后Nginx 里最关键的一段配置就是 fastcgi 相关参数。最简单的测试配置长这样server { listen 80; server_name _; root /usr/share/nginx/html; index index.php index.html; location / { try_files $uri $uri/ 404; } location ~ \.php$ { fastcgi_pass unix:/run/php-fpm/www.sock; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } }fastcgi_pass指定 PHP-FPM 的监听地址fastcgi_param SCRIPT_FILENAME告诉 FPM 要执行哪个 PHP 文件include fastcgi_params导入 FastCGI 标准参数。三段缺一不可很多解析异常都是这三行没写对。然后用一条命令验证整条链路是否通畅echo ?php phpinfo(); ? /usr/share/nginx/html/info.php curl -I http://127.0.0.1/info.php如果返回 HTTP/1.1 200 且带有 X-Powered-By: PHP 字样说明 Nginx 和 PHP-FPM 已经成功联动。3. 动静分离的核心原理与配置实战3.1 动静分离到底在解决什么问题动静分离字面意思就是动态请求和静态请求分开处理。静态资源图片、CSS、JS、字体文件等由 Nginx 直接读取文件系统并返回完全不经过 PHP-FPM只有 .php 这样的动态请求才交给 PHP-FPM 处理。为什么要这么做核心原因是性能。假设用户访问一个页面页面里有 10 张图片、3 个 JS 文件、2 个 CSS 文件如果这些静态资源都由 PHP 脚本动态输出意味着浏览器每请求一个文件PHP-FPM 就要启动一次脚本执行生命周期从框架初始化、路由解析到渲染输出整套流程跑一遍。据统计PHP 应用中约 60%~70% 的请求其实是静态资源让它们去消耗 PHP 进程池完全是在浪费资源。Nginx 处理静态文件的效率极高因为它不需要 fork 子进程、不需要解析脚本直接通过 sendfile 系统调用把文件内容从磁盘发到网络。我自己在同一台机器上做过粗略测试同样一个 100KB 左右的图片走 Nginx 直出的响应时间基本在 1~3ms走 PHP 脚本输出则普遍在 20ms 以上这还只是单次请求的差距。高并发场景下动静分离节省出来的 PHP 进程资源可以直接留给真正需要它的动态接口。动静分离的底层逻辑其实就是专业的事交给专业的组件做Nginx 擅长处理静态文件和并发连接PHP-FPM 擅长执行业务逻辑MySQL 擅长数据存取。合理的架构应该让每个组件只做自己最擅长的事。3.2 完整配置与参数解析理解了原理接下来看实战配置。动静分离的关键技术点是 Nginx 的 location 匹配规则。Nginx 匹配 location 的时候有一套优先级体系从高到低大致是精确匹配^~普通前缀匹配命中后不再检查正则~/~*正则匹配区分/不区分大小写 普通前缀匹配最长匹配原则。这套优先级不掌握配置写起来就很容易出现明明写了规则却不生效的情况。一个典型的动静分离配置模板如下server { listen 80; server_name example.com; root /var/www/example; index index.php index.html; # 静态文件直接由 Nginx 处理 location ~* \.(gif|jpg|jpeg|png|bmp|swf|css|js|ico|woff|woff2|ttf|svg)$ { expires 7d; add_header Cache-Control public, max-age604800; try_files $uri 404; } # 动态请求交给 PHP-FPM location ~ \.php$ { fastcgi_pass unix:/run/php-fpm/www.sock; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; fastcgi_read_timeout 120s; } location / { try_files $uri $uri/ 404; } }第一段静态资源规则用了正则匹配凡是以括号里这些后缀结尾的请求都会命中。expires 7d是告诉浏览器这些文件可以缓存 7 天之后访问直接使用本地缓存连请求都不用发这对用户体验和服务器压力都是巨大改善。注意try_files $uri 404也很重要它保证文件真实存在才返回不存在就直接 404防止某些情况下 Nginx 把静态资源请求又转给后端处理。第二段 PHP 规则只匹配 .php 结尾的请求交 PHP-FPM 执行。fastcgi_read_timeout 120s是我习惯设置的防止某些慢接口执行时间过长被提前掐断导致 504。第三段是兜底规则处理既不是静态文件也不是 PHP 的其他请求。这里有一个很常见的配置误区和大家分享下很多人会在静态规则里加try_files $uri 404但如果你的静态资源文件确实存在却返回 404多半不是规则问题而是文件系统权限问题——Nginx 进程nginx 用户没有读目标文件的权限。遇到这种情况先ls -l看看文件权限再判断是不是 location 没匹配上。3.3 多站点部署与反向代理扩展动静分离解决的是静态文件和动态脚本分流的问题但在真实业务里一个 Nginx 往往还要承担多站点部署、前后端分离反向代理等工作。这三件事经常是同时发生的。多站点部署其实很简单在 conf.d 目录下为每个站点建一个独立的 server 配置块。比如同时跑两个项目一个放在 /var/www/site-a一个放在 /var/www/site-b分别绑定不同的 server_name 或端口。# /etc/nginx/conf.d/site-b.conf server { listen 80; server_name b.example.com; root /var/www/site-b; index index.php index.html; location / { try_files $uri $uri/ 404; } location ~ \.php$ { fastcgi_pass unix:/run/php-fpm/www.sock; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } }这样只要域名解析到服务器 IPNginx 会根据 server_name 自动路由到对应的站点目录互不干扰。再来说反向代理。现在的项目越来越流行前后端分离前端是 Vue/React 构建出来的静态文件由 Nginx 直接托管后端是 Java/PHP/Go 写的 API 服务跑在本机的某个端口上。此时 Nginx 不仅要做动静分离还要把 /api 开头的请求转发给后端服务location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }proxy_pass把请求转发到本地 8080 端口的后端服务proxy_set_header X-Real-IP和X-Forwarded-For是用来传递客户端真实 IP 的。这里有个容易犯的错误如果后端服务要拿到用户真实 IPNginx 必须在反代时显式传递这些头部否则后端拿到的 IP 全部是 127.0.0.1。结合动静分离与反向代理一个典型的前后端分离项目完整配置可以是server { listen 80; server_name myapp.com; # 前端静态资源 root /var/www/myapp/dist; index index.html; location / { try_files $uri $uri/ /index.html; } # 后端 API 反向代理 location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 图片等静态资源走本地 location ~* \.(png|jpg|jpeg|gif|css|js|ico)$ { expires 30d; } }这个配置里try_files $uri $uri/ /index.html是 Vue Router 的 history 模式必备的前端路由对应的 URL 在服务器上并不存在真实文件所以全部重写到 index.html由前端路由接管。如果不写这行刷新一个子页面路由就会 404。这个坑几乎所有做过前后端分离部署的人都踩过。4. 部署过程中的典型问题与排查实录4.1 访问 PHP 文件变成下载文件这个可以说是 LNMP 新手第一大坑。安装好环境后浏览器访问 info.php结果不是显示 phpinfo 页面而是直接弹出一个文件下载框内容就是 PHP 源代码文本。原因非常简单Nginx 不知道 .php 文件需要交给 PHP-FPM 处理它把 PHP 文件当成普通静态文件直接返回了。绝大多数情况都是 server 块里缺少了location ~ \.php$配置段或者 fastcgi_pass 参数写错了。排查路径以我的经验按三步走第一步确认 nginx 配置里确实存在 php location 段把上文 2.3 节那段配置完整贴进去第二步nginx -t验证配置语法然后nginx -s reload重载第三步如果还不行检查include fastcgi_params;这行是否存在缺失这行会导致 FastCGI 参数不完整也会出现类似问题。还有一种隐蔽的情况是配置文件里写了 php location但优先级被更高的规则覆盖了。比如某些 CMS 会默认拦截所有请求转到 index.php导致正则匹配的 php location 永远执行不到。这种就要仔细检查 location 的匹配优先级是否符合预期。4.2 502 Bad Gateway 与 504 Gateway Timeout502 和 504 是 LNMP 环境里另外两个高频报错但很多人会把它们混为一谈其实处理思路完全不同。502 Bad Gateway字面意义是网关错误实际含义是 Nginx 连接不上 PHP-FPM。常见原因有三个php-fpm 服务根本没启动fastcgi_pass 里的 socket 路径与实际 php-fpm 监听路径不一致socket 文件权限不对导致 Nginx 进程无权访问。排查先执行systemctl status php-fpm看服务状态再用ls -l /run/php-fpm/www.sock看 socket 是否存在接着比对 nginx 配置里的fastcgi_pass是不是指向同一个路径。我自己遇到过一次比较典型的 502php-fpm 的 www.conf 里把 listen 改成了127.0.0.1:9000但 nginx 那边 fastcgi_pass 仍然写的unix:/run/php-fpm/www.sock两边对不上结果全部请求 502。这类问题的本质是前后端配置不一致排查时始终围绕Nginx 到底要连哪里php-fpm 到底监听哪里这两个问题展开。504 Gateway Timeout 则是 Nginx 等待 PHP-FPM 响应超时。默认的fastcgi_read_timeout是 60 秒如果某个 PHP 接口因为执行慢 SQL、调用外部接口等原因超过这个时间Nginx 就会返回 504。解决手段要么优化业务缩短执行时间要么适当调大fastcgi_read_timeout。但注意不要把超时时间设得过大否则 PHP-FPM 里堆积的慢请求会越来越多最终拖垮整个进程池。配合 php-fpm 的request_terminate_timeout一起治理慢接口才是正路。4.3 日志分析与安全加固细节日志是排查所有问题的第一现场一定要养成先看日志的习惯。Nginx 的日志分为访问日志和错误日志默认路径分别是访问日志/var/log/nginx/access.log错误日志/var/log/nginx/error.logPHP-FPM 的日志路径是/var/log/php-fpm/error.log当页面 500 或 502 时Nginx 错误日志通常会记录上游连接问题而 PHP-FPM 日志则记录了具体是哪个脚本报错、什么错误类型。两者对照着看定位问题的效率会高很多。线上排查的时候我常用这个组合命令实时观察日志tail -f /var/log/nginx/error.log /var/log/php-fpm/error.log两个日志同时跟踪一眼就能看出是 Nginx 层面的问题还是 PHP 层面的问题。最后聊几个安全加固的习惯都是在实际运维中被逼出来的经验第一关闭 Nginx 版本号显示。在 nginx.conf 的 http 块里加上server_tokens off;否则请求任意不存在的路径404 页面会暴露 Nginx 的完整版本号攻击者可以直接按版本搜索已知漏洞。第二禁止访问 .git、.svn 这类隐藏目录和文件。项目代码里往往藏着敏感的配置文件location ~ /\.(?!well-known) { deny all; }第三禁止目录列表。如果站点某个目录下没有 index 文件默认 Nginx 会返回目录文件列表这等于把服务器文件结构直接暴露给访客。确保配置里autoindex off;默认就是 off避免无意中打开。第四给配置文件做定期备份。配置文件改动前先cp nginx.conf nginx.conf.bak这个习惯救过我很多次——有时候改动一时头大忘了原样出问题再想改回来有备份直接秒回。下面把常见的 LNMP 问题整理成速查表方便你日后对照排查现象可能原因快速定位方法解决方法PHP 文件被下载缺少 php location 段检查 nginx 配置补全 fastcgi 配置403 Forbidden目录无 index 文件或权限不足看 error.log调整文件权限或补 index404 Not Found文件不存在或路径错误检查 root 路径修正站点根目录502 Bad Gatewayphp-fpm 未启动或 socket 不匹配systemctl status php-fpm启动服务或修正 fastcgi_pass504 Gateway Timeout脚本执行超时看 php-fpm 日志调 fastcgi_read_timeout 或优化代码连接被拒绝防火墙或端口未放行curl 127.0.0.1 测试放行端口或关闭 firewall5. 部署流程最终串联组件都装好、配置也理清了最后我把 LNMP 从零到一的整体流程串一遍方便你照着操作先装 Nginx、MySQL、PHP-FPM 三件套并启动然后初始化 MySQL 安全配置并创建业务库和账号接着编写 Nginx server 配置包含动静分离 location最后放一个 phpinfo 测试页验证链路。验证通过后再把业务代码部署到站点目录调整权限一个可用的 LNMP 环境就落地了。整体流程里最容易被忽略的是各组件运行用户的一致性。Nginx 的 worker 进程默认跑在 nginx 用户下php-fpm 的进程默认可能是 nobody 或其他用户如果两边的运行用户不一样php-fpm 处理的文件 Nginx 可能无权访问静态文件也可能因为权限不足返回 404。我自己的习惯是安装后统一把 php-fpm 的user和group改成 nginx然后把站点目录的属主设为 nginx这样权限问题会少一大半。个人实际运维中的体会是LNMP 这套架构的价值不在于某个单一组件的性能有多强而在于组合后各司其职的稳定性。动静分离也不是一个简单的 location 正则就完事它背后是对请求类型的理解和对系统资源的合理分配。配置不需要一次调到完美但要为后续扩展留好余地——多个 conf 文件分开管理、日志路径清晰、参数不过度调优这些习惯比任何一两个配置项都重要。后面我打算继续整理 Nginx 反向代理与负载均衡的实战笔记等实操沉淀得差不多了再更新分享。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门