はじめに
Ubuntu 20.04 LTS から Ubuntu 22.04 LTS へ do-release-upgrade でアップグレードしようとしたところ、次のエラーで停止する事例に遭遇しました。
After updating your package information, the essential package
'ubuntu-minimal' could not be located.
This may be because you have no official mirrors listed in your software sources,
or because of excessive load on the mirror you are using.
See /etc/apt/sources.list
一見すると、
-
/etc/apt/sources.listが壊れている - 公式Ubuntuミラーを見ていない
-
ubuntu-minimalがAPTから見えていない - Ubuntu 20.04 / 22.04 / 24.04 のリポジトリが混在している
といった問題に見えます。
しかし、今回のケースでは少し違いました。
結論からいうと、ubuntu-minimal が存在しなかったわけではなく、do-release-upgrade の内部処理が Ubuntu 22.04(jammy)の Release ファイルを Python urllib 経由で取得しようとした際に 403 Forbidden で弾かれたことが原因と考えられます。
最終的には、Ubuntuの参照ミラーを archive.ubuntu.com / security.ubuntu.com から jp.archive.ubuntu.com に変更することで、アップグレードが先へ進みました。
環境
今回の環境は以下です。
Ubuntu 20.04.6 LTS
Codename: focal
アップグレード対象は、
Ubuntu 20.04 focal → Ubuntu 22.04 jammy
です。
もともとは Ubuntu 22.04 → 24.04 の手順を見て作業していましたが、実際のサーバーは Ubuntu 20.04 でした。
そのため、まず必要なのは 20.04 → 22.04 のアップグレードでした。
発生したエラー
sudo do-release-upgrade を実行すると、以下のように停止しました。
After updating your package information, the essential package
'ubuntu-minimal' could not be located.
This may be because you have no official mirrors listed in your software sources,
or because of excessive load on the mirror you are using.
See /etc/apt/sources.list
その後、処理は中断されます。
Restoring original system state
Aborting
まず確認したこと
現在のUbuntuバージョン
lsb_release -a
結果:
Distributor ID: Ubuntu
Description: Ubuntu 20.04.6 LTS
Release: 20.04
Codename: focal
現在は Ubuntu 20.04 focal であることが確認できました。
/etc/apt/sources.list の確認
cat /etc/apt/sources.list
主な内容は以下でした。
deb http://archive.ubuntu.com/ubuntu focal main restricted
deb http://archive.ubuntu.com/ubuntu focal-updates main restricted
deb http://archive.ubuntu.com/ubuntu focal universe
deb http://archive.ubuntu.com/ubuntu focal-updates universe
deb http://archive.ubuntu.com/ubuntu focal multiverse
deb http://archive.ubuntu.com/ubuntu focal-updates multiverse
deb http://archive.ubuntu.com/ubuntu focal-backports main restricted universe multiverse
deb http://security.ubuntu.com/ubuntu focal-security main restricted
deb http://security.ubuntu.com/ubuntu focal-security universe
deb http://security.ubuntu.com/ubuntu focal-security multiverse
focal main が存在しており、Ubuntu 20.04 用としては大きく壊れているようには見えませんでした。
通常のAPTでは ubuntu-minimal は見えていた
次に、通常のAPTで ubuntu-minimal が見えるか確認しました。
sudo apt update
apt-cache policy ubuntu-minimal
結果:
ubuntu-minimal:
Installed: 1.450.3
Candidate: 1.450.3
ここが重要です。
通常のAPTでは、ubuntu-minimal はちゃんと見えています。
つまり、少なくともこの時点で、
ubuntu-minimal が本当に存在しない
という状態ではありませんでした。
do-release-upgrade のログを確認
次に、do-release-upgrade のログを確認しました。
grep -n "No 'ubuntu-minimal'" /var/log/dist-upgrade/main.log /var/log/dist-upgrade/*/main.log
結果として、毎回同じようなエラーが記録されていました。
ERROR No 'ubuntu-minimal' available/downloadable after sources.list rewrite+update
さらに、該当箇所の前後を確認しました。
nl -ba /var/log/dist-upgrade/main.log | sed -n '45,90p'
すると、重要な行が見つかりました。
DEBUG error from httplib: 'HTTP Error 403: Forbidden'
しかも、archive.ubuntu.com 側だけでなく、security.ubuntu.com 側でも同様に 403 になっていました。
流れとしては以下のように読めます。
do-release-upgrade が focal → jammy へ切り替える確認を行う
↓
jammy の Release ファイルを取得しに行く
↓
Python/httplib 経由で HTTP Error 403: Forbidden
↓
jammy のパッケージ情報を作れない
↓
ubuntu-minimal が available/downloadable ではないと判定
↓
アップグレード中止
つまり、77行目付近の
No 'ubuntu-minimal' available/downloadable after sources.list rewrite+update
は、直接の原因というより、その前の 403 Forbidden の結果として出ている最終判定と考えられます。
curl では 200 OK だった
ここで、実際に curl で同じURLへアクセスしてみました。
curl -I http://archive.ubuntu.com/ubuntu/dists/jammy/Release
curl -I https://archive.ubuntu.com/ubuntu/dists/jammy/Release
curl -I http://security.ubuntu.com/ubuntu/dists/jammy/Release
curl -I https://security.ubuntu.com/ubuntu/dists/jammy/Release
結果は、いずれも 200 OK でした。
つまり、単純に
サーバーから jammy の Release ファイルへアクセスできない
という話ではありません。
curl では通るのに、do-release-upgrade 内部では 403 になります。
Python urllib で再現確認
do-release-upgrade のログには httplib と出ていたため、Python経由で同じURLへアクセスしてみました。
python3 - <<'PY'
import urllib.request
urls = [
"http://archive.ubuntu.com/ubuntu/dists/jammy/Release",
"https://archive.ubuntu.com/ubuntu/dists/jammy/Release",
"http://security.ubuntu.com/ubuntu/dists/jammy/Release",
"https://security.ubuntu.com/ubuntu/dists/jammy/Release",
]
for url in urls:
print("URL:", url)
try:
with urllib.request.urlopen(url, timeout=20) as r:
print("STATUS:", r.status)
except Exception as e:
print("ERROR:", repr(e))
print()
PY
結果は、すべて 403 Forbidden でした。
HTTP Error 403: Forbidden
ここでかなり原因が絞れました。
curl では 200 OK
Python urllib では 403 Forbidden
do-release-upgrade でも 403 Forbidden
つまり、Python urllib 系のアクセスだけが拒否されている可能性が高くなりました。
User-Agent を変えると Python でも 200 になった
さらに、Python urllib の User-Agent を curl 風に変えて再確認しました。
python3 - <<'PY'
import urllib.request
urls = [
"http://archive.ubuntu.com/ubuntu/dists/jammy/Release",
"https://archive.ubuntu.com/ubuntu/dists/jammy/Release",
"http://security.ubuntu.com/ubuntu/dists/jammy/Release",
"https://security.ubuntu.com/ubuntu/dists/jammy/Release",
]
for url in urls:
print("URL:", url)
try:
req = urllib.request.Request(
url,
headers={"User-Agent": "curl/7.68.0"}
)
with urllib.request.urlopen(req, timeout=20) as r:
print("STATUS:", r.status)
except Exception as e:
print("ERROR:", repr(e))
print()
PY
すると、200 が返りました。
つまり、
Python urllib の標準User-Agent → 403
Python urllib + curl風User-Agent → 200
curl → 200
という状態です。
この結果から、CloudflareなどのBot判定・WAFにより、Python urllib の標準User-Agentが拒否されていた可能性が高いと考えました。
参考:Cloudflare と Python urllib の 403 事例
今回の現象に近い既知事例として、Cloudflare配下のサイトで Python urllib のデフォルトUser-Agentが 403 Forbidden になる報告があります。
Cloudflare Community
Cloudflare Community では、Cloudflare API がデフォルトの Python-urllib/3.x User-Agent を拒否し、User-Agentを変更すると通るようになったという報告があります。
https://community.cloudflare.com/t/api-call-suddenly-returns-403-forbidden/396383
Python urllib だけ 403 になる日本語検証記事
Python標準ライブラリの urllib でだけ 403 Forbidden になり、User-Agentを指定すると通るという検証記事です。
https://nikkie-ftnext.hatenablog.com/entry/python-urllib-403-cloudflare-user-agent
Python.org Discussions
Python.org のフォーラムでも、Cloudflare proxy 配下のURLに対して urllib.request.urlopen() が 403 になるという相談が出ています。
https://discuss.python.org/t/overcoming-cloudflare-urlopen-issue/106024
Stack Overflow Meta
Stack Overflowでも、Python-urllib で始まるUser-Agentが拒否される挙動について議論されています。
https://meta.stackoverflow.com/questions/310498/why-is-user-agent-python-urllib-rejected
今回の症状は、これらの事例とかなり近い挙動でした。
archive.ubuntu.com 周りの接続性問題
また、archive.ubuntu.com 周辺では、403 Forbidden やタイムアウトに関する報告もあります。
CircleCI Support
CircleCI のサポート記事では、archive.ubuntu.com への接続で 403 Forbidden や timeout が発生する場合があり、代替ミラーへの切り替えが回避策として紹介されています。
CircleCI Discuss
CircleCI Discuss でも、archive.ubuntu.com への接続問題が報告されています。
https://discuss.circleci.com/t/connection-issues-with-apt-get-from-archive-ubuntu-com/48094
ただし、これらは主にCI環境での apt-get の接続性問題であり、今回のような do-release-upgrade 内部の Python urllib による 403 と完全に同一の症例ではありません。
あくまで、
archive.ubuntu.com 周辺で 403 や接続性問題が起こり得る
という補足材料として見ています。
対処:Python urllib でも通るミラーへ変更
次に、国内ミラーで Python urllib が通るか確認しました。
python3 - <<'PY'
import urllib.request
urls = [
"http://jp.archive.ubuntu.com/ubuntu/dists/jammy/Release",
"https://jp.archive.ubuntu.com/ubuntu/dists/jammy/Release",
"http://ftp.jaist.ac.jp/pub/Linux/ubuntu/dists/jammy/Release",
"https://ftp.jaist.ac.jp/pub/Linux/ubuntu/dists/jammy/Release",
"http://ftp.riken.jp/Linux/ubuntu/dists/jammy/Release",
"https://ftp.riken.jp/Linux/ubuntu/dists/jammy/Release",
]
for url in urls:
print("URL:", url)
try:
with urllib.request.urlopen(url, timeout=20) as r:
print("STATUS:", r.status)
except Exception as e:
print("ERROR:", repr(e))
print()
PY
jp.archive.ubuntu.com、JAIST、RIKEN などは Python urllib 標準アクセスでも 200 になりました。
今回は jp.archive.ubuntu.com を使うことにしました。
さらに、実際のアップグレードで使われる可能性がある suite も確認しました。
python3 - <<'PY'
import urllib.request
base = "http://jp.archive.ubuntu.com/ubuntu"
suites = ["jammy", "jammy-updates", "jammy-security", "jammy-backports"]
for suite in suites:
url = f"{base}/dists/{suite}/Release"
print("URL:", url)
try:
with urllib.request.urlopen(url, timeout=20) as r:
print("STATUS:", r.status)
except Exception as e:
print("ERROR:", repr(e))
print()
PY
すべて 200 でした。
/etc/apt/sources.list を変更
変更前にバックアップを取ります。
sudo cp -a /etc/apt/sources.list /etc/apt/sources.list.bak.$(date +%F-%H%M)
archive.ubuntu.com と security.ubuntu.com を jp.archive.ubuntu.com に変更します。
sudo sed -i 's|http://archive.ubuntu.com/ubuntu|http://jp.archive.ubuntu.com/ubuntu|g' /etc/apt/sources.list
sudo sed -i 's|http://security.ubuntu.com/ubuntu|http://jp.archive.ubuntu.com/ubuntu|g' /etc/apt/sources.list
変更確認:
grep -R "^[^#]" /etc/apt/sources.list
すべて jp.archive.ubuntu.com に変わっていることを確認します。
deb http://jp.archive.ubuntu.com/ubuntu focal main restricted
deb http://jp.archive.ubuntu.com/ubuntu focal-updates main restricted
...
deb http://jp.archive.ubuntu.com/ubuntu focal-security main restricted
その後、APT更新を確認します。
sudo apt update
さらに ubuntu-minimal が見えることを確認します。
apt-cache policy ubuntu-minimal
結果:
Installed: 1.450.3
Candidate: 1.450.3
do-release-upgrade を再実行
まず確認:
sudo do-release-upgrade -c
New release '22.04.x LTS' available. のように表示されればOKです。
SSH作業の場合は、tmux の中で本番実行するのが安全です。
tmux new -s ubuntu-upgrade2
sudo do-release-upgrade
既存の tmux セッション名と被る場合は、ubuntu-upgrade3 など別名で構いません。
今回はミラー変更後、以前の ubuntu-minimal could not be located の箇所を越えて、通常のアップグレード手順に戻りました。
注意点
1. いきなり sources.list を編集しない
今回のようなエラーが出た場合でも、まずは確認が必要です。
最低限、以下を確認した方がよいです。
lsb_release -a
cat /etc/apt/sources.list
sudo apt update
apt-cache policy ubuntu-minimal
通常APTで ubuntu-minimal が見えているかどうかは重要です。
2. do-release-upgrade の再実行を繰り返さない
同じ原因で止まっている場合、再実行しても同じ場所で止まります。
ログを見る方が早いです。
grep -n "No 'ubuntu-minimal'" /var/log/dist-upgrade/main.log /var/log/dist-upgrade/*/main.log
nl -ba /var/log/dist-upgrade/main.log | sed -n '45,90p'
3. apt のロックファイルを安易に消さない
途中で以下のようなエラーが出ることがあります。
Could not get lock /var/lib/apt/lists/lock
この場合、do-release-upgrade や apt のプロセスがまだ動いている可能性があります。
安易にロックファイルを削除せず、まず確認します。
ps -fp <PID>
pgrep -a "do-release-upgrade|apt|dpkg|jammy"
tmux ls
4. cloud-init 管理の sources.list に注意
今回の環境では、/etc/apt/sources.list の冒頭に以下のような注意書きがありました。
Note, this file is written by cloud-init on first boot of an instance
modifications made here will not survive a re-bundle.
cloud-init 管理の環境では、sources.list の扱いに注意が必要です。
一時的なアップグレード対応として変更する場合でも、必ずバックアップを取ってから作業した方がよいです。
今回の原因まとめ
今回の原因は、かなり高い確度で以下だと考えています。
do-release-upgrade が 20.04 focal → 22.04 jammy へ切り替える確認中、
Python urllib/httplib 経由で archive.ubuntu.com / security.ubuntu.com の
jammy Release ファイルを取得しようとしたところ 403 Forbidden になった。
そのため jammy 側のパッケージ情報を作れず、
結果として ubuntu-minimal が available/downloadable ではないと判定された。
重要なのは、ubuntu-minimal が本当に存在しなかったわけではないという点です。
通常のAPTでは ubuntu-minimal は見えていました。
apt-cache policy ubuntu-minimal
Installed: 1.450.3
Candidate: 1.450.3
しかし、do-release-upgrade 内部の取得処理では 403 になっていました。
最終的に、
archive.ubuntu.com / security.ubuntu.com
→ Python urllib 標準アクセスで 403
jp.archive.ubuntu.com
→ Python urllib 標準アクセスで 200
だったため、ミラーを jp.archive.ubuntu.com に変更することで回避できました。
参考リンク
Cloudflare / Python urllib 関連
Cloudflare Community: API call suddenly returns 403/Forbidden
https://community.cloudflare.com/t/api-call-suddenly-returns-403-forbidden/396383
Python標準ライブラリのurllibでだけ403 Forbiddenとなる
https://nikkie-ftnext.hatenablog.com/entry/python-urllib-403-cloudflare-user-agent
Python.org Discussions: Overcoming cloudflare urlopen issue
https://discuss.python.org/t/overcoming-cloudflare-urlopen-issue/106024
Stack Overflow Meta: Why is user-agent Python-urllib rejected?
https://meta.stackoverflow.com/questions/310498/why-is-user-agent-python-urllib-rejected
archive.ubuntu.com 接続性問題
CircleCI Support: Resolving Unable to connect to archive.ubuntu.com, 403 Forbidden Error in CircleCI
https://support.circleci.com/hc/en-us/articles/37474192881179-Resolving-Unable-to-connect-to-archive-ubuntu-com-403-Forbidden-Error-in-CircleCI
CircleCI Discuss: Connection issues with apt-get from archive.ubuntu.com
https://discuss.circleci.com/t/connection-issues-with-apt-get-from-archive-ubuntu-com/48094
Ubuntuミラー関連
Ubuntu Mirrors JP
https://mirrors.ubuntu.com/JP.txt
Ubuntu aptが遅かったので jp.archive.ubuntu.com を変更
https://kuni92.net/2022/10/ubuntu-apt-jparchiveubuntucom.html
おわりに
ubuntu-minimal could not be located と出ると、まず sources.list の破損やリポジトリ設定ミスを疑います。
しかし今回のように、
通常APTでは ubuntu-minimal が見える
curl では jammy Release が取れる
でも do-release-upgrade 内部では 403 になる
というケースもあります。
特に今回のような、
curl は 200
Python urllib は 403
User-Agent を変えると 200
別ミラーなら Python urllib でも 200
という挙動は、Cloudflare / Bot判定 / User-Agent / ミラー経路が絡んだ少しレアな事例だと思います。
同じエラーで詰まった場合は、ubuntu-minimal そのものだけでなく、do-release-upgrade が内部で使う通信経路も疑ってみるとよいかもしれません。
---
補足として、上の記事内の外部根拠は、Cloudflare Communityで `Python-urllib/3.x` のUser-Agentが403になった報告、Python urllibだけ403になる検証記事、Python.orgでのCloudflare proxy下の `urlopen` 403相談、CircleCIによる `archive.ubuntu.com` の403/timeoutと代替ミラー案内を確認した上で入れています。:contentReference[oaicite:0]{index=0}
::contentReference[oaicite:1]{index=1}
(おわり)