プッシュ通知が1通も届かない:APNs は HTTP/2 が必須で、PHP の curl が黙って HTTP/1.1 で送っていた
アプリ内のお知らせ(ベル)には入るのに、プッシュ通知が1通も届きませんでした。宛先も本文も送るタイミングも正しく、送信もしていました。本番のログには Unexpected HTTP/1.x request と出ていて、原因は、PHP の curl が HTTP/2 を指定されないまま HTTP/1.1 で送っていたことでした。

PHP から APNs(Apple のプッシュ通知の窓口)に通知を送っても届かず、status が 0 になる場合、原因は「HTTP/2 を指定していないこと」かもしれません。APNs は HTTP/2 でないと応答として読めません。私たちは、curl の設定で HTTP のバージョンを指定せず、本番では HTTP/1.1 で送られていました。HTTP/2 を明示し、使えない環境ではその理由を出す仕組みを足して直しました。
接客の録音アプリ(TalkLens)で、採点が終わると、スタッフに通知が届く作りです。ところが、実機で試したオーナーから、「アプリの中のベル(お知らせの一覧)には採点完了が入るのに、プッシュ通知が来ない」と報告がありました。この記事は、その原因を調べた記録です。デバイスの識別の文字列や、鍵の情報は書きません。
ベルには入るのに、プッシュ通知が来ない
アプリの中のベルには、採点完了が入っていました。つまり、サーバーは通知を作っています。それでも、iPhone のロック画面には何も出ませんでした。


本番のログに、Unexpected HTTP/1.x request と並んでいた
次に、本番のログを見ました。

APNs responded 0: ... Unexpected HTTP/1.x request: POST /3/device/...「HTTP/1.x のリクエストは予期していない」という意味の文が、並んでいました。通知の送り先の窓口が、私たちの送り方を、受け付けていなかったのです。
原因:HTTP のバージョンを指定せず、HTTP/1.1 で送っていた
私たちの PHP の送信の部品は、curl の HTTP のバージョンを指定せず、curl が相手と話し合って決める(ALPN というネゴシエーション)のに任せていました。本番のサーバーでは、その結果、HTTP/1.1 で送られていました。


直し方:HTTP/2 を明示し、使えない環境では理由を出す

curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); // APNs は HTTP/2 が必須レンタルサーバーで、確かめておくこと
レンタルサーバーの PHP と curl は、サーバーによって作り方が違います。

curl が HTTP/2 に対応しているか確かめる
PHP から curl_version() を呼ぶと、対応している機能の一覧が見られます。HTTP2 の項目があるかを確かめます。
送る前に、対応を表示する仕組みを入れる
通知を送る道具の最初に、HTTP/2 への対応を表示させます。対応していなければ、そこで止めます。
実機に通知が届くまで、ログを見る
status が 0 のままなら、HTTP のバージョンを疑います。
まとめ

- ベルには入るのにプッシュ通知が来ない時、宛先・本文・送信のタイミングが正しくても、HTTP のバージョンを疑う
- APNs は HTTP/2 が必須。HTTP/1.1 で送ると、status が 0 になり、原因が読み取りにくい
- 本番のログに Unexpected HTTP/1.x request が並んでいた
- CURL_HTTP_VERSION_2_0 を明示する。nghttp2 無しの curl は、指定しても黙って HTTP/1.1 になるので、対応を実際に確かめる
この記事は、AI(Claude)の支援を受けて作成し、筆者が内容を確認しています。


