简介面向企业级Linux运维与IT管理人员的Ubuntu Server 24.04内网APT源搭建指南针对封闭网络环境下软件更新与部署效率提升、降低公网依赖及保障敏感数据安全等场景提供从环境准备、阿里云/清华镜像源选型、apt-mirror配置与同步、Nginx发布到客户端源地址替换的完整实施路径。资源为单份PDF文档体积894KB内容覆盖AMD64与ARM64两种架构选型、mirror.list关键参数如base_path、nthreads调整、离线安装Nginx及依赖包、软连接与权限配置、404访问问题排查等实操细节亦可用于私有镜像服务体系的搭建参考。当前已有2026人学习适合有Ubuntu基础、需在隔离网络内自建APT仓储的技术人员直接参考实施。1. 内网环境下的APT源搭建为什么Ubuntu Server 24.04要自建源很多运维第一次碰内网APT源都是在停电一样的现场一批Ubuntu Server 24.04机器不许出公网apt-get update直接超时开发环境要装gcc、docker又缺一堆依赖。这时候最实用的一条路是用一台还能访问公网的机器跑apt-mirror把Ubuntu 24.04noble仓库同步回来再用Nginx在内网发布客户端把sources.list.d里的URIs改成内网IP就能正常装包。这套方案不只解决“装不上包”还能统一内网软件版本让几百台机器共享一份缓存避免每台都重复拉公网流量。适合管过一批Ubuntu服务器、被离线依赖困住过的运维也适合刚接手封闭网络环境的新手照着配。2. 服务器端准备与镜像源选型apt-mirror先装起来mirror.list的参数逐行讲2.1 为什么是 apt-mirror 而不是 apt-cacher-ng在做内网APT源之前我先说一句如果只是想给三五台测试机省流量用apt-cacher-ng会更省心。它的工作方式是拦截客户端发往公网源的HTTP请求第一次从公网拉下来缓存之后其他机器直接从缓存取。但问题也很明显一是第一次总要有一台机器能真正访问公网二是缓存内容是“用过才有”如果内网机器要装一个从来没装过的包这台机器就得临时开公网权限。对完全封闭的网络这不是好体验。apt-mirror的模式是主动同步。它先下载dists目录里的Packages索引再根据索引把pool里的deb拉下来整个仓库离线躺在本地。客户端无论装什么包只要同步时索引里有就能在内网直接命中。所以我的选型习惯是测试环境图省事用apt-cacher-ng正经生产内网、大几十台机器、有统一版本诉求的用apt-mirror。后面所有步骤都按apt-mirror走。2.2 环境准备noble 版本与架构判断搭建同步机建议直接用Ubuntu Server 24.04.1因为我们要同步的同样是noble仓库版本上下对应少踩很多隐形坑。先登录系统确认codenamelsb_release -aCodename这一行应该是noble。如果显示的是jammy或focal后面的源配置要整体换版本代号不能照抄。架构方面同步机本身是什么架构影响不大真正决定同步内容的是mirror.list里的source地址。Ubuntu官方仓库分两个入口一般AMD64走ubuntu路径ARM64走ubuntu-ports路径清华镜像站也是按这个规则区分。如果内网同时有x86和ARM两种客户端别指望一个仓库通吃后面会讲怎么分开组织。硬盘空间提前看。noble的main、universe、multiverse加起来容量不小还要同步security、updates、backports。我一般建议至少留出60G用df -h确认剩余空间别等同步到一半把根目录写满。2.3 安装apt-mirror在能访问公网的机器上执行apt-get update apt-get install -y apt-mirror安装过程本身不复杂核心命令就一个apt-mirror。但这一步有个前提这台机器必须能正常连上公网源否则第一步就装不上。装完可以用apt-cache policy apt-mirror确认版本Ubuntu 24.04官方源里自带这个包不用额外加PPA或编译。2.4 mirror.list配置从默认文件改到可同步安装后配置文件在/etc/apt/mirror.list。习惯是先备份原文件再改cp /etc/apt/mirror.list /etc/apt/mirror.list.bak vim /etc/apt/mirror.list一份能用的最小配置如下############# config ############# set base_path /var/spool/apt-mirror set nthreads 20 set _tilde 0 ############# end config ############# deb https://mirrors.aliyun.com/ubuntu noble main restricted universe multiverse deb https://mirrors.aliyun.com/ubuntu noble-security main restricted universe multiverse deb https://mirrors.aliyun.com/ubuntu noble-updates main restricted universe multiverse deb https://mirrors.aliyun.com/ubuntu noble-backports main restricted universe multiverse逐行说明set base_path是镜像文件下载根目录后面所有mirror、skel、var子目录都从这里生成。默认路径是/var/spool/apt-mirror一般不用改。set nthreads是下载线程数。带宽足的内网同步机可以设20但如果源站响应慢或者网络有丢包20很容易刷出一排Failed。我一般会先设8跑一次稳定后再调高。set _tilde 0是关掉路径里~字符的特殊处理。apt源里有些包名带~如果打开tilde处理URL会被改写导致404默认保持0即可。源地址部分阿里云、中科大、清华三选一。阿里云和中科大路径相同都是/ubuntu清华的AMD64也是/ubuntuARM64要用/ubuntu-ports。组件部分只写了main、restricted、universe、multiverse没有加deb-src。很多教程会把deb-src也写上结果同步时间翻倍多下一堆源代码包大多数内网环境用不上。如果确实有源码编译需求再单独加一行deb-src并同步承受那份翻倍的时间和存储。2.5 多架构源怎么选AMD64、ARM64 与镜像站对照刚才提到清华镜像站的ubuntu-ports这个很容易踩。先放一张对照表需求架构镜像站URL前缀配置里写法的来源AMD64阿里云mirrors.aliyun.com/ubuntudeb https://mirrors.aliyun.com/ubuntu noble ...AMD64中科大mirrors.ustc.edu.cn/ubuntudeb http://mirrors.ustc.edu.cn/ubuntu noble ...AMD64清华mirrors.tuna.tsinghua.edu.cn/ubuntudeb https://mirrors.tuna.tsinghua.edu.cn/ubuntu noble ...ARM64清华mirrors.tuna.tsinghua.edu.cn/ubuntu-portsdeb https://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports noble ...如果mirror.list只写AMD64源而内网有ARM64机器那这些ARM64机器之后从你的内网源update时会发现自己需要的arm64架构索引根本不存在。这不是发布问题是同步阶段没把ARM64源拉下来。解决做法是建两套mirror.list或者在同一台机器上用两个base_path分开同步。我习惯直接开两个配置文件分别指定不同的base_pathset base_path /var/spool/apt-mirror-amd64 deb https://mirrors.aliyun.com/ubuntu noble main universe multiverse另一份set base_path /var/spool/apt-mirror-arm64 deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports noble main universe multiverse这样两个仓库物理隔离后面用Nginx发布时只分成两个location客户端按架构选对应源地址。2.6 还有两个参数defaultarch 与 postmirror_script原文档里还有几个注释项趁配置时一并说明。set defaultarch amd64是指定同步架构默认跟随本机架构。如果同步机是x86但想同步ARM64单靠defaultarch解决不了因为ARM64仓库入口都不在同一个路径下。所以我更建议用分base_path的方式比改单个参数灵活。postmirror_script是同步完成后执行的脚本路径run_postmirror设为1才启用。我这边会在脚本里放一个同步日志归档或者结果通知方便第二天检查。没有这个需求就保持注释。var目录下的clean.sh脚本很重要下一章会专门说什么时候跑、什么时候不能跑。3. 同步与清理让apt-mirror把远程仓库变成本地目录3.1 第一次同步命令、日志与预期配置好mirror.list后用nohup把同步放到后台避免终端断开把进程带走mkdir -p /var/spool/apt-mirror/var/log nohup apt-mirror /var/spool/apt-mirror/var/log/sync.log 21 看同步进度tail -f /var/spool/apt-mirror/var/log/sync.logapt-mirror启动后会先下载dists目录下的Packages、Release等索引文件这个过程在日志里表现为一行行Receiving index files。索引全部拿到之后才会进入实际deb包下载阶段日志开始刷pool路径下的下载记录。如果你看到日志长时间停在某个Index文件不动先看网络和源站不一定是挂了。第一次同步耗时和仓库大小相关。noble的main加universe累计几十GB走千兆内网加镜像站也要一段时间公网带宽有限就耐心等。我一般不会开着终端干等而是等日志文件不再增长后再grep一次失败项grep -i fail /var/spool/apt-mirror/var/log/sync.log有失败就重跑没有失败才算同步结束。3.2 同步完成的标志不是“退出”而是“干净”apt-mirror跑完不会像脚本那样打印一句“完成”退出码正常就结束了。但这里有个坑wget下载中断时磁盘上会留下0字节文件apt-mirror重跑时会把它当作已下载文件跳过客户端读取索引时就会报Hash Sum mismatch。所以我的习惯是同步完成后先扫一遍0字节文件find /var/spool/apt-mirror/mirror -type f -size 0 -print如果有输出删掉这些文件find /var/spool/apt-mirror/mirror -type f -size 0 -delete然后重新执行apt-mirror让缺失文件重新下载。这一步看起来多余实际上能省掉后面好几天排障。很多人忽略这一点等客户端大量报错才回头查那时候已经分不清是发布问题还是同步问题。3.3 clean.sh什么时候该跑什么时候打死别跑apt-mirror在var目录下生成了一个clean.sh它会扫描镜像目录里的所有deb文件和当前索引对比把索引里不存在的一律删掉。作用是让仓库体积保持精简不积累一堆已经被上游移除的旧包。但clean.sh一旦跑起来没有后悔药。有次我在mirror.list里少写了一个组件同步完成后顺手跑了clean.sh第二天发现镜像目录被删掉大量包因为新索引里根本没有这些组件。所以我现在对clean.sh的态度是只在确认mirror.list的源、组件、架构都正确时才执行cd /var/spool/apt-mirror/var ./clean.sh如果只是修改了components、切换了源或者不确定客户端还有没有旧版本依赖就先不要跑clean.sh。多占点磁盘总比删错后重新同步便宜。跑之前也可以先du -sh /var/spool/apt-mirror/mirror记录原体积删完对比一下心里有数。3.4 多源、多架构、多发行版目录结构怎么组织apt-mirror的目录结构有一个特征它按源的主机名分目录。比如用https://mirrors.aliyun.com/ubuntu同步同步完的文件在/var/spool/apt-mirror/mirror/mirrors.aliyun.com/ubuntu/下面第一层目录是mirrors.aliyun.com不是ubuntu。如果你换成中科大源又会多出一个mirrors.ustc.edu.cn目录。发布时要记住这个层次别把根目录指错。当你同时同步了AMD64和ARM64两份仓库并分别指定了base_path目录就变成两个独立树。发布时一个软链接指向amd64仓库另一个指向arm64仓库ln -s /var/spool/apt-mirror-amd64/mirror/mirrors.aliyun.com /var/www/mirror-amd64 ln -s /var/spool/apt-mirror-arm64/mirror/mirrors.tuna.tsinghua.edu.cn /var/www/mirror-arm64客户端sources里的URIs分别写成/mirror-amd64/ubuntu和/mirror-arm64/ubuntu-ports。这样架构分离update才不会乱。如果还想同步另一个发行版方法一样再加一个软链接就行。3.5 搬运到内网发布机rsync、权限与路径一致性同步机和发布机通常不是同一台同步机需要临时公网权限发布机放在机房内网。两个角色也可以合并但多数场景是分开的。搬运时用rsync比scp好可以断点续传并保留权限rsync -avP /var/spool/apt-mirror/mirror/ 192.168.1.10:/var/spool/apt-mirror/注意源路径结尾的斜杠它的作用是拷贝mirror目录下的内容到目标目录而不是多套一层。搬运前先在发布机上创建目录并调整属主mkdir -p /var/spool/apt-mirror chown -R root:root /var/spool/apt-mirror搬运完成后确认路径ls /var/spool/apt-mirror/mirror/mirrors.aliyun.com/ubuntu/dists/noble/Release如果能找到Release文件路径才算对齐。常见的搬运问题是目标机没有apt-mirror包/var/spool/apt-mirror目录是rsync临时创建的目录所属权限可能不对。所以搬运后顺手补一次读权限chmod -R arX /var/spool/apt-mirror/mirror然后交给Nginx处理。3.6 增量同步的日常维护内网仓库建立之后不是一锤子买卖Ubuntu的security更新基本每天都有。日常维护一般用cron在凌晨跑同步0 3 * * * /usr/bin/apt-mirror /var/log/apt-mirror-sync.log 21这条cron没有加锁。如果上次同步还没跑完下一次又启动可能出现并发写目录。稳妥一点用flock0 3 * * * /usr/bin/flock -x /var/lock/apt-mirror.lock /usr/bin/apt-mirror /var/log/apt-mirror-sync.log 21flock在拿到锁文件后才执行apt-mirror避免两个进程同时写镜像目录。增量同步属于同步阶段必须考虑的东西不然等你真正依赖这个源时更新到一半卡死那个压力比搭源时大得多。4. 用Nginx发布APT源软链接、alias与HTTP访问细节4.1 为什么发布层选Nginx镜像仓库最终要变成客户端能访问的HTTP目录Nginx很适合做这件事。APT客户端本质上就是发出HTTP GET请求访问dists和pool目录下的文件不需要动态应用只需要静态文件服务和目录索引。Nginx的autoindex可以生成目录列表方便浏览器排查404而且内网环境通常机器资源不宽裕Nginx一个进程就能扛住成百上千台客户端的update。如果你在现有环境里对Apache更熟也完全可以替代但配置思路是一样的指定站点根目录、打开目录索引、确保路径拼接正确。这里按Nginx讲因为APT源场景里Nginx更常见原文档的发布方式也是Nginx。4.2 离线安装Nginx 1.24.0deb包批量装发布机大概率没有公网权限所以不能用apt-get install nginx直接拉公网包。需要提前在任意能联网的Ubuntu 24.04机器上用apt-get download把Nginx和相关依赖抓下来再拷贝到发布机apt-get download nginx nginx-common nginx-core apt-get download libnginx-mod-http-geoip libnginx-mod-http-image-filter libnginx-mod-mail libnginx-mod-stream apt-get download libgeoip1t64把下载好的deb放在同一个目录后批量安装mkdir -p /root/nginx-setup cd /root/nginx-setup dpkg -i *.debdpkg安装过程中如果报依赖错误通常会指出缺哪个包。比如提示缺libnginx-mod-http-geoip说明拷贝时漏了补上后重新执行dpkg -i *.deb即可。注意包版本必须匹配Ubuntu 24.04noble不要混入其他发行版的deb否则依赖会搭错。这个用apt-get download方式拿包比从网上到处找要可靠因为版本一定和noble源对应。安装完成后验证nginx -v nginx -tnginx -t显示语法正确后再启动服务systemctl enable --now nginx4.3 软链接映射镜像仓库到站点根目录我的Nginx配置思路是站点根目录root设置为/var/www然后在/var/www下用软链接把镜像仓库引进来。先创建软链接ln -s /var/spool/apt-mirror/mirror/mirrors.aliyun.com /var/www/mirror这里有个特别容易踩的细节。apt-mirror生成的目录名是mirrors.aliyun.com不是mirror.aliyun.com少个s就找不到目录。如果你配的是中科大源就要链接mirrors.ustc.edu.cn。如果按前面说的分了两套架构就分别链接ln -s /var/spool/apt-mirror-amd64/mirror/mirrors.aliyun.com /var/www/mirror-amd64 ln -s /var/spool/apt-mirror-arm64/mirror/mirrors.tuna.tsinghua.edu.cn /var/www/mirror-arm64创建后一定要确认目标存在用ls -ld /var/www/mirror查看软链接是否指向真实目录。这个确认动作很便宜但能避开后续大量404排查。4.4 Nginx配置root、autoindex 与默认站点修改发布机的默认站点配置文件vim /etc/nginx/sites-enabled/default把内容改成server { listen 80 default_server; listen [::]:80 default_server; root /var/www; server_name _; location /mirror/ { autoindex on; autoindex_exact_size off; autoindex_localtime on; } }这里的逻辑请求/mirror/ubuntu/dists/noble/Release时Nginx先按root /var/www定位到/var/www把URI里的/mirror/ubuntu/dists/noble/Release拼接上去实际文件路径变成/var/www/mirror/ubuntu/dists/noble/Release再通过软链接映射到镜像仓库对应位置。autoindex on是让目录请求返回HTML列表方便浏览器检查autoindex_exact_size off把文件大小显示成可读格式autoindex_localtime on让列表时间显示成服务器本地时间。很多教程会在这时候混用root和alias。简单记住root是把配置的路径和完整URI拼接alias是把配置的路径和location后的剩余部分拼接。如果你在/mirror/这个location里写alias /var/www/mirror/结果是一样的但root写法更省心也可以顺便服务其他静态页面。我用root方案后软链接指向哪里浏览器路径就是什么排查时少一层换算。改完先测试再重启nginx -t systemctl restart nginx4.5 验证HTTP发布curl、目录列表和nginx错误日志发布是否成功不要只看浏览器能打开首页要检查关键的Release和Packages文件。在发布机上直接curlcurl -I http://127.0.0.1/mirror/ubuntu/dists/noble/Release返回200 OK就算通。再验证客户端实际会请求的Packages索引curl -I http://127.0.0.1/mirror/ubuntu/dists/noble/main/binary-amd64/Packages.gz如果404马上看Nginx错误日志tail -f /var/log/nginx/error.log日志里会直接显示open() /var/www/mirror/... failed这个路径就是Nginx实际尝试读取的位置。对照软链接和镜像目录基本一眼就能看出是链接错还是目录名写错。也可以从另一台内网机器用wget模拟客户端wget -q http://192.168.1.10/mirror/ubuntu/dists/noble/InRelease -O /dev/null返回0就说明内网访问正常。这一步是从客户端视角验证能排除curl本机loopback时的路径错觉。还有一点如果发布机只服务内网网段listen不要开成0.0.0.0:80直接把IP写成发布机内网IP比如listen 192.168.1.10:80。这样即使机器有多个网卡也不会把服务暴露到不该暴露的网段。安全范围缩到最小是搭内网源的基本习惯。5. 常见问题与避坑同步中断、404、权限、架构混用下面几条是实际搭过几个内网源之后沉淀下来的排障记录。每条按现象、原因、解决展开按顺序查大部分问题十分钟内能定位。5.1 客户端apt-get update报404但浏览器里明明能打开现象客户端执行apt-get update终端显示Err:1 http://192.168.1.10/mirror/ubuntu noble InRelease 404 Not Found但同一台发布机上用浏览器访问同一个URL又是正常的。原因浏览器访问路径和apt客户端请求路径不一致。最常见的是mirror.list里配置的host和实际生成的目录名对不上比如软链接了/var/www/mirror但它指向了/var/spool/apt-mirror/mirror/mirrors.aliyun.com/ubuntu而实际镜像目录是/var/spool/apt-mirror/mirror/mirrors.aliyun.com/ubuntu。还有一种情况是客户端sources里URIs最后多写或漏写了/ubuntu把路径整体错位。解决先看Nginx错误日志里的实际open路径再顺着这个路径一级一级用ls核对。如果日志显示open() /var/www/mirror/dists/noble/InRelease failed说明URIs里少了/ubuntu因为真实路径应该是/var/www/mirror/ubuntu/dists/noble/InRelease。用readlink -f /var/www/mirror确认软链接最终指向哪里。浏览器验证反而容易被目录列表误导因为浏览器打开的是目录页apt请求的是精确文件。5.2 第一次同步就大量Failed重跑还是缺包现象apt-mirror运行过程中日志刷出大量下载失败重跑一次看似结束但客户端更新时依然缺某些包的索引。原因两方面。一是网络质量不好源站连接被重置或半途断掉wget留下0字节文件二是nthreads开太大比如直接写20源站稍微限流就会成片失败而apt-mirror不会自动重试失败的deb。解决先把线程数降到8set nthreads 8然后跑完整同步结束后删除0字节文件find /var/spool/apt-mirror/mirror -type f -size 0 -delete再重新执行apt-mirror让缺失文件补下。这个流程要重复到grep -i fail的日志为空才能进入发布阶段。不要认为apt-mirror进程退出就是同步干净了。5.3 clean.sh跑完仓库少了大量包现象clean.sh执行之后镜像目录体积变小但客户端想安装一个旧版本包时提示E: Package xxx has no installation candidate去pool目录翻发现对应deb已经被删了。原因clean.sh按当前索引清理文件。如果在mirror.list换过源、减少过components、把deb-src去掉了索引覆盖范围变小那些不在新索引里的deb全部被判定为多余文件并删除。这个过程非常快等你发现时只剩新索引里的包。解决变更mirror.list之后不要立即跑clean.sh先跑完同步并观察客户端使用情况。确认仓库里所有还在用的包都在索引覆盖范围内再执行清理。清理前至少记录目录原体积也可以把clean.sh复制一份到/tmp备用。严格来说脚本本身没有错错在对索引变化的敏感度不够。从那以后我每改一次配置都会先注释掉clean段的调用稳定后再放开。5.4 update时报403或Permission denied现象客户端update时报Failed to fetch ... 403 Forbidden发布机Nginx错误日志里出现权限denied。原因apt-mirror同步生成的文件默认属主是root很多目录权限是700或750。Nginx worker进程以www-data用户运行没有权限读取这些目录和文件访问时被拒绝。解决直接放开镜像目录读权限chown -R www-data:www-data /var/spool/apt-mirror/mirror chmod -R arX /var/spool/apt-mirror/mirror其中arX表示给所有用户加读权限对目录加执行权限以允许进入。如果发布机和同步机是单独角色这个操作在发布机上做如果同一台机器同步完记得补这条。还有一种隐蔽情况软链接本身权限正常但目标路径中间某一级目录没有x权限nginx同样进不去。5.5 AMD64和ARM64混在一个仓库能update但install不到现象内网同时有x86和ARM客户端配置同一个内网源地址。AMD64客户端update正常ARM64客户端update出现404或者install时提示找不到包。原因mirror.list里只同步了AMD64仓库或者两个架构的仓库被合并到同一个base_path生成的dists/noble/main下只有binary-amd64目录和空的binary-arm64索引实际包根本没有拉全。APT客户端按二进制索引查找包位置索引里没有对应架构的包自然找不到。解决AMD64和ARM64分开仓库用两个base_path、两个软链接、两个location发布。客户端根据架构选择URIs比如AMD64客户端写/mirror-amd64/ubuntuARM64客户端写/mirror-arm64/ubuntu-ports。不要尝试把两个架构的pool合并成一个目录Ubuntu的Release签名会校验仓库结构手工合并只会让InRelease验证失败。6. 客户端切换与验证从ubuntu.sources到批量分发6.1 改客户端sources文件Ubuntu 24.04的APT源迁移到了/etc/apt/sources.list.d/ubuntu.sources不再是旧的sources.list单行格式。把文件内容改成Types: deb URIs: http://192.168.1.10/mirror/ubuntu Suites: noble noble-security noble-updates noble-backports Components: main restricted universe multiverse Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg这段的要点是URIs换成你的内网Nginx地址和软链接路径其他项保持noble仓库原有结构。因为同步下来的Release文件仍然是Ubuntu官方签名客户端的gpg keyring不用换apt-get update时会直接校验通过。如果改完sources之后update报签名错误优先检查Nginx路径是否指到了错误目录而不是急着导入新key。6.2 验证从update到install在客户端执行apt-get update apt-get install --download-only curlupdate输出里应该只有Hit出现404或Failed就回看第5章。--download-only的意义是验证能拿到deb但不实际安装。再进一步用apt-get download直接下载一个包cd /tmp apt-get download openssl能成功说明内网源已经可以完整走通。第一次验证一定要从干净环境走一遍别在一台已经配置过各种源的机器上试否则会把旧源缓存的问题算到新源头上。6.3 批量分发的习惯几十台机器手改sources不现实用配置管理工具分发ubuntu.sources就行。分发前把原始sources文件保留好不然误改后找不到备份。分发完强制跑一次apt-get update收集输出里的Hit和Err数量。我从几次翻车里养成的习惯是内网源每次变更后不管Nginx页面看起来多正常都找一台干净的客户端完整跑一遍update加install用日志确认后再让其他人用。这个验证动作几分钟比让整个团队踩一圈坑划算得多。希望帮到你。本文还有配套的精品资源点击获取