ACF PRO 5.5.11が入ったサイトを最新の6.8.7に上げました。ライセンスキーは登録済みなのにwp plugin updateが「更新パッケージは利用できません」で失敗し、昔よく使われていたダウンロードURLも今は404を返します。最終的にComposerリポジトリから正規のzipを取得して解決しました。9年分バージョンが飛ぶ更新で何を検証したかも書きます。
更新コマンドが「更新パッケージは利用できません」で止まる
管理画面のカスタムフィールド → 更新でライセンスキーは有効化済みの状態です。それでもwp-cliの更新が通りません。
$ wp plugin update advanced-custom-fields-pro
メンテナンスモードを有効にします…
Warning: 更新パッケージは利用できません。
メンテナンスモードを無効にします…
name old_version new_version status
advanced-custom-fields-pro 5.5.11 6.8.7 Error
Error: No plugins updated (1 failed).
注目すべきはnew_versionに6.8.7が正しく表示されている点です。つまり「最新版が存在すること」の確認まではできていて、ダウンロードの段階で失敗しています。ライセンスキーが無効なわけではありません。
原因は、5.5.11に組み込まれた更新処理が古すぎることです。このバージョンがリリースされたのは2017年で、当時のACFのダウンロードエンドポイントを叩きに行きます。そのエンドポイントは現在存在しません。
昔のダウンロードURLは404になっている
ACF PROを手動でダウンロードする方法として、ライセンスキーをURLに含める形式が長く知られていました。
curl -o acf-pro.zip \
"https://connect.advancedcustomfields.com/index.php?a=download&p=pro&k=ライセンスキー"
これを実行すると、zipではなくHTMLが返ってきます。
$ file acf-pro.zip
acf-pro.zip: HTML document text, ASCII text, with CRLF line terminators
$ cat acf-pro.zip
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx</center>
</body>
</html>
curlは成功扱いで終わり、拡張子が.zipのファイルが出来上がるので、確認せずにインストールへ進むと「パッケージを展開できません」で詰まります。ダウンロード後にfileコマンドで中身を確認する癖をつけておくと、この種の事故はすぐ分かります。
ComposerリポジトリからダウンロードURLを引く
現在のACF PROはComposer経由の配布に対応していて、そのリポジトリはバージョン一覧をJSONで公開しています。このJSONは認証なしで取得できます。
curl -s https://connect.advancedcustomfields.com/packages.json -o packages.json
中身を見ると、PRO版と無料版のパッケージが入っています。
$ python3 -c "
import json
d = json.load(open('packages.json'))
pkgs = d['packages']
print('パッケージ:', list(pkgs.keys()))
vers = list(pkgs['wpengine/advanced-custom-fields-pro'].values())
print('バージョン数:', len(vers))
print('最新:', vers[0]['version'])
print('dist:', vers[0]['dist']['url'])
"
パッケージ: ['wpengine/advanced-custom-fields-pro', 'wpengine/advanced-custom-fields']
バージョン数: 175
最新: 6.8.7
dist: https://connect.advancedcustomfields.com/v2/plugins/composer_download?s=composer&p=pro&t=6.8.7
これが現在のダウンロードURLです。バージョン番号がt=パラメータで指定されているので、特定のバージョンを狙って取得することもできます。
ダウンロードには認証が必要で、その渡し方が独特です。ユーザー名がライセンスキー、パスワードがライセンスを有効化しているサイトのURL(https://込み)というBasic認証です。
curl -sL -u "ライセンスキー:https://example.com" \
-o acf-pro-687.zip \
"https://connect.advancedcustomfields.com/v2/plugins/composer_download?s=composer&p=pro&t=6.8.7"
今度は正しくzipが返ります。
$ file acf-pro-687.zip
acf-pro-687.zip: Zip archive data, at least v2.0 to extract, compression method=deflate
$ unzip -l acf-pro-687.zip | head -5
Archive: acf-pro-687.zip
Length Date Time Name
--------- ---------- ----- ----
1760 08-03-2026 19:29 advanced-custom-fields-pro/README.md
29410 08-03-2026 19:29 advanced-custom-fields-pro/acf.php
あとは--forceで上書きインストールします。
wp plugin install /tmp/acf-pro-687.zip --force
パッケージを展開しています…
プラグインをインストールしています…
プラグインの古いバージョンを削除しています…
プラグインの更新に成功しました。
Success: Installed 1 of 1 plugins.
ライセンスキーはDBに保存されたままなので、この方法で入れ替えても再入力は不要です。次回以降は6.8.7の更新処理が動くので、通常のwp plugin updateが使えるようになります。
なお継続的にComposerで管理するなら、auth.jsonに同じ認証情報を書いてcomposer require wpengine/advanced-custom-fields-proで入れる方法が公式に案内されています。単発の更新なら上のcurlで足ります。
9年分の差を埋める更新で確認したこと
5.5.11から6.8.7は約9年分のジャンプです。フィールドのデータ形式自体は互換が保たれているのでDBが壊れる心配は薄いのですが、出力側で変わったところがあります。6.2.5でHTMLエスケープの扱いが変更されており、フィールドにHTMLを保存してthe_field()で出力している箇所があると、タグが文字として表示される可能性があります。
そこで本番に適用する前に、ローカルのDocker環境で先に更新して検証しました。まずテーマ内でACFを使っている箇所を洗い出します。
grep -rln "the_field\|get_field\|have_rows\|get_sub_field" --include='*.php' wp-content/themes/
該当したのは3ファイルだけでした。次にそのテンプレートを使っているページを実際に表示し、HTTPステータスとエラー文字列、エスケープ崩れの有無を機械的に見ます。
curl -sL -o page.html http://localhost:8090/acf-using-page/
grep -o '<' page.html | wc -l # エスケープ崩れの検出
grep -c 'Fatal error\|Warning:' page.html
ここまでは「エラーが出ていないこと」の確認です。もう一歩踏み込んで、更新後のローカルと更新前の本番で、同じページのACF出力が一致するかを比べました。
curl -sL -o prod.html https://example.com/acf-using-page/
python3 -c "
import re
for f in ['page','prod']:
html = open(f + '.html', encoding='utf-8', errors='ignore').read()
tds = re.findall(r'<td>(.*?)</td>', html, re.S)
open(f + '-tds.txt','w').write('\n'.join(t.strip()[:80] for t in tds))
"
diff page-tds.txt prod-tds.txt && echo 'ACF出力は一致'
ACFのフィールド値を並べているテーブルの<td>だけを抜き出して正規化し、差分を取る方法です。ページ全体をdiffすると日付やアクセスカウンタなどの動的要素で必ず差が出るので、確認したい部分だけに絞ります。これで差分ゼロなら、少なくともそのページのフィールド出力は変わっていないと言えます。
本番適用後は同じ手順を、更新前に保存しておいたHTMLに対して実行しました。
diff prod-before-tds.txt prod-after-tds.txt && echo '更新前後で一致'
目視で「たぶん大丈夫」と判断するより、確認した範囲がはっきりします。今回はローカル検証と本番適用の両方で一致し、php-fpmのエラーログにも新しいFatalは出ていません。
検証したのはフロント側の表示です。管理画面のフィールド編集UIは6系で大きく変わっているので、そちらは更新後に一度自分の目で見ておく必要があります。
ローカル検証で見つかった別の問題
この検証中、ローカル環境だけPHPの致命的エラーで500が返る場面がありました。
PHP Fatal error: Array and string offset access syntax with curly braces
is no longer supported in .../wordpress-seo/vendor/yoast/api-libs/google/service/Google_Utils.php on line 58
波括弧による文字列オフセット参照はPHP 8.0で削除された記法です。原因はACFではなく、ローカルに残っていた古いSEOプラグインでした。ローカルのwp-contentが本番よりかなり古いスナップショットだったために、PHP 8.3では動かないファイルが残っていたわけです。
ローカル検証は本番と同じ構成でこそ意味があります。今回は検証対象がACFだったので該当プラグインを一時的に無効化して進めましたが、こういうズレが見つかった時点で、ローカル環境そのものを本番から作り直す判断も選択肢になります。
更新前に取っておいたもの
本番へ適用する前に、プラグインのファイル一式とデータベースのフルダンプを取りました。
cd /path/to/wordpress/wp-content/plugins
sudo tar czf /path/to/backup/acf-pro-5.5.11-backup.tar.gz advanced-custom-fields-pro
cd /path/to/wordpress
sudo -u apache wp db export - | sudo tee /path/to/backup/db-before-acf-update.sql > /dev/null
ACFはフィールド定義を投稿として、値をpostmetaとして持つので、切り戻す時はファイルとDBの両方が必要になります。ファイルだけ戻してもDBに新しいバージョンが書いた情報が残っていると噛み合いません。
今回は切り戻さずに済みましたが、9年分飛ぶ更新で「戻せる状態」を作らずに本番から始めるのは避けたいところです。ローカルで先に通し、出力を比較して一致を確認し、バックアップを取ってから本番。手間は増えますが、この順番なら途中で何が起きても引き返せます。