
最近给一台全新的 Ubuntu 24.04 LTS 做了一次完整的环境部署从裸系统一路装到 MySQL、Redis、Nginx 三件套全部跑通业务服务再把自动备份补上。这套流程我前前后后走过很多遍每次都能踩出一两个新坑这次索性把所有操作和踩坑记录整理出来。如果你正准备在 Ubuntu 上装数据库、缓存和 Web 服务或者想要一套能真正落到自动备份的部署方案这篇文章可以直接当操作手册参考。我会按真实部署的顺序走环境准备、MySQL、Redis、Nginx、备份、排错每一步尽量说清楚为什么这么做而不只是贴命令。中间会穿插一些我在生产环境里常用的参数和容易出错的地方。跟那些一句句抄官方文档的教程不一样这篇更接近我自己的实操笔记适合有一定 Linux 基础、想少走弯路的读者。1. 部署前必做的三步系统更新、SSH 保障、目录规划很多教程上来就让你apt install mysql-server装完再说。但实际上部署前有几步准备工作能帮你省掉后面一大半麻烦。1.1 选对 Ubuntu 版本这里说的就是长期支持版本。我这次用的是 Ubuntu 24.04 LTS目前 apt 源里带的 MySQL 是 8.0 系列、Redis 是 7.x、Nginx 是 1.24 左右都是比较顺手且稳定的版本。如果你还在用 20.04 或更老的版本建议别折腾升级路线了趁着重装直接上 24.04。我的建议只有一条生产环境无脑选当前最新的 LTS普通项目里用开发版意义不大。另外安装时我选了最小安装Minimal不带图形界面、不带 LibreOffice 这些吃资源的组件后面所有东西都是命令行操作。1.2 换源、更新、安装基础工具国内服务器普遍会做一步换源操作这里就不展开具体源地址了网上搜Ubuntu 换源能找到一堆原理是把/etc/apt/sources.list.d/ubuntu.sources里的下载地址改成离自己近的镜像站。换完源以后执行sudo apt update sudo apt upgrade -y这一步必须做。我见过太多人新系统没更新直接装 MySQL结果装出来版本老或者依赖缺胳膊少腿后面排查起来极其痛苦。顺手把常用工具装了sudo apt install -y curl wget git vim net-tools lsof unzip这几个工具在排查问题时基本都会用到。尤其是lsof和net-tools后面查端口、查连接状态都靠它们。1.3 目录规划一切备份的基础强烈建议从第一天就规划好目录而不是让文件散落在各处。我习惯用这套结构/opt/backup ├── mysql ├── redis └── nginx备份脚本里的路径全部引用这几个目录后续配合 cron 做定时任务也清晰。顺便说一句很多人重装系统后数据全没了根源不是没装备份工具而是从第一天就没想过数据是落在哪个目录里。把备份目录当成应用的一部分来规划比任何备份工具都有效。网络热词里有“ubuntu系统重装”我多提一句如果你经历过一次系统重装就知道上面这几句话有多重要了。2. MySQL 8.0从 apt 安装到 root 认证方式调整MySQL 这一块坑比较多尤其是 Ubuntu 上默认的 root 认证方式跟很多人的习惯完全不同。我会从安装开始把整个过程拆开讲。2.1 安装与首次登录的 auth_socket 机制Ubuntu 24.04 上装 MySQL 特别简单sudo apt install -y mysql-server装完以后系统会自动把 MySQL 8.0 启动起来并且注册为 systemd 服务。然后问题来了你执行mysql -u root -p想登录发现密码怎么都是错的。这是因为 Ubuntu 仓库里 MySQL 的 root 默认走的是auth_socket插件不使用密码认证而是校验当前 Linux 系统用户。所以正确的首次登录方式是sudo mysql直接以 root 权限登录不需要密码。进去以后第一件事就是查一下当前认证方式SELECT user, host, plugin FROM mysql.user WHERE userroot;看到 root 的 plugin 是auth_socket就理解了为什么密码登录进不去。如果你想改成常规的密码登录——大多数场景都建议改掉因为后面的自动化脚本、备份脚本都不太好处理 socket 认证——需要执行ALTER USER rootlocalhost IDENTIFIED WITH caching_sha2_password BY 一个足够强的密码; FLUSH PRIVILEGES;之后再用mysql -u root -p就能进去了。2.2 安全加固与业务账号创建的标准流程MySQL 安装完默认是能用就行的状态端口暴露、空密码、测试库都还在。我建议跑一遍官方自带的安全脚本sudo mysql_secure_installation按提示设置密码安全等级、删除匿名用户、禁用 root 远程登录、删除 test 库。这些选项全部选 yes 就行。然后是创建业务账号。这里有个原则业务代码里用的账号权限最小化不要用 root。我一般这么做CREATE USER appuserlocalhost IDENTIFIED BY AppUser2024; CREATE DATABASE IF NOT EXISTS appdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; GRANT ALL PRIVILEGES ON appdb.* TO appuserlocalhost; FLUSH PRIVILEGES;注意几个细节字符集显式指定utf8mb4MySQL 8.0 默认就是这个但建库时写清楚不会出错。账号 host 用localhost表示只允许本机连接。如果应用也在这台机器上localhost 就够了不需要开网络端口。如果你非要让 MySQL 监听在 0.0.0.0 上让别的机器连记得去改/etc/mysql/mysql.conf.d/mysqld.cnf里的bind-address同时确认防火墙放行 3306 端口。但我的态度是能不开远程就不开实在要开会加上防火墙白名单。2.3 内存与连接数的第一轮调优MySQL 装完以后配置文件在/etc/mysql/mysql.conf.d/mysqld.cnf。这一步不是说让你盲目追参数而是根据自己的机器配置给一个合理的起点。我常用的一套基础调优[mysqld] innodb_buffer_pool_size 2G max_connections 300 log_bin /var/log/mysql/mysql-bin.log expire_logs_days 7 max_binlog_size 100M character_set_server utf8mb4 slow_query_log 1 slow_query_log_file /var/log/mysql/slow.log long_query_time 2innodb_buffer_pool_size是 InnoDB 的缓存池经验值是物理内存的 50%~70%。机器内存 8G 的话设置为 4G~5G。设置太大导致系统 SWAP 频繁反而得不偿失。max_connections不要一味加大300~500 对绝大多数中小项目足够。如果应用经常报连接数超限优先排查代码里的连接池配置而不是调大 MySQL 数字。慢查询日志我在开发环境就会打开long_query_time设置为 2 秒方便尽早发现索引失效的 SQL。改完配置记得重启sudo systemctl restart mysql2.4 MySQL 排错的日志入口MySQL 的日志目录是/var/log/mysql/错误日志默认在/var/log/mysql/error.log。服务起不来、密码校验失败这类问题先看这里的报错sudo tail -100 /var/log/mysql/error.log很多人在这一项上踩坑改完配置重启 MySQL 失败结果不去看日志反复试配置文件里的参数错过了错误日志里明明白白写着的错误原因。这一步是我在排错链路里最先走的一步。3. Redis 7.x缓存服务落地配置与分布式锁常识Redis 安装也简单但真正要让它安全稳定地跑在生产环境有几个配置不是默认值就能用的必须自己调。这一节我按安装-配置-进阶认知的顺序来。3.1 apt 装 Redis 和源码编译到底选哪个Ubuntu 24.04 上直接apt install redis-server装出来就是 Redis 7.x质量没问题也自动带了 systemd 服务单元。这个方式对绝大多数场景足够我不太建议一上来就源码编译。源码编译适合什么情况呢你需要特定版本特性或者你要魔改 Redis 源码的时候。日常使用apt 版本的构建参数和你自己编译的差别不大省时省力。别忘了手动启动服务sudo systemctl enable --now redis-server3.2 必须动的那几行 redis.confRedis 的配置文件在/etc/redis/redis.conf。强烈建议打开文件逐项确认下面这些bind 127.0.0.1 protected-mode yes port 6379 requirepass 你的强密码 maxmemory 512mb maxmemory-policy allkeys-lru appendonly yes appendfsync everysecbind 和 protected-mode如果 Redis 只给本机应用用bind 127.0.0.1就够了别暴露到公网。这是云上服务器被挖矿木马盯上的重灾区。requirepass生产环境必须设置。设置后客户端连接都要带密码redis-cli -a 密码 ping可以快速验证。maxmemory maxmemory-policy这是缓存最重要的两个参数。maxmemory限制 Redis 最多用多少内存maxmemory-policy决定内存满了以后怎么淘汰。allkeys-lru是全局 LRU 淘汰适合大多数缓存场景如果是做只淘汰设置了过期时间的 key这种业务可以用volatile-lru。appendonly yes开启 AOF 持久化配合appendfsync everysec每秒刷盘一次这是性能和持久化之间的平衡点。只做缓存、丢了也无所谓的场景可以只保留默认的 RDB 快照。改完重启sudo systemctl restart redis-server3.3 分布式锁最小实现与单点局限很多项目用 Redis 做分布式锁。热词里也有“redis 分布式锁”我展开讲一下最小实现。获取锁的正确姿势是 SET 命令加上 NX 和 PXSET lock:order:10001 uuid-xxxx NX PX 30000NX只在 key 不存在时才设置成功。PX 30000锁的自动过期时间 30 秒防止持有锁的进程挂掉导致死锁。value 用唯一标识比如 UUID释放锁时比对防止误删别人的锁。释放锁的推荐写法是用 Lua 脚本保证原子性if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end必须明确一点这种单实例分布式锁存在主从切换时锁丢失的问题极端情况下会破坏互斥性。严格来说可以用 Redisson 的实现或者权衡后换成 etcd 这类强一致组件。但对大多数内部系统上面的最小实现已经能挡住 99% 的并发问题了。你要清楚它的边界别把它当万能方案。3.4 缓存治理与内存淘汰策略的经验参数热词里有redis缓存治理我说两个最常见场景的经验参数。一个是缓存穿透。查询一个不存在的 key每次都会打到数据库。最简单的方案是在代码里把空值也缓存起来TTL 短一些比如 60 秒。另一个是被频繁暴力访问的 key可以考虑用布隆过滤器提前拦截不存在的数据但这个工程量就大了做之前先评估值不值。另一个是缓存雪崩。大量 key 在同一时间过期请求集体落到数据库。解决办法挺无脑过期时间加一个随机偏移量比如TTL 基准时间 random(0, 300)秒把过期时间打散。Redis 这个环节我强烈建议你两个都做一是把慢日志调低阈值二是开启监控。Redis 自带SLOWLOG GET可以看慢命令INFO命令可以看内存、客户端连接数别等告警响了你还没头绪。4. Nginx多站点、反向代理与 location 匹配优先级Nginx 是这套环境里配置最灵活、也最容易让人懵的部分。我会从配置文件结构讲起再到实际站点怎么配最后把 location 匹配规则彻底说清楚。4.1 配置文件结构梳理sites-available 与 sites-enabledNginx 安装sudo apt install -y nginxUbuntu 的 Nginx 配置结构和 CentOS 不太一样它用 sites-available 存站点配置、sites-enabled 存启用配置本质是软链接。我从来不动/etc/nginx/nginx.conf里的主配置只在 sites-available 下新建文件然后ln -s到 sites-enabled。创建一个站点sudo vim /etc/nginx/sites-available/mysite sudo ln -s /etc/nginx/sites-available/mysite /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx每次改完配置nginx -t检查语法是肌肉记忆。这个命令会告诉你是少了分号还是路径写错了省掉很多无谓的 restart。4.2 开发机多端口多站点的配置方式热词里有“本地虚拟机 多端口nginx 开发环境多站点自定义域名配置”这个场景很常见。你在一台 Ubuntu 上要同时跑好几个项目又不想给每个项目配一个域名就可以让不同项目监听不同端口。server { listen 8081; server_name project1.local; root /var/www/project1; index index.html; } server { listen 8082; server_name project2.local; root /var/www/project2; index index.html; }然后配合/etc/hosts做自定义域名解析127.0.0.1 project1.local 127.0.0.1 project2.local这样浏览器里访问http://project1.local:8081就能命中第一个项目。端口和域名分开理解就行端口决定请求进哪个 server 块server_name决定匹配哪个域名。开发环境用这种方式比在 nginx.conf 里堆一堆 if 判断干净太多了。4.3 反向代理环境的搭建Host 头与超时参数Nginx 做反向代理是后端接口服务和外部访问之间的接线员。最基本的代理配置长这样server { listen 80; server_name api.example.com; 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_set_header Host $host。如果不设置这一行后端的 Web 应用拿到的 Host 头可能变成127.0.0.1:8080很多框架在做 URL 跳转、CSRF 校验时会直接出错。另外一个隐蔽的坑是超时。默认情况下 Nginx 的proxy_read_timeout是 60 秒如果你的后端接口有长任务比如导出报表客户端会看到 504。根据业务情况调大一点proxy_connect_timeout 10s; proxy_read_timeout 120s; proxy_send_timeout 120s;4.4 location 匹配优先级的实际验证location 是 Nginx 最常出问题的地方我干脆把匹配规则在这里说透。Nginx 的 location 匹配优先级从高到低大致是这样类型写法优先级精确匹配location /image最高前缀匹配带 ^~location ^~ /static/其次正则匹配location ~ \.php$再次普通前缀匹配location /static/最低规则理解起来很简单先看有没有精确匹配有就直接用。再看有没有带^~的前缀匹配有且匹配到就停下来。然后按顺序扫描正则第一个匹配到的正则生效。如果正则都没有匹配到才使用最长普通前缀匹配。举一个实际例子。你配了这两个location /api/ { proxy_pass http://backend1; } location ~* ^/api/v2/ { proxy_pass http://backend2; }请求/api/v2/users时普通前缀/api/能匹配但因为是正则先命中^/api/v2/实际会转发到 backend2。如果你没预期到这个行为就会看到后端地址莫名其妙跟配置对不上。想验证 location 规则对不对可以在error.log里开启 debug 级别查看匹配过程也可以直接访问后再对照后端日志。4.5 给代理服务注入 API Key 与 HTTPS 证书错误热词里有“nginx 代理 ollama 设置apikey”这个场景大概是有一台本地的 AI 推理服务希望统一通过 Nginx 入口访问并在代理层注入鉴权信息。最简单的实现是location /v1/ { proxy_pass http://127.0.0.1:11434; proxy_set_header Authorization Bearer sk-换成你自己的key; }这样客户端跟 Nginx 通信时不用关心下游服务需要什么鉴权Nginx 统一把 Authorization 头塞进去。但是要提醒一句把 key 写死在 nginx 配置文件里不够安全尤其是配置目录被开发机同步到代码仓库时key 就等于直接泄露了。更稳妥的做法是作为环境变量注入或者用 Lua 模块从外部读取。另外热词里还有一个很典型的 HTTPS 报错net::ERR_CERT_COMMON_NAME_INVALID。这通常是证书里的域名和实际访问域名不一致导致的。排查思路就三步确认server_name写的是不是访问时的域名。确认证书的 CN 或 SAN 包含这个域名。如果用了自签名证书浏览器会继续报警告那就要在客户端手动信任 CA或者换正规 CA 签发的证书。5. 自动备份脚本、定时任务与恢复演练自动备份是全套部署流程里最容易被忽略、出事时最要命的一环。我的原则是宁可服务少装一个备份也必须先跑通。5.1 备份清单与备份脚本的整体结构我需要备份的东西有哪些MySQL 全量数据、Redis 的持久化文件、Nginx 配置和站点文件。第一版备份脚本我放在/usr/local/bin/backup_all.sh结构大致这样#!/bin/bash set -e BACKUP_BASE/opt/backup DATE$(date %F_%H%M%S) LOG/var/log/backup.log # MySQL 备份 mkdir -p $BACKUP_BASE/mysql mysqldump --single-transaction --quick --routines --triggers \ -u root -p密码 appdb | gzip $BACKUP_BASE/mysql/appdb_$DATE.sql.gz # Redis 备份 mkdir -p $BACKUP_BASE/redis redis-cli -a 密码 BGSAVE sleep 2 cp /var/lib/redis/dump.rdb $BACKUP_BASE/redis/dump_$DATE.rdb # Nginx 配置备份 mkdir -p $BACKUP_BASE/nginx tar czf $BACKUP_BASE/nginx/nginx_conf_$DATE.tar.gz /etc/nginx几点说明mysqldump的--single-transaction参数很关键它利用 InnoDB 的一致性快照在备份时不锁表可以在生产环境在线备份。注意前提是你所有表都是 InnoDB如果混用了 MyISAM这部分还是要锁表的。Redis 备份前先执行BGSAVE能让 RDB 文件落到 disk 上再复制比直接复制当前内存状态更干净。脚本里set -e表示任一环节出错就停止避免 MySQL 备份失败了后面的 Redis 还继续执行这种半吊子状态。首次运行前先chmod x并手动执行一次确认没有报错再挂 cron。5.2 mysqldump 与 Redis RDB 的备份细节MySQL 备份时有两个额外建议第一个是备份账号别用 root。可以新建一个只读账号CREATE USER backuplocalhost IDENTIFIED BY BackupPss; GRANT SELECT, SHOW VIEW, PROCESS, RELOAD, LOCK TABLES, REPLICATION CLIENT ON *.* TO backuplocalhost;其中PROCESS权限是 mysqldump 执行一致性快照时需要的REPLICATION CLIENT用于查看 binlog 位置信息。缺了会报权限错误。第二个是如果数据库体量超过 5GBmysqldump逻辑备份的速度和恢复速度都会很慢这时候建议上物理备份方案比如 Percona XtraBackup 或直接对数据目录做 LVM 快照。逻辑备份适合中小库物理备份适合大库这个分界线你要心里有数。Redis 的小细节是备份dump.rdb前要确认 Redis 的持久化目录实际在哪。默认是/var/lib/redis/dump.rdb但如果你改过dir配置以实际路径为准。5.3 cron 定时任务与保留策略定时任务没什么花哨的直接写进 root 的 crontabsudo crontab -e加一行0 3 * * * /usr/local/bin/backup_all.sh /var/log/backup.log 21表示每天凌晨三点执行一次。选凌晨是因为这个时间业务访问量最低MySQL 备份和 Redis BGSAVE 对性能的影响最小。如果你的业务 24 小时都有流量也可以选凌晨 4 点自己权衡。保留策略也很重要只留最近 30 天的备份。在脚本末尾加一句find $BACKUP_BASE/mysql -name *.sql.gz -mtime 30 -delete find $BACKUP_BASE/redis -name *.rdb -mtime 30 -delete find $BACKUP_BASE/nginx -name *.tar.gz -mtime 30 -delete这一步是防止备份文件无限积累把磁盘塞满。备份本身占空间不清理的话总有一天会反过来把系统搞崩。5.4 恢复演练备份没验证过等于没有备份这一节是全文我最想强调的内容。很多人的备份流程是脚本能跑、文件能生成但等到真出事要恢复时发现 SQL 文件是坏的或者导入时报编码错误。所以一定要做恢复演练。我自己的习惯是每周选一台开发机把最新备份还原一次gunzip /opt/backup/mysql/appdb_最新.sql.gz | mysql -u root -p密码 -D test_restoreRedis 的恢复更简单停掉 Redis把备份的 dump.rdb 复制到数据目录启动服务然后redis-cli DBSIZE看数据量对不对。恢复演练这块钱省不了。你至少得知道从备份到恢复这条链路是通的否则备份只是给你一个心理安慰。另外如果条件允许可以把备份增量同步到另一台机器或外部存储上防止整台服务器物理损坏后所有数据一起消失。rsync就能干这个事这里不展开但方向是对的。6. 部署过程中的高频报错与排查链路最后给你梳理一下我从这套部署流程里实际遇到过的、以及在各种社区里高频出现的报错。排查思路比直接给答案重要。6.1 服务起不来journalctl 是第一步拿到服务起不来这个话题我第一反应永远是看日志。注意 systemd 管理下的 MySQL、Redis、Nginx 都统一用 journal 收集日志sudo journalctl -u mysql --since 10 minutes ago sudo journalctl -u redis-server --since 10 minutes ago sudo journalctl -u nginx --since 10 minutes ago常见故障举例MySQL 启动失败日志提示Cant open file大概率是数据目录权限被改坏了执行chown -R mysql:mysql /var/lib/mysql。Redis 启动失败日志提示Cant open the log file说明日志目录不存在mkdir -p /var/log/redis并改所属用户。Nginx 启动失败nginx -t会直接告诉你哪个文件第几行有问题照着改就行。6.2 端口冲突与防火墙的排查顺序端口被占用这种问题别去猜直接用命令查sudo lsof -i :6379 sudo netstat -tlnp | grep 3306常见的场景是你自己手动装了一个 MySQL之后用 apt 又装了一个结果两个进程抢同一个端口。查出来以后要么停掉多余的服务要么改其中一个端口。防火墙这块Ubuntu 默认用的 ufw。先看状态sudo ufw status如果 ufw 是开启的记得放行你需要的端口sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw allow from 你的内网网段 to any port 3306云服务器的话还要额外去安全组放行端口。本地虚拟机环境就没有这一步很多人忘了外部安全组这个概念导致本机一切正常、外部访问死活不通。6.3 误改环境变量后如何紧急恢复热词里有ubuntu环境变量配置错误这个我在自己机器上也发生过。改错了~/.bashrc里的PATH结果所有命令都提示command not found。紧急自救的方法是不要慌直接用绝对路径执行命令。/bin/echo $PATH /usr/bin/export PATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin恢复以后再去修~/.bashrc把写错的那行改回来。别在 PATH 配置里留空格也别写不存在的目录。如果是改坏了/etc/environment同理用sudo /usr/bin/vim /etc/environment去修。6.4 我踩过的几个隐蔽坑最后说几个不容易搜到答案的坑都是这次部署时的血泪经验。第一个是 MySQL 的caching_sha2_password认证方式。MySQL 8.0 默认是这个认证插件但老版本的客户端驱动可能不支持。现象是应用连 MySQL 报Authentication plugin caching_sha2_password cannot be loaded。解决办法有两个升级客户端驱动或者把账号认证方式改成mysql_native_password。我建议优先升级驱动因为mysql_native_password在 MySQL 8.4 里已经标记为废弃了。第二个是 Redis 的tcp-backlog。高并发环境下如果客户端连接大量失败日志里可能会看到Tcp backlog threshold相关提示需要同时调整 Redis 的tcp-backlog参数默认 511和系统内核的somaxconn参数。先把系统参数调大再改配置sudo sysctl -w net.core.somaxconn1024不过这是压测到一定量级才会遇到的事小项目不用管。第三个是 Nginx 的proxy_pass结尾斜杠问题。proxy_pass http://127.0.0.1:8080和proxy_pass http://127.0.0.1:8080/差别很大带斜杠时location 匹配到的前缀会被替换掉不带斜杠时会把完整原始 URI 透传过去。举个例子location /api/匹配/api/user不带斜杠会转发/api/user带斜杠则会在转发时把/api前缀去掉只转发/user。这个行为如果不测试上线后简直是灾难。每次配完代理建议用一个真实请求跑一遍确认 URI 是符合预期的。整套流程走完MySQL 在跑、Redis 在跑、Nginx 在跑备份也在每天凌晨稳定执行。从零到一部署这套环境真正花时间的不是那几条安装命令而是理解每个服务为什么这么配、出了问题去哪查。希望这篇记录能帮你在 Ubuntu 部署路上少踩几个我踩过的坑。