手头这台Debian 10服务器从2019年跑到现在,业务没怎么动过,但OpenSSL、PHP这类基础组件已经很旧了,安全更新也早已停止。折腾升级这事拖了很久,直到上周才真正动手,目标是从Debian 10一路升到Debian 13。
最开始我也想过偷懒,把sources.list直接改成Debian 13的代号,然后一条apt full-upgrade收工。好在动手前认真翻了Debian官方的升级文档,才意识到这个想法有多危险。Debian的升级机制其实很讲究,只保证相邻大版本之间的迁移路径可靠,10到11、11到12、12到13,每一步都有各自的依赖变迁和配置兼容要求,跳级操作大概率会把系统搞进依赖地狱。
这篇就把我完整的升级过程和踩坑记录整理出来,内容包括升级前必须做的准备、每一跳的具体命令与验证方式、以及升级过程中最常见的几个翻车点。适合服务器在跑老版本Debian、又不敢贸然重装系统的朋友参考,也能帮准备升级自己桌面环境的同学避掉一些明显的坑。
1. 为什么Debian升级不能跳版本:三跳升级的底层逻辑
1.1 Debian的版本代号与相邻升级策略
Debian每两年左右发布一个新的大版本,每个版本都有自己的代号。Debian 10叫Buster,Debian 11叫Bullseye,Debian 12叫Bookworm,Debian 13叫Trixie。官方只支持从当前版本升级到相邻的下一版本,比如10升11、11升12、12升13,每一跳都有对应的Release Notes和升级指南。
这背后的原因不复杂。Debian的包管理器apt在解析依赖时,依赖的是每个版本仓库中已有的元数据和包版本关系。一个大版本发布时,包维护者只针对"从上一个版本升级过来"这个路径做了完整的兼容性测试。换句话说,Debian 11的软件包在设计时就考虑了从Debian 10的包状态正确过渡;但Debian 13的包不会考虑如何从Debian 10的状态直接迁移。
如果把源直接指向更远的版本,apt就会拿到一大串无法满足的依赖关系:有的包要求新版库文件、有的包被改名或拆分、有的包因为依赖的运行时环境差异导致配置无法兼容。系统通常不会立即崩溃,但升级到一半出现"软件包依赖无法解决"的报错时,往往已经进入了进退两难的中间状态。
1.2 跳级升级会触发哪些依赖问题
跨大版本升级时,最核心的几个底层组件会同步更新,包括glibc、OpenSSL、GCC、Python、Perl、systemd等。这些组件之间本身就存在相互依赖,而且很多应用软件在编译时绑定了特定版本的运行时库。
超出官方支持范围的跳级升级,容易在三个层面出问题。第一,apt依赖解析器面对的是"旧版本包A依赖旧库B,新版本包C依赖新库D,但新库D又会破坏旧版本包E的兼容"这类多连锁关系,很难自动找到一个完整且合理的升级方案。第二,配置文件格式如果跨了多个大版本发生变化,比如某个服务的配置语法从版本11到版本13已经改了两次,中间步骤没有迁移过程,最终结果就是服务起不来。第三,第三方软件源通常只适配特定Debian版本,跳级会让这些源全部失效,apt update阶段就报404或GPG错误。
与其去赌这些风险,不如老老实实三步走。每一步只改动一个版本跨度,升级完、重启、验证服务,再进入下一个版本。这样一旦出问题,定位范围也被限制在一个版本跨度内,排查成本低得多。
1.3 Buster到Trixie跨了四代,组件变化到底有多大
从Debian 10到Debian 13,实际跨越了四个大版本(10、11、12、13),组件版本的变化是全局性的。拿关键部件举例,内核从Debian 10时代的4.19/5.10一路走到Debian 13的6.12 LTS,glibc从2.28演进到2.36再往后更新,OpenSSL从1.1.1系列迁移到3.x系列,默认的Python解释器也从3.7跨越到3.13。
这样大幅度的组件更新,意味着很多编译好的应用和内核模块都需要重新编译或重新安装。如果机器上跑了第三方内核模块、商用的数据库软件、或者很久没维护的老应用,升级前必须先确认它们对新版本组件的兼容性。我这次升级的机器上跑着Docker和几个自研服务,Docker部分后来统一重装了新版docker-ce,自研服务则全部是基于Python虚拟环境运行,属于Python 3.13能兼容的纯代码应用,这才放心动的手。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 动手升级前先做这几件事:备份、源检查和环境体检
2.1 一份能兜底的备份方案
升级前最重要的永远是备份。不要觉得机器一直跑得好好的就跳过这步,大版本升级涉及几百上千个软件包的替换,任何一个环节上的意外都可能让系统无法正常启动。
我自己的备份策略分为两层。第一层是全盘快照,针对的是虚拟化环境,先在宿主机上对整台虚拟机做一次快照,这样如果升级过程中出现无法修复的问题,可以直接回滚到升级前的状态。如果你的机器是物理机,可以用dd对系统盘做镜像,或者至少用LVM快照把根文件系统状态固定下来。
第二层是文件级别的关键目录备份。不管有没有全盘快照,我都会单独备份/etc目录、/var/lib/dpkg目录、/var/lib/apt/extended_states文件,以及应用相关的数据目录。/etc里存着所有服务的配置,升级过程很容易触发配置迁移;/var/lib/dpkg里是dpkg的包状态数据库,万一升级中断还能用来恢复包状态;extended_states里记录了哪些包是被自动安装的,这对升级后的apt autoremove清理至关重要。
实操时直接打包这几个关键目录就行:
bash复制tar -czpf /root/debian-upgrade-backup-etc.tar.gz /etc
tar -czpf /root/debian-upgrade-backup-dpkg.tar.gz /var/lib/dpkg /var/lib/apt/extended_states
再顺手导出一份当前已安装包的清单:
bash复制dpkg --get-selections > /root/installed-packages.txt
这套组合下来,系统层面和应用配置层面都有了兜底,升级过程中真遇到"配置被迁移得面目全非"的情况,也能手动把旧配置翻出来。
2.2 磁盘空间、网络源与版本确认
大版本升级需要下载和安装大量新包,磁盘空间不够会直接导致升级失败。我的经验是根文件系统至少留出5GB以上的可用空间,如果你安装的包比较多,最好留8到10GB。升级前用df -h确认一下根目录、/usr、/var这些目录的剩余空间,如果不够就先清理日志和旧包缓存:
bash复制apt clean
journalctl --vacuum-size=200M
网络方面,建议把apt源换成离你网络较近的官方镜像源。这里有个容易被忽略的点:很多第三方镜像站对大版本目录的同步会有延迟。比如Debian 13刚发布时,某些镜像站的trixie目录可能还没完全同步好,如果升级时碰到404,换回官方源deb.debian.org通常能解决问题。
还要确认当前系统确实处于"未处于升级中间状态"。如果之前有未完成的apt操作,或者有包处于半安装状态,必须先处理掉再开始。可以先跑一遍:
bash复制apt update
apt list --upgradable
apt-get check
如果apt-get check没有任何报错,说明dpkg状态一致,可以继续。
2.3 清单化记录包状态,清理第三方源
升级前需要知道自己机器上装了什么、哪些是手动安装的、哪些是依赖自动拉进来的。检查hold状态的包很重要,这些包如果被锁定在旧版本,升级时可能阻塞整个依赖链:
bash复制apt-mark showhold
如果有hold包,先把它们解除,等升级完成后再根据实际需要重新设置。我自己就吃过这个亏,之前为了固定某个服务版本手动hold了它,结果升级时apt一直报"下列软件包未升级",排查了半天才想起来还有这回事。
第三方软件源的处理要特别注意。常见的比如Docker官方源、PostgreSQL官方源、NodeSource源,在升级到新版本Debian后大概率会失效,因为源地址里通常写死了组件代号。我有两种处理方式:一是升级前把它们全部注释掉,升级完成后再统一更换为适配新版本的源并重新添加GPG key;二是如果源本身提供了跨版本兼容的地址格式,就直接在源文件里把版本代号改掉。
建议升级期间只用Debian官方源或可信镜像源,把第三方源全部隔离,干净利落地完成核心系统升级,再逐一恢复第三方软件。这样排查问题时也容易区分是系统的问题还是第三方软件的问题。
3. 逐级升级全流程:从Buster到Trixie的每个关键点
3.1 第一跳:Buster到Bullseye(10→11)
第一跳从Debian 10升级到Debian 11。这一跳相对平缓,但组件变化已经不小,涉及内核从4.19升级到5.10 LTS,glibc从2.28到2.31,默认的Python从3.7升到3.9。
升级的第一步是修改apt源。Debian 10默认使用/etc/apt/sources.list文件,把文件中所有的buster替换为bullseye。这里要注意,除了主源,buster/updates(安全源)也需要改成bullseye-security。改完后的源文件核心内容类似:
bash复制deb http://deb.debian.org/debian bullseye main
deb http://deb.debian.org/debian-security bullseye-security main
改完源之后,先不要直接full-upgrade,先更新索引并做一次常规升级:
bash复制apt update
apt upgrade --without-new-pkgs
--without-new-pkgs这个参数的意思是只升级已安装包的版本,不因为依赖关系引入新的包。这样能让大部分常规软件包先平滑过渡到Bullseye的版本,减少后续full-upgrade时的依赖冲突。跑完这一步后,再执行完整的版本升级:
bash复制apt full-upgrade
full-upgrade会允许apt根据依赖关系安装新包、移除不再需要的旧包,这才是真正完成大版本跨越的动作。这一步执行过程中,系统会弹出一系列配置文件的交互询问,比如某些服务的配置需要更新时,会问你是保留现有配置还是使用维护者版本。这个交互的处理策略我后面单独讲。
第一跳完成后,重启系统,让它加载Bullseye的内核和服务配置。重启完确认版本:
bash复制cat /etc/debian_version # 应该显示 11.x
uname -r # 确认内核已经是 5.10.x
确认无误后,再做一次全量检查,比如systemctl --failed看有没有服务启动失败,再决定是否进入下一跳。
3.2 第二跳:Bullseye到Bookworm(11→12)
Debian 11到Debian 12的变化明显更大,最核心的是OpenSSL从1.1.1升级到3.0系列。这个变动影响面很广,很多老应用如果直接编译链接了OpenSSL 1.1的动态库,升级后需要重新编译或安装兼容版本。内核从5.10升到6.1 LTS,默认Python从3.9升到3.11,PHP默认版本也从7.4升到了8.2。
另外还有一个值得注意的点:Debian 12开始,默认安装的非自由固件处理方式有变化,安装介质里包含了更多固件包。但对已有系统的升级来说,这意味着一些固件相关包可能会被安装或更新,对普通用户感知不明显,但如果你遇到无线网卡等设备在升级后工作异常,优先检查固件包的安装状态。
这一跳的步骤和第一跳相同,改源、update、upgrade、full-upgrade。源文件里把bullseye全部改成bookworm:
bash复制deb http://deb.debian.org/debian bookworm main
deb http://deb.debian.org/debian-security bookworm-security main
然后执行:
bash复制apt update
apt upgrade --without-new-pkgs
apt full-upgrade
在跑full-upgrade时,Debian 12的选择性变化会开始显现。比如libssl1.1这个包在Bullseye里还存在,但Bookworm已经不再提供,依赖老版本OpenSSL的第三方应用就会出现依赖无法满足的问题。遇到这种情况,先看官方是否提供了兼容包,没有的话只能等应用方发布新版再升级。
如果真的遇到依赖无法满足导致升级停滞,不要在升级过程中强制删除一批依赖包。更稳妥的方式是先让升级停在当前状态,确认报错涉及哪些包,再通过安装新包替换、升级第三方软件等方式解决。升级到一半强制中断,反而容易把dpkg状态搞坏。
重启后验证版本为12.x,同样检查服务状态,然后再进入最后一跳。
3.3 第三跳:Bookworm到Trixie(12→13)
从Debian 12到Debian 13的升级是最后一步,也是变化最全面的一步。内核从6.1 LTS升到6.12 LTS,默认Python到3.13,GCC更新到14系列,OpenSSL在3.x系列内继续升级。对桌面用户来说,Wayland和桌面组件的版本都推进了一大截;对服务器用户来说,主要关注的是运行时的通用组件更新。
Debian 13还有一个需要注意的变化:从Debian 12开始,系统里可能已经默认使用了deb822格式的源文件,路径是/etc/apt/sources.list.d/debian.sources。如果你是从很老的版本一路升上来的,/etc/apt/sources.list可能还是传统deb格式。两个文件可以共存,但要注意不能同时生效导致源重复。我的处理方式是把/etc/apt/sources.list里的内容清掉,统一使用.sources文件:
bash复制cat /etc/apt/sources.list.d/debian.sources
这个文件的内容是类似这样的deb822格式:
code复制Types: deb
URIs: http://deb.debian.org/debian
Suites: trixie trixie-updates
Components: main
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
出于稳妥考虑,我选择继续沿用传统的sources.list方式,只把bookworm全部替换成trixie,没有强行切换格式。Debian 13对传统格式仍然支持,升级过程中没必要给自己加戏。
执行同样的升级命令:
bash复制apt update
apt upgrade --without-new-pkgs
apt full-upgrade
升级完成后重启,这时要留意内核的加载情况。Debian 13默认内核是6.12 LTS,如果机器上有第三方内核模块(比如特定网卡驱动、显卡驱动),需要确认它们是否支持6.12内核。如果不支持,启动阶段可能直接卡住,或者模块加载失败,此时可以借助GRUB选择旧内核启动,进入系统后再处理驱动问题。
到这里,三个版本跨度全部走完,系统已经是Debian 13。
4. 升级过程中最容易翻车的几个环节与排错案例
4.1 配置文件冲突的交互策略,以及自动化处理方案
大版本升级中,配置文件冲突是几乎一定会遇到的情况。当apt检测到某个包的新版本带有修改过的配置文件时,而系统上这个文件也被本地修改过,dpkg就会停下来提问,常见的三种选择是:
| 选项 | 含义 | 适用场景 |
|---|---|---|
| Y 或 I | 安装维护者的新版本 | 明确知道新配置格式有变化 |
| N 或 O | 保留现有旧配置 | 本地做过深度定制,不确定迁移后果 |
| D 或 Z | 查看两个版本的差异 | 想先对比再决定 |
我的默认策略是保留旧配置,即选N。原因是很多服务在新版本里能兼容旧配置格式,安全更新会通过其他机制推进;而如果选了新配置,一旦迁移逻辑不完整,服务可能直接起不来。升级完成后再逐个服务检查、手动调整配置,比升级过程中被迫做决定要安全得多。
如果在无人值守的批处理场景下升级,可以使用dpkg的force选项统一处理:
bash复制apt full-upgrade -o Dpkg::Options::="--force-confold"
--force-confold会让dpkg在所有配置文件冲突时默认保留旧版,不进行任何交互提问。这个方式适合远程升级、非交互环境,但升级完成后一定要主动检查那些配置格式确实发生变化的服务。
4.2 第三方apt源和GPG Key失效的典型场景
升级过程中,apt update报GP G key错误或404错误,绝大多数情况是第三方软件源导致的。比如Docker官方源如果还指向bullseye,而你的系统已经进入bookworm阶段,apt update会尝试去拉取bullseye的包列表,如果该目录还在没问题,一旦源仓库调整了目录结构或删除旧版本支持,就会得到404。
我的处理流程是这样的:升级前把所有.list文件和.sources文件中非Debian官方的源全部注释掉,路径一般在/etc/apt/sources.list.d/下。升级过程全程只用官方源。升级完成后,逐个重新添加第三方源,注意要添加适配当前Debian版本的地址,并重新下载GPG key:
bash复制curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker.gpg
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker.gpg] https://download.docker.com/linux/debian trixie stable" > /etc/apt/sources.list.d/docker.list
这里把源地址里的bullseye或bookworm改成trixie,再apt update并重装对应软件即可。如果apt update时GPG key报错,检查一下key是否已过期,key所在路径是否和源文件里的signed-by参数一致,这两个问题占了GPG错误的大部分比例。
4.3 内核升级后起不来:GRUB应急与模块问题
内核跨版本升级后,直接重启可能是整个升级流程里风险最高的一步。万一新内核无法启动,不要慌,GRUB会保留多个内核条目。开机时进入GRUB菜单,在Advanced options里选择旧版本的Linux内核启动。
进入系统后,要排查新内核无法启动的原因。常见的情况有三种:一是第三方内核模块没有正确编译安装,启动时模块加载阶段直接panic;二是新内核需要的initramfs没有正确生成;三是某些硬件驱动在新内核下不受支持或存在bug。优先级最高的动作是先把第三方驱动相关的包重新编译或降级,再update-initramfs -u重新生成initramfs,然后尝试再次引导新内核。
我自己的习惯是在每一跳升级完成、重启验证通过之后,才进入下一跳。不要在几个大版本的内核和模块叠加状态下才检查启动问题,那样根本说不清是哪个版本引入的问题。分步验证虽然慢一点,但每一步的可控性都高得多。
5. 升级完成后的系统整备与常用设置复核
5.1 版本确认、残留清理与安全订阅
三跳全部完成并成功重启后,第一件事是确认版本号:
bash复制cat /etc/debian_version
lsb_release -a
uname -a
如果这三个输出显示的都是13.x和6.12内核,说明版本层面已经到位。接下来要做的是清理升级过程中残留的旧包和依赖:
bash复制apt autoremove --purge
apt clean
autoremove --purge会把升级过程中不再需要的旧依赖包清除掉。这里要注意,如果之前extended_states备份缺失,autoremove可能会误删一些你手动安装的包。升级到Debian 13后,建议跑一遍:
bash复制apt list --manual-installed | grep -v "^lib"
人工过一遍这个列表,确认没有重要软件被误标记为自动安装。另外,Debian 13作为新的stable版本,安全更新仓库需要确认状态。执行apt update后看有没有trixie-security相关的更新源。如果用的是传统sources.list,加一行:
bash复制deb http://deb.debian.org/debian-security trixie-security main
这样才能保证后续安全补丁正常推送。
5.2 第三方软件、Docker与服务的重配清单
系统升级完成后,第三方软件的重装工作要按依赖关系排列。我这次主要处理的是Docker服务。升级到Debian 13后,Docker daemon如果之前是从旧版本直接升上来的,很可能因为cgroup v2、iptables规则或containerd版本差异导致无法启动。我的做法是彻底移除旧版本docker-ce、containerd等组件,用官方trixie源重新安装:
bash复制apt remove docker-ce docker-ce-cli containerd.io docker-compose-plugin
apt autoremove --purge
apt update
apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin
重新安装后,Docker的数据目录在/var/lib/docker,默认这些数据不会因为卸载软件包而删除,所以旧镜像和容器数据仍然保留。启动Docker后检查一下:
bash复制systemctl start docker
docker ps -a
如果容器列表还在,说明数据完好,逐个启动容器观察日志即可。
自建服务方面,检查的重点是systemd服务单元是否还能正确启动。升级过程中,很多服务的unit文件路径或依赖的服务名可能发生变化,比如某个服务依赖的库文件路径从/usr/lib挪到了/usr/lib/x86_64-linux-gnu,这类变化会导致服务启动时报找不到文件。逐个执行systemctl status,同时用systemctl --failed快速锁定启动失败的服务。
5.3 网络、Swap、输入法等容易遗漏的细节复核
升级到Debian 13后,有几个系统设置容易被忽略,但这些设置一旦出问题会直接影响使用体验。
网络配置最容易出问题。很多服务器用/etc/network/interfaces配置静态IP,但升级后如果系统自动安装了NetworkManager,并且接管了网络管理,可能出现IP配置冲突。检查当前网络状态:
bash复制ip a
ss -tlnp
确认外部服务能正常监听、系统能访问外网。如果IP丢了,检查/etc/network/interfaces还在不在,NetworkManager是否在运行,两者只能保留一个作为网络管理器。
Swap的设置也需要确认。升级过程中内核参数和fstab的格式兼容性通常没问题,但如果原来的Swap是挂在独立分区上,建议确认fstab里的UUID是否正确,避免因为磁盘设备名变化导致swap没挂上:
bash复制swapon --show
free -h
如果swap没有正常启用,说明fstab里的设备标识可能失效了,更新为新的UUID即可。
桌面用户可能会遇到输入法配置重置的问题。如果之前用的是fcitx,升级到13之后要确认环境变量GTK_IM_MODULE、QT_IM_MODULE和XMODIFIERS是否还指向fcitx。Debian 13的输入法框架选择也比以前更灵活,升级后重新配置一下im-config,或者检查~/.xinputrc里面是否还保留着正确的输入法配置。
我自己在升级完这台服务器后,又顺手跑了一次完整的安全更新,确认所有apt源都指向trixie系列,确认Docker容器恢复运行,确认几个自建服务日志采集正常。整个升级过程折腾了大概一个下午,但因为有分步验证,每一步的问题都被控制在很小范围内,基本没有出现过系统起不来的情况。
最后再分享一个小技巧:如果手头有可以复用的虚拟机或容器环境,强烈建议先克隆一台和待升级服务器配置相近的测试机,在测试机上完整跑一遍三跳升级流程,记录每一步的报错和耗时。这样在真机上操作时,你会提前知道哪一步会停留多久、哪些包的配置冲突可以放心选N,整个过程会从容很多。
