
1. 方案选型为什么LNMP依然是经典搭配1.1 LNMP解决什么问题LNMP这个组合我在生产环境里用了差不多十年至今仍然觉得它是中小型Web项目最稳的那条路。Linux提供底层稳定性Nginx负责高并发静态资源和反向代理MySQL或MariaDB管数据持久化PHP通过FastCGI协议跟Nginx通信四者各管一摊分工明确。相比Apache那套mod_php的进程模型Nginx加PHP-FPM的组合在并发连接上的表现更平滑内存占用也更可控尤其是站点流量上来之后这种差异会非常明显。很多人问现在都有Docker了还用得着手动部署一套LNMP吗我的看法是Docker适合快速交付和隔离环境但你要排查线上问题时终究得懂这些组件是怎么装上去、怎么协作的。而且不少公司老机器上跑的仍是直接部署的LNMP没有容器化改造的余力。掌握手工部署这套流程相当于把Web服务的内功练扎实了换哪个容器镜像都能举一反三。还有一个很实际的原因LNMP的手工部署过程本身就是很好的性能调优学习素材。每装一个组件你都要想清楚它装在哪个目录、用什么用户跑、socket放哪里、日志写到哪这些决策点全盘想明白了对Nginx配置、PHP-FPM进程模型、MySQL缓冲机制的理解都会深一个层次。1.2 版本搭配PHP 8.2、MySQL 8.0还是MariaDB版本选型是整个部署过程中最需要提前定下来的事直接影响后续兼容性。我当前的推荐组合是Nginx 1.24.x主线稳定版、PHP 8.2.x、MySQL 8.0.x操作系统层面以CentOS 7老机器和Rocky Linux 9新机器都适用为前提来写。PHP版本这块8.2是目前比较折中的选择。8.0和8.1虽然也还在维护但8.2在性能、类型系统、随机数生成器这些方面都有实打实的改进。如果你的项目要跑ThinkPHP、Laravel这些框架8.2的兼容性已经很成熟了。PHP 8.3其实也可以上但有些老框架的依赖还没完全跟上来我建议慎重一点。另外从PHP 7.4跨到8.x的朋友要特别注意很多老代码里的写法在8.x下会直接报错比如隐式类型转换、动态属性这些升级前至少要在测试环境跑一遍全量用例。MySQL这边我倾向于用官方MySQL 8.0而不是MariaDB。虽然MariaDB在很多云厂商的镜像里被当作默认替代而且它确实是MySQL的兼容分支但我们使用中遇到的一些老项目连MariaDB会有驱动兼容问题。MySQL 8.0的坑主要在认证插件上——它默认用caching_sha2_password而一些老版本PHP扩展不认识这个插件连接时直接报错。解决办法有两个要么PHP端确认用了mysqlnd驱动下文会细说要么在MySQL里给账号单独指定mysql_native_password认证方式。新部署环境直接用mysqlnd基本不会踩这个坑。Nginx选主线版而不是稳定版这一点可能有人不理解。Nginx的稳定版其实更保守而主线版已经足够稳定反而更快支持新特性。比如HTTP/3这些能力稳定版往往要等很久才合并。生产环境我用了好几年主线版没有因为版本问题翻过车。1.3 安装方式源码编译还是包管理器装LNMP有三种主流方式发行版包管理器直接装、下载官方预编译包、源码编译安装。我这次笔记以源码编译为主原因是它能让你清楚看到每个组件的依赖关系和安装路径。Nginx和PHP源码编译很快几分钟而已真正花时间的是解决编译依赖。MySQL我就不建议源码编译了编译一个MySQL要半小时以上而且很容易因为内存不足编译到一半被杀掉直接用官方提供的一键式二进制tar包即可解压就能用。用包管理器安装也有优势胜在省心升级方便依赖自动解决。CentOS 7自带的PHP版本太老需要额外引入EPEL或Remi源这又牵扯到源的信任问题。从学习深度来看源码编译一遍你以后看别人的部署脚本、Dockerfile都会轻松很多。注意本文所有路径和配置以源码编译安装为基准。如果你的环境是用yum apt装的配置文件的存放位置可能会不同但核心配置项思路完全通用。2. 环境准备系统依赖、目录规划与前置配置2.1 系统基础与编译依赖不管新机器还是老机器装系统时我都建议最小化安装不要带图形界面少一个包就少一个攻击面。系统装好后第一件事是同步时间和更新基础软件包时间不同步会影响日志排错和HTTPS证书校验。yum update -y yum install -y gcc gcc-c make autoconf wget tar编译Nginx需要pcre、zlib、openssl这几个库以及对应的开发包。pcre是Nginx重写模块和正则表达式的基础zlib是gzip压缩模块必须的openssl则是HTTPS的关键依赖。很多初学者编译Nginx报错八成就是缺了这些开发包报错信息里会明确提示缺什么头文件缺啥补啥就行。yum install -y pcre pcre-devel zlib zlib-devel openssl openssl-devel编译PHP的依赖更多一点除了解压、XML、curl这些基础库PHP 8.x还需要libxml2、sqlite、oniguruma这些开发包。oniguruma是mbstring正则表达式依赖的缺了它编译PHP时会在mbstring环节报错。CentOS 7自带的libzip版本较低PHP 8.2要求libzip 0.11版本以上如果你装了EPEL源一般能解决否则就得手动编一个新版libzip。yum install -y epel-release yum install -y libxml2-devel sqlite-devel libcurl-devel oniguruma oniguruma-devel libzip libzip-devel2.2 目录规划与账户管理LNMP的目录规划原则是软件和网站数据分离日志集中存放方便备份和排查。我的习惯是软件统一装到/usr/local下面网站根目录放/data/wwwroot日志放/data/logs。这样路径清晰也避免把网站文件散落在根目录的各个角落。mkdir -p /data/wwwroot /data/logs进程账户方面Nginx和PHP-FPM不要用root跑worker进程这是安全底线。web服务只需要读写自己的目录就够用了用root跑等于把整台服务器的权限都交给了web应用一旦代码有漏洞后果不堪设想。我创建一个专门的www用户不分配Shell也不能登录useradd -M -s /sbin/nologin wwwMySQL有自己的mysql用户安装包里一般会自动创建如果手动创建也按同样的原则来。这里有个容易忽略的点www用户需要能读取网站目录、能写日志目录但绝不能让它能写PHP文件。很多站点被入侵就是因为上传目录可写同时PHP解析又没做限制攻击者上传一个webshell后直接拿到PHP执行权限。2.3 防火墙与SELinux前置处理新机器上的防火墙默认是开着的如果不处理装好Nginx后会发现外网根本访问不了。CentOS 7用firewalld管理直接放行HTTP和HTTPS端口即可firewall-cmd --permanent --add-servicehttp firewall-cmd --permanent --add-servicehttps firewall-cmd --reload还有SELinux这个大坑。很多人部署不成功最后发现是SELinux拦着不让Nginx访问文件或网络。我见过太多案例配置全对但访问就是403或502关掉SELinux就一切正常。对新手来说最省事的办法是先把SELinux设为permissive等环境跑通了再决定要不要精细化策略。setenforce 0 sed -i s/^SELINUXenforcing/SELINUXpermissive/ /etc/selinux/config上面这条命令把当前环境临时放开同时把配置文件改成永久permissive。生产环境我还是建议留着SELinux并配置好策略但那是后话先把环境跑起来再说。因为SELinux策略配置本身又是一个知识体系混在部署笔记里会让你分不清到底是配置问题还是SELinux问题。3. 核心组件部署实操3.1 Nginx编译安装与systemd托管Nginx源码下载地址是http://nginx.org/download/版本号以官方为准。我这边以1.24.0为例来演示你如果拿到更新的稳定版路径结构是一样的。cd /usr/local/src wget http://nginx.org/download/nginx-1.24.0.tar.gz tar -xf nginx-1.24.0.tar.gz cd nginx-1.24.0 ./configure --prefix/usr/local/nginx --userwww --groupwww --with-http_ssl_module --with-http_v2_module --with-http_realip_module --with-http_stub_status_module --with-pcre --with-stream make -j$(nproc) make installconfigure的参数我逐个解释。--prefix指定安装目录后续所有Nginx文件都在这个目录下卸载也方便直接删目录就行。--user和--group指定worker进程以www身份运行。--with-http_ssl_module是HTTPS必需不编译这个模块后面配证书会直接报不支持ssl指令。--with-http_v2_module开启HTTP/2协议这个是现代站点性能优化的重要选项。--with-http_realip_module用于反向代理场景让后端拿得到客户端真实IP。--with-http_stub_status_module提供一个简易状态页后面做基础监控要用。--with-stream是支持TCP/UDP代理做负载分流时有用。make的-j参数指定并行编译线程数$(nproc)自动获取CPU核心数四核机器就四路并行编译速度快很多。装完后把Nginx注册成systemd服务方便开机自启和管理。创建一个单元文件cat /usr/lib/systemd/system/nginx.service EOF [Unit] Descriptionnginx - high performance web server Afternetwork.target [Service] Typeforking PIDFile/usr/local/nginx/logs/nginx.pid ExecStart/usr/local/nginx/sbin/nginx ExecReload/usr/local/nginx/sbin/nginx -s reload ExecStop/usr/local/nginx/sbin/nginx -s quit PrivateTmptrue [Install] WantedBymulti-user.target EOFNginx是master-worker模型master进程启动后fork出worker进程master本身常驻所以Type要写成forking并指定PIDFile路径。如果Type写错了systemd会认为启动失败实际进程却活着这是个很隐蔽的坑。启动并验证systemctl daemon-reload systemctl enable nginx systemctl start nginx curl -I http://127.0.0.1看到HTTP/1.1 200 OK就说明Nginx起来了。3.2 MySQL初始化与基础配置MySQL我用官方二进制tar包下载地址在https://dev.mysql.com/downloads/mysql/选Linux - Generic版本。这里要特别注意glibc版本老机器如果是CentOS 7需要选glibc2.12版本对应的包。cd /usr/local/src wget https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-8.0.36-linux-glibc2.12-x86_64.tar.xz useradd -M -s /sbin/nologin mysql tar -xf mysql-8.0.36-linux-glibc2.12-x86_64.tar.xz -C /usr/local/ mv /usr/local/mysql-8.0.36-linux-glibc2.12-x86_64 /usr/local/mysql mkdir -p /data/mysql-data chown -R mysql:mysql /usr/local/mysql /data/mysql-data写一份my.cnf这是MySQL的核心配置。数据目录放在/data/mysql-data而非默认的/var/lib/mysql为的是跟系统盘分离后面扩容迁移都方便。cat /etc/my.cnf EOF [mysqld] basedir/usr/local/mysql datadir/data/mysql-data socket/tmp/mysql.sock port3306 character-set-serverutf8mb4 collation-serverutf8mb4_unicode_ci pid-file/data/mysql-data/mysqld.pid log-error/data/mysql-data/error.log innodb_buffer_pool_size1G EOFinnodb_buffer_pool_size是InnoDB最重要的性能参数官方建议设为物理内存的60%到70%。1G这个值适合2G内存的机器如果你的机器内存大可以按比例往上调。socket路径写成/tmp/mysql.sock这是PHP连接MySQL的默认socket路径保持一致能避免很多连接报错。初始化数据目录是MySQL 8.0和5.7的分水岭老版本用mysql_install_db8.0直接用mysqld命令/usr/local/mysql/bin/mysqld --defaults-file/etc/my.cnf --initialize-insecure --usermysql--initialize-insecure会生成一个无密码的root账号方便首次登录。如果删掉insecure参数初始化完成后会把临时密码写进error.log你需要去日志里找出来才能登录。生产环境我建议用默认的--initialize拿随机密码测试环境用--initialize-insecure更省事。启动MySQL并设置开机自启cp /usr/local/mysql/support-files/mysql.server /etc/init.d/mysqld chmod x /etc/init.d/mysqld chkconfig --add mysqld service mysqld start第一次登录后立刻修改root密码/usr/local/mysql/bin/mysql -uroot ALTER USER rootlocalhost IDENTIFIED BY YourStrongPassword; FLUSH PRIVILEGES;别忘了把MySQL的bin目录加到PATH里不然每次都要敲全路径。在/etc/profile.d/mysql.sh里写一行export PATH/usr/local/mysql/bin:$PATH就行。3.3 PHP与PHP-FPM编译安装PHP编译安装是全流程中最容易出错的一环主要问题都集中在依赖缺失上。源码包从https://www.php.net/downloads下载以8.2.20为例cd /usr/local/src wget https://www.php.net/distributions/php-8.2.20.tar.gz tar -xf php-8.2.20.tar.gz cd php-8.2.20 ./configure --prefix/usr/local/php \ --with-config-file-path/usr/local/php/etc \ --with-mysqlimysqlnd \ --with-pdo-mysqlmysqlnd \ --with-fpm-userwww \ --with-fpm-groupwww \ --enable-fpm \ --enable-mbstring \ --enable-bcmath \ --enable-pcntl \ --enable-zip \ --with-curl \ --with-openssl \ --with-zlib make -j$(nproc) make install这些参数里--with-mysqlimysqlnd和--with-pdo-mysqlmysqlnd是重点。mysqlnd是PHP官方自带的MySQL驱动不需要额外装MySQL客户端库还能解决前面提到的认证插件兼容问题。--enable-fpm是编译PHP-FPM进程管理器Nginx解析PHP全靠它。--enable-mbstring开启多字节字符串支持现代PHP框架基本都依赖它。--enable-bcmath是精确计算扩展很多支付接口和数学运算类库需要。--with-fpm-user和--with-fpm-group指定FPM进程以www身份运行。编译完成后需要复制配置文件。PHP安装包提供了两个初始配置php.ini-production是生产环境推荐配置php.ini-development是开发环境配置。生产环境用前者然后把时区和上传大小等几个关键项改一下cp php.ini-production /usr/local/php/etc/php.ini sed -i s/;date.timezone /date.timezone Asia\/Shanghai/ /usr/local/php/etc/php.ini sed -i s/upload_max_filesize 2M/upload_max_filesize 20M/ /usr/local/php/etc/php.ini sed -i s/post_max_size 8M/post_max_size 20M/ /usr/local/php/etc/php.iniPHP-FPM有自己的配置目录cp /usr/local/php/etc/php-fpm.conf.default /usr/local/php/etc/php-fpm.conf cp /usr/local/php/etc/php-fpm.d/www.conf.default /usr/local/php/etc/php-fpm.d/www.conf关键的www.conf参数需要逐项调。listen指定PHP-FPM监听方式这里用Unix socket而不是TCP端口因为本机通信用socket比走网络协议栈更快还能避免端口被外部扫描到。listen.mode要设置好Nginx进程是www身份如果socket权限不对Nginx连不上它就一直502。pm参数决定PHP-FPM怎么管理子进程dynamic模式适合绝大多数场景按需创建子进程省内存。sed -i s/;listen.owner www/listen.owner www/ /usr/local/php/etc/php-fpm.d/www.conf sed -i s/;listen.group www/listen.group www/ /usr/local/php/etc/php-fpm.d/www.conf sed -i s/;listen.mode 0660/listen.mode 0660/ /usr/local/php/etc/php-fpm.d/www.conf sed -i s/pm.max_children 5/pm.max_children 50/ /usr/local/php/etc/php-fpm.d/www.conf sed -i s/pm.start_servers 2/pm.start_servers 5/ /usr/local/php/etc/php-fpm.d/www.conf sed -i s/pm.min_spare_servers 1/pm.min_spare_servers 5/ /usr/local/php/etc/php-fpm.d/www.conf sed -i s/pm.max_spare_servers 3/pm.max_spare_servers 35/ /usr/local/php/etc/php-fpm.d/www.confpm.max_children是PHP-FPM最多能同时处理的请求数设50意味着同时最多50个PHP进程。这个值不是越大越好每个PHP进程大约占20到50M内存50个进程最多可能吃掉2.5G内存小机器直接OOM。pm.max_requests1000推荐加上让每个子进程处理1000个请求后自动退出重建可以防止PHP代码里的内存泄漏长期累积。注册systemd服务PHP-FPM不像Nginx那样自带master管理脚本手动写单元文件cat /usr/lib/systemd/system/php-fpm.service EOF [Unit] DescriptionPHP-FPM Afternetwork.target [Service] Typeforking PIDFile/usr/local/php/var/run/php-fpm.pid ExecStart/usr/local/php/sbin/php-fpm ExecReload/usr/local/php/sbin/php-fpm -t ExecStop/bin/kill -QUIT $MAINPID PrivateTmptrue [Install] WantedBymulti-user.target EOF试试启动systemctl daemon-reload systemctl enable php-fpm systemctl start php-fpm ss -lntp | grep php-fpm看到php-fpm.sock文件出现在/tmp下就正常了。3.4 Nginx与PHP-FPM联调Nginx和PHP都装好之后真正让它们协同工作的是Nginx的server块配置。我在/data/wwwroot下建一个测试站点目录写入一个phpinfo文件用来验证整条链路是否通。mkdir -p /data/wwwroot/default cat /data/wwwroot/default/index.php EOF ?php phpinfo(); EOF chown -R www:www /data/wwwroot/default然后编辑Nginx配置在/usr/local/nginx/conf/nginx.conf的http块里加一段server配置server { listen 80; server_name localhost; root /data/wwwroot/default; index index.php index.html; access_log /data/logs/default_access.log; error_log /data/logs/default_error.log; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass unix:/tmp/php-cgi.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } }这里最关键的配置有两点。第一root用绝对路径指向网站目录它的层级要和文件实际所在位置完全一致。第二fastcgi_pass用的是unix:/tmp/php-cgi.sock这必须和PHP-FPM的listen路径一致。很多502问题的根源就是这两个地方对不上。SCRIPT_FILENAME这一行的$document_root$fastcgi_script_name写法也很重要。它告诉PHP-FPM要执行哪个文件。如果这里写死成某个绝对路径比如/data/wwwroot$fastcgi_script_name一旦server块里改了root目录所有站点都会去老的路径找文件结果就是404。还要把Nginx默认的server块删掉或注释掉避免默认配置干扰。打开配置文件把example.com相关那段注释掉然后先测配置再重载/usr/local/nginx/sbin/nginx -t /usr/local/nginx/sbin/nginx -s reload浏览器或curl访问http://服务器IP/index.php如果能看到PHP信息页面就说明Nginx到PHP-FPM的链路已经通了。此时在phpinfo的输出里检查几个关键项Server API是FPM/FastCGIPDO drivers里面要有mysqlmysqli支持是enabled。4. 常见问题与排查技巧实录4.1 502 Bad Gateway的排查流程502是LNMP环境里出现频率最高的错误它的含义是Nginx作为网关没能从上游服务PHP-FPM拿到有效响应。我的排查顺序固定是先看PHP-FPM进程在不在再看socket路径对不对最后看PHP-FPM日志。第一步确认PHP-FPM进程存活ps aux | grep php-fpm如果master进程看不到说明PHP-FPM挂了。此时看错误日志/usr/local/php/var/log/php-fpm.log一般会给出具体原因比如内存不足被杀死、配置语法错误启动失败。第二步确认socket文件路径一致。分别看PHP-FPM的listen配置和Nginx的fastcgi_pass配置grep listen /usr/local/php/etc/php-fpm.d/www.conf grep fastcgi_pass /usr/local/nginx/conf/nginx.conf两者必须完全一致。我最开始部署时犯过低级错误PHP-FPM监听的是/dev/shm/php-cgi.sock内存盘而Nginx配的是/tmp/php-cgi.sock结果是连不上。这种问题查多久都发现不了。第三步看socket文件权限。用ll /tmp/php-cgi.sock查看属主属组如果socket文件的owner和group不是www或者权限位没有0660Nginx的www进程就无法访问。前面在www.conf里配置的listen.owner、listen.group、listen.mode就是干这个用的。注意有时候502是PHP执行超时导致PHP-FPM主动断开连接。如果日志里频繁出现request execution timed out需要调大max_execution_time或者检查代码里是否有死循环和慢查询。4.2 404与PHP文件不执行问题404的排查比502稍微绕一点。常见的原因是try_files配置错误。try_files的语法是try_files $uri $uri/ /index.php?$query_string意思是先找真实文件找不到就把请求重写到index.php入口。Typo或者漏写这个指令访问带路由的URL就会直接404。另一个隐蔽的404原因是index指令没配置。如果访问目录根路径比如http://ip/Nginx需要知道默认找哪个文件。index配置成index index.php index.htmlNginx才会去目录下找index.php。如果index里没有phpNginx会去找index.html找不到就直接返回403或404了。还有一种情况PHP文件明明存在访问却404。这时要检查root路径是否带文件实际位置。有个很经典的错误是root写成/data/wwwroot而实际文件在/var/www/html路径对不上自然404。用nginx -T可以输出完整的解析后配置看每个server块实际生效的root是什么。4.3 MySQL连接报错与认证插件问题PHP连MySQL最常见的两类错误一是Cant connect to local MySQL server through socket /tmp/mysql.sock二是Unable to connect带SQLSTATE[HY000]。第一类是socket路径不一致。PHP默认用/tmp/mysql.sock连接本地MySQL如果你在my.cnf里把socket改到了比如/var/lib/mysql/mysql.sock但PHP这边不知道就会去连一个不存在的路径。解决办法是统一socket路径要么都放/tmp要么都放/var/lib/mysql。我习惯是固定用/tmp/mysql.sock简单直接。第二类需要检查MySQL账户认证方式。MySQL 8.0默认caching_sha2_password老版本PHP的pdo_mysql如果没走mysqlnd就会连接失败。在MySQL里确认一下mysql -uroot -p SELECT host, user, plugin FROM mysql.user;如果某个账户的plugin不是mysqlnd能识别的可以这样切换ALTER USER applocalhost IDENTIFIED WITH mysql_native_password BY YourPassword; FLUSH PRIVILEGES;新环境用PHP 8.2配mysqlnd驱动的话caching_sha2_password是支持的大概率不会有这个问题。老环境升级就要重点检查这里。4.4 SELinux和权限校验问题SELinux引发的故障最迷惑人因为表面上所有配置都正确但服务就是不合预期。Nginx版本常见的SELinux阻塞场景是反向代理模式需要放行网络连接。PHP-FPM连接MySQL时也可能被SELinux策略挡住表现为PHP代码里面执行数据库查询就超时或报权限错误而命令行测试却正常。排查SELinux是否在拦截可以先看审计日志grep nginx /var/log/audit/audit.log | tail -20 grep php-fpm /var/log/audit/audit.log | tail -20如果看到类denied记录基本就是SELinux拦截了。临时放行网络连接setsebool -P httpd_can_network_connect on这里-P参数是持久化配置不加重启后失效。还有一个容易混淆的是文件权限问题。PHP-FPM以www用户运行Nginx的worker进程也是www用户但网站文件如果是从其他机器同步过来的owner可能是别的用户。用ls -l检查一下网站目录的owner和group如果不是www或者目录权限没有读和执行位就会出现莫名其妙的白屏或权限错误。统一授权chown -R www:www /data/wwwroot find /data/wwwroot -type d -exec chmod 755 {} \; find /data/wwwroot -type f -exec chmod 644 {} \;这里要注意任何上传目录都不能写成755至少要细分到某个子目录并禁止执行PHP。这是安全底线。5. 安全加固与日常维护经验5.1 目录权限与PHP禁用函数LNMP跑通之后第一件事不是部署业务代码而是做安全加固。前面说过的目录权限是第一步接着要处理PHP的禁用函数。老生常谈的禁用列表包括exec, shell_exec, system, passthru, popen, proc_open, dl这些它们允许PHP代码直接执行系统命令一旦代码里存在命令注入漏洞攻击者就能借此直接拿shell。但这里有个实操层面的细节Composer和Symfony这类开发工具在部署时要用到proc_open函数如果禁用了composer install会直接失败。我的方案是生产环境的php.ini里禁用这套危险函数而开发环境或CI服务器用单独的配置不禁用。如果业务代码确实需要执行外部命令比如调用ffmpeg处理视频那么更安全的方式是用supervisor单独管理这些命令行任务而不是让PHP直接通过exec去触发。还要把expose_php和server_tokens关掉隐藏PHP和Nginx的版本号。攻击者扫描到具体版本号之后可以直接去漏洞库匹配已知漏洞暴露在公网的机器尤其要注意。sed -i s/expose_php On/expose_php Off/ /usr/local/php/etc/php.iniNginx则在http块里加server_tokens off;这样HTTP响应头里不再带版本号。5.2 MySQL最小权限与备份MySQL的账号管理要遵守最小权限原则。部署应用时不要直接用root连接数据库而是创建一个专用的应用账号只能操作它自己需要的那几个库。比如CREATE USER applocalhost IDENTIFIED BY AppStrongPassword; GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO applocalhost; FLUSH PRIVILEGES;这样即使应用被拖库攻击者最多只能操作appdb这一个库动不了系统表和其他库。如果应用只需要读写某个库连DDL权限都不给防止恶意SQL语句执行建表删表操作。备份这件事我踩过很多次坑才养成习惯。备份策略其实不复杂就是每天全量mysqldump加每小时的binlog增量。全量备份命令/usr/local/mysql/bin/mysqldump --defaults-file/etc/my.cnf -uroot -p --single-transaction --master-data2 --routines --triggers --all-databases | gzip /data/backup/mysql_$(date %F).sql.gz--single-transaction用于InnoDB表可以在备份时不锁表业务无感知。--master-data2会记录binlog位置做增量恢复的时候靠它定位。配合crontab定时执行保留最近7天的备份这个量级足够应对绝大多数故障场景。5.3 日志轮转与基础监控日志不处理会写满磁盘这是运维事故的高发原因之一。Nginx的access_log和error_log默认无限增长MySQL的慢查询日志也一样。系统自带的logrotate可以配置轮转策略cat /etc/logrotate.d/nginx EOF /data/logs/*.log { daily missingok rotate 30 compress delaycompress notifempty create 640 www www sharedscripts postrotate [ -f /usr/local/nginx/logs/nginx.pid ] kill -USR1 cat /usr/local/nginx/logs/nginx.pid endscript } EOF这里有个关键点日志轮转之后Nginx的文件句柄还指向旧日志文件必须发送USR1信号让Nginx重新打开日志文件否则轮转不生效还白白占磁盘。用systemd管理Nginx的话postrotate里的kill命令就是给nginx进程发信号不是直接杀掉进程。基础监控我用Nginx自带的stub_status模块就够。在server块里加一个locationlocation /nginx_status { stub_status on; access_log off; allow 127.0.0.1; deny all; }访问/nginx_status能看到当前活跃连接数、总共接受的请求数、正在读写的连接数。配合crontab定时curl这个页面并把结果写入文件就能做趋势分析判断流量增长情况。MySQL的慢查询日志也要记得开超过1秒的查询记录下来定期分析有没有缺少索引的僵查询。我个人在实际操作中还有一个省心的体会整套部署流程走熟了之后强烈建议整理成自动化脚本。从系统依赖到三个组件的编译安装、配置文件生成全部写成bash脚本变量抽出来以后新机器到手跑一遍就能交付。这样既不会漏步骤也方便团队其他人复用。有人会觉得手动敲一遍才记得牢但脚本化并不是让你不记原理而是把已经验证过的决策固化下来避免每次部署都因为手误而出幺蛾子。你现在手搓一遍理解了每行配置的作用再把它写成幂等脚本两种能力就都有了。