---
title: "APNs証明書とは｜取得・更新とAPNs-410/403/400エラーの原因・再送可否"
url: "https://www.issoh.co.jp/tech/details/5280/"
published: 2025-02-12
updated: 2026-10-21
categories: ["モバイル（iOS・Android）"]
publisher: "株式会社一創"
---

# APNs証明書とは｜取得・更新とAPNs-410/403/400エラーの原因・再送可否

APNs証明書は、自社のサーバー（プロバイダ）がAppleのプッシュ通知基盤APNsに接続するときの身分証です。つまずく場所は決まっていて、アプリ用とMDM用の証明書の取り違え、1年の期限切れ、そして送信時に返る410や403の読み違えの3つです。この記事では、Appleの公式ドキュメントの記述と、2026年10月21日にAPNsの開発環境（sandbox）へ実際に送って確かめた応答をもとに、この3つを切り分けられるように整理します。

## まとめ

- 「APNs証明書」には2系統ある。アプリの通知用（Apple Developerで証明書を発行し、秘密鍵とともに`.p12`へ書き出す）と、MDMのデバイス管理用（Apple Push Certificates Portalで発行）で、発行元も更新手順も別物。
- 証明書はどちらも有効期限1年。アプリ用は期限のない認証キー（`.p8`）に切り替えれば更新作業そのものが無くなるので、新規実装は`.p8`を選ぶ。
- APNs-410はデバイストークンが無効になったという通知で、再送してはいけない。レスポンスの`timestamp`（ミリ秒）より前に取得したトークンを配信対象から外す。
- APNs-403は証明書・認証トークン側の問題。sandboxで試した範囲では、認証はトークンの形式や本文サイズより先に検査され、403が出ている間は400や413が返らなかった。403を直してから再送し、残りの問題を確かめる。
- 再送してよいのは5xx（15分後から）と`TooManyRequests`（間隔を空けて）。`BadDeviceToken`・`Unregistered`・`PayloadTooLarge`などは直さずに再送しても結果は変わらない。
- 2025年のAPNsサーバー証明書のCA変更は、自社の`.p12`を作り直す話ではない。接続するサーバー側の信頼ストアに新しいルート証明書を入れる話。

## 2系統あるAPNs証明書：アプリ用とMDM用の違い

APNs（Apple Push Notification service）は、Appleが運用する通知の中継基盤です。通知を送る自社サーバー、中継するAPNs、受け取るiPhoneやMacの3者がいて、サーバーがAPNsに接続するときに提示するのがAPNs証明書です。厄介なのは、この一語が用途の違う2種類を指していることで、検索して出てくる手順が食い違って見えるのはたいていこの混同が原因です。

| 項目      | アプリ用                | MDM用               |
| ------- | ------------------- | ------------------ |
| 正式な呼び名  | APNs SSL証明書／認証キー    | MDMプッシュ証明書         |
| 発行場所    | developer.apple.com | identity.apple.com |
| 用途      | 自社サーバーからアプリへ通知      | MDMがデバイスを管理        |
| 紐づく単位   | App ID（Bundle ID）   | 作成したAppleアカウント     |
| 有効期限    | 証明書1年・.p8は無期限       | 1年                 |
| 期限なしの代替 | .p8トークン認証           | なし（毎年更新）           |

iOSアプリにプッシュ通知を実装したいならアプリ用です。Intune・Jamf・mobiconnectなどのMDMから「Apple Push Certificates Portalにサインインして」と指示されたならMDM用で、アプリ開発とは関係がありません。以降の章も、どちらの話かを見出しで分けています。

### アプリ用認証で.p8を推奨する理由

アプリ用の接続方式は、証明書（`.p12`）で接続する方式と、認証キー（`.p8`）で署名したJWTを毎リクエストに付ける方式の2つです。新規に組むなら`.p8`一択です。Appleのヘルプは署名キーについて「The signing key doesn’t expire, but can be revoked.」と書いており、年1回の更新作業が構造的に無くなります。1つのキーを複数アプリで使える点も証明書には無い利点です（[Apple Developer ヘルプ「Communicate with APNs using authentication tokens」](https://developer.apple.com/help/account/capabilities/communicate-with-apns-using-authentication-tokens/)）。

証明書を選ぶ理由が残るのは、既存の配信基盤やSDKが`.p12`しか受け付けない場合に限られます。証明書の更新漏れで通知が止まる事故は、`.p8`へ移した時点で起きなくなります。

## アプリ用のAPNs証明書・認証キーを発行する手順

どちらの方式も、Apple Developer Programの有料メンバーシップが前提です。年会費や法人登録に必要なD-U-N-S番号は[Apple Developer Programの年会費と登録方法](/tech/details/7317/)にまとめています。

### 証明書（.p12）を発行する4ステップ

1. **CSRを作る**：Macの「キーチェーンアクセス」で「証明書アシスタント」→「認証局に証明書を要求」。メールアドレスと通称を入れ、「ディスクに保存」を選ぶ。秘密鍵はこのMacのキーチェーンに残るため、後の書き出しも同じMacで行う（[Apple「Create a certificate signing request」](https://developer.apple.com/help/account/certificates/create-a-certificate-signing-request)）。
2. **App IDでPush Notificationsを有効にする**：Certificates, Identifiers & Profiles → Identifiers で対象のBundle IDを開き、Push Notificationsにチェックを入れる。
3. **証明書を発行する**：Certificates → ＋ →「Apple Push Notification service SSL (Sandbox & Production)」を選び、App IDを指定してCSRをアップロードし、`.cer`をダウンロードする。
4. **.p12に書き出す**：`.cer`をキーチェーンに取り込み、秘密鍵と一緒に選んで「書き出す」→`.p12`形式で保存する。サーバーにはこの`.p12`とパスフレーズを設定する。

手順3の証明書名に「Sandbox & Production」とあるとおり、この証明書1枚で開発環境と本番環境の両方に接続できます。

### 認証キー（.p8）の作成とTeam Scoped・Topic Specificの違い

認証キーはCSRもApp IDごとの証明書も要りません。Certificates, Identifiers & Profiles の Keys で＋を押し、「Apple Push Notification service」にチェックを入れて作ります。作成できるのはAccount HolderかAdminの役割を持つメンバーだけです。

現在は「Configure」から環境（Sandbox・Production）とキーの種類を選ぶ画面があり、チーム内の全アプリに使える**Team Scoped**と、選んだトピック（Bundle ID）にだけ使える**Topic Specific**を選べます。どちらも環境を1つに限定して作るので、開発用と本番用で別のキーになります（[Apple「Create a private key to access a service」](https://developer.apple.com/help/account/keys/create-a-private-key/)）。Team Scopedは環境ごとに最大2本、Topic Specificは環境ごとに最大200本まで作れ、1本に最大400トピックを指定できます（[Apple「Establishing a token-based connection to APNs」](https://developer.apple.com/documentation/usernotifications/establishing-a-token-based-connection-to-apns)）。この2種類は2025年2月17日に追加されたもので、それ以前に作ったキーは引き続き全トピック・両環境で使えます（[Apple Developer News「New features for APNs token authentication are now available」](https://developer.apple.com/news/?id=wy4tb0uo)）。キーが漏れたときの影響範囲を1アプリに閉じたいならTopic Specificを選びます。

ダウンロードは1回きりです。Appleのヘルプは「the key is not saved in your developer account and you won’t be able to download it again」と警告しており、ファイルを失えばキーを作り直すしかありません。サーバーには`.p8`ファイル、Key ID（10文字）、Team ID（10文字）の3点を渡します。Firebase Cloud Messaging経由でiOSへ送る構成でも、Firebaseコンソールに登録するのはこの`.p8`です。FCM側の設定は[FCM（Firebase Cloud Messaging）の仕組みと送信方法](/tech/details/3448/)、配信サービスを使う場合は[OneSignalの実装手順とFCM直接送信との使い分け](/tech/details/18353/)を参照してください。

## MDM用のAPNsプッシュ証明書を取得・更新する手順

MDMの運用担当者向けの章です。MDM製品の管理画面からCSRをダウンロードし、Apple Push Certificates Portal（`identity.apple.com`）にアップロードして`.pem`を受け取り、MDMへ登録します。Microsoft IntuneやJamf Proなど製品ごとに画面は違いますが、Apple側の手順は同じです（[Microsoft Learn「Intune 用の Apple MDM プッシュ証明書を取得する」](https://learn.microsoft.com/ja-jp/intune/device-enrollment/apple/create-mdm-push-certificate)）。

### MDM証明書の更新：同じAppleアカウントでのRenew

MDM証明書は、更新を誤ったときの被害がもっとも大きい場所です。既存証明書の「Renew」ではなく新規作成した証明書をMDMへ登録し、従来の証明書と置き換えると、既存デバイスの再登録が必要になります。Microsoft Learnは、証明書は作成に使ったAppleアカウントに紐づくため同じアカウントで更新すること、削除して作り直した場合はデバイスを再登録する必要があることを明記しています。Jamf Proのドキュメントも「must be renewed using the same Apple Account that was used to generate it」と同じ条件を書いています。期限切れ後には30日の猶予期間があるとも書かれていますが、猶予に頼る運用はしないでください。

対策は、作成に使ったアカウントと期限を担当者個人でなく組織の台帳に残すことです。個人のApple Accountで作ると、退職後にそのアカウントへアクセスできず、更新が滞るおそれがあります。Appleの導入ガイドも、作成に使った管理対象Appleアカウント（推奨）またはAppleアカウントを書き留めておくよう求めています（[Apple「APNを利用できるようにデバイスを構成する」](https://support.apple.com/ja-jp/guide/deployment/dep2de55389a/web)）。

## APNsのレスポンスコード一覧と再送してよいかの判断

通知が届かないときの切り分けは、APNsが返すHTTPステータスとレスポンス本文の`reason`を読むところから始めます。Appleが定義しているステータスは次の10種類で、ステータスごとに疑う場所と再送の可否がはっきり分かれます（[Apple「Handling notification responses from APNs」](https://developer.apple.com/documentation/usernotifications/handling-notification-responses-from-apns)）。

| ステータス | 公式の意味          | 疑う場所          | 再送     |
| ----- | -------------- | ------------- | ------ |
| 200   | APNsが受理        | 配信ログ・端末設定     | ―      |
| 400   | 不正なリクエスト       | トークン・トピック・ヘッダ | 修正後    |
| 403   | 証明書・認証トークンのエラー | .p12・.p8・JWT  | 修正後    |
| 404   | :path が不正      | リクエストURL      | 修正後    |
| 405   | :method が不正    | POST以外で送信     | 修正後    |
| 410   | トークンがそのトピックで無効 | デバイストークン      | しない    |
| 413   | ペイロードが大きすぎる    | 本文4KB超        | しない    |
| 429   | 同じトークンへの送信過多   | 送信頻度・JWT更新頻度  | 間隔を空けて |
| 500   | APNs内部エラー      | APNs側         | 15分後から |
| 503   | サーバー停止中        | APNs側         | 15分後から |

再送の規則は公式に書かれています。5xxは15分経ってから再送でき、再送時にはバックオフを使ってよい。4xxの多くは`reason`の原因を直せば再送できますが、`BadDeviceToken`・`DeviceTokenNotForTopic`・`Forbidden`・`ExpiredToken`・`Unregistered`・`PayloadTooLarge`は再送しないよう明記されています。`TooManyRequests`だけは待てば再送してかまいません。

見落とされやすいのが接続への影響です。4xxが続くと送信できる速度が落ち、エラーの多い接続はAPNsから切断されます。`BadDeviceToken`はとくに早く切られます。一方で410はエラー条件に数えないと書かれており、無効トークンへの送信が溜まっても接続は切られにくい代わりに、送るだけ無駄な通信が続きます。

### APNs-410：timestamp（ミリ秒）に基づく無効トークンの除外

410は「The device token is no longer active for the topic.」、つまりそのアプリに対してデバイストークンがもう有効ではないという意味です。`reason`は`Unregistered`（トークンが非アクティブ）と`ExpiredToken`（トークンの期限切れ）の2種類で、アプリの削除や端末の初期化で返ります。公式は`Unregistered`について、アプリが同じトークンを再取得しない限り同じトークンへ送る必要はないと説明しています。

実装の要点は、410のときだけレスポンスに含まれる`timestamp`です。APNsがトークンの無効を確認した時刻で、単位は**Epochからのミリ秒**です。秒として変換すると5万年以上先の日付になります。この時刻より前に受け取ったトークンは削除し、後でアプリが再登録して届いた同じ値は有効として残します。410を受けて登録時刻を確認せずに削除すると、再登録直後の有効なトークンまで消し、通知を送れなくなることがあります。

### APNs-403：認証・権限に関する8つのreason

403はデバイスではなく、証明書・認証トークン、または送ろうとした操作の権限の問題です。`reason`で原因を絞り込めます。

- `InvalidProviderToken`：JWTが無効、または署名を検証できない。Key ID・Team ID・`.p8`の組み合わせ違い、ES256以外の署名で起きる。
- `ExpiredProviderToken`：JWTの`iat`が1時間より古い。トークンを作り直す。
- `MissingProviderToken`：証明書も`authorization`ヘッダも無い。
- `BadCertificate`：証明書が無効。
- `Forbidden`：指定した操作が許可されていない。同じ通知は再送しない。
- `BadCertificateEnvironment`：証明書と接続先の環境が一致しない。
- `UnrelatedKeyIdInToken`：同じ接続の最初の送信で使ったキーと無関係なキーを使った。Topic Specificキーを混ぜると起きるので、新しい接続を開く。
- `BadEnvironmentKeyIdInToken`：キーの環境（Sandbox・Production）と接続先が食い違う。

JWTの更新頻度にも上限があります。Appleは「Refresh your token no more than once every 20 minutes and no less than once every 60 minutes.」と定めており、毎リクエストでJWTを作り直すと429の`TooManyProviderTokenUpdates`で弾かれます（[Apple「Establishing a token-based connection to APNs」](https://developer.apple.com/documentation/usernotifications/establishing-a-token-based-connection-to-apns)）。作ったJWTは20〜60分の間キャッシュして使い回してください。

### APNs-400：トークンの無効・環境不一致・トピック不一致

400で多い`reason`は`BadDeviceToken`と`DeviceTokenNotForTopic`です。環境の取り違えも原因の一つで、Xcodeから入れた開発ビルドのトークンを本番サーバー（`api.push.apple.com`）へ送ると`BadDeviceToken`になります。開発ビルドのトークンは`api.sandbox.push.apple.com`へ送ります。`DeviceTokenNotForTopic`は、`apns-topic`のBundle IDとトークンを発行したアプリが一致しないときに返ります。

ほかの400は名前どおりで、`TopicDisallowed`はそのトピックへの送信が許可されていない、`MissingTopic`は複数トピックに対応した証明書なのに`apns-topic`が無い、`InvalidPushType`は`apns-push-type`の値が不正、という意味です。`apns-push-type`はwatchOS 6以降で必須、iOS・macOSなどでは推奨とされており、通知の中身と食い違うと配信が遅れたり捨てられたりします（[Apple「Sending notification requests to APNs」](https://developer.apple.com/documentation/usernotifications/sending-notification-requests-to-apns)）。

### APNs-405・404・413・429の原因

405の`MethodNotAllowed`はPOST以外で送ったとき、404の`BadPath`はパスが`/3/device/<トークン>`の形になっていないときに返ります。413の`PayloadTooLarge`は本文が4KB（4,096バイト）を超えたときで、VoIP通知だけは5KBまで送れます。429は同じトークンへの連続送信（`TooManyRequests`）か、JWTの更新しすぎ（`TooManyProviderTokenUpdates`）のどちらかです。

### 公式一覧にない409・418などを受けたときの切り分け

「apns-409」「apns-418」のように検索されるコードは、Appleの一覧にありません。APNsが返すステータスは上の表の10種類だけなので、409や418を受け取ったなら、間に挟まっている配信サービス、SDK、社内のプロキシやAPIゲートウェイが自前で返していると考えて、そちらのログとドキュメントを先に確認します。`apns-id`は通知を照合する識別子です。応答元は接続先と中継サービスのログを合わせて確認します。

## curl・Node.jsによるAPNs sandboxへの送信テスト

アプリやSDKを経由すると、どこでエラーが起きたのかが見えにくくなります。サーバーから直接APNsへ1通送れば、証明書・キー・トークンのどれが悪いのかをその場で切り分けられます。Appleも同じ目的でコマンドライン手順を公開しています（[Apple「Sending push notifications using command-line tools」](https://developer.apple.com/documentation/usernotifications/sending-push-notifications-using-command-line-tools)）。テスト中は対象のiPhoneのロックを解除し、アプリを前面に出しておきます。

### .p12のPEM変換とcurlによる通知送信

curlは`.p12`をそのまま読めない環境があるため、先にPEMへ変換します。ここで作るPEMには暗号化されていない秘密鍵が含まれます。作成後に`chmod 600 apns.pem`でアクセス権を制限し、リポジトリへ登録せず、テスト後に不要なら削除してください。

```
# .p12 から証明書と秘密鍵を1つのPEMに書き出す（-nodes＝鍵を暗号化しない）
openssl pkcs12 -in apns.p12 -out apns.pem -nodes

# 開発環境へ1通送る（DEVICE_TOKENは開発ビルドのトークン）
curl -v --http2 \
  --cert apns.pem \
  --header "apns-topic: com.example.app" \
  --header "apns-push-type: alert" \
  --data '{"aps":{"alert":"test"}}' \
  https://api.sandbox.push.apple.com/3/device/${DEVICE_TOKEN}
```

APNsが受理するとステータス200が返ります。端末での「test」の表示には通知の許可が必要で、アプリを前面に出したまま確認する場合は前景通知の表示処理も必要です。証明書ではなく`.p8`を使う場合は、次のNode.jsスクリプトのほうが手早く確かめられます。

### .p8でJWTを作って送るNode.jsスクリプト

外部ライブラリを使わず、Node.js標準の`crypto`と`http2`だけで書いています。Node.js 26.5.0で動作を確認しました。

```
// 使い方: TEAM_ID=... KEY_ID=... KEY_FILE=AuthKey_XXXX.p8 TOPIC=com.example.app DEVICE_TOKEN=... node send.mjs
import { readFileSync } from "node:fs";
import { createPrivateKey, sign } from "node:crypto";
import http2 from "node:http2";

const { TEAM_ID, KEY_ID, KEY_FILE, TOPIC, DEVICE_TOKEN } = process.env;
const HOST = process.env.APNS_HOST ?? "api.sandbox.push.apple.com";

const b64url = (buf) => Buffer.from(buf).toString("base64url");
const header = b64url(JSON.stringify({ alg: "ES256", kid: KEY_ID }));
const claims = b64url(JSON.stringify({ iss: TEAM_ID, iat: Math.floor(Date.now() / 1000) }));
const key = createPrivateKey(readFileSync(KEY_FILE));
// JWTのES256署名は r||s の64バイト形式にする（既定のDER形式は約70バイト）
const sig = sign("sha256", Buffer.from(`${header}.${claims}`), { key, dsaEncoding: "ieee-p1363" });
const jwt = `${header}.${claims}.${b64url(sig)}`;

const client = http2.connect(`https://${HOST}`);
const req = client.request({
  ":method": "POST",
  ":path": `/3/device/${DEVICE_TOKEN}`,
  "authorization": `bearer ${jwt}`,
  "apns-topic": TOPIC,
  "apns-push-type": "alert",
  "apns-priority": "10",
});
const fail = (err) => {
  console.error(err.message);
  process.exitCode = 1;
  client.destroy();
};
client.on("error", fail);
req.on("error", fail);
req.setTimeout(15000, () => fail(new Error("APNs request timed out")));
req.setEncoding("utf8");
let body = "";
req.on("response", (h) => console.log("status", h[":status"], "apns-id", h["apns-id"]));
req.on("data", (c) => (body += c));
req.on("end", () => { console.log("body", body || "(empty)"); client.close(); });
req.end(JSON.stringify({ aps: { alert: { title: "テスト", body: "APNs疎通確認" } } }));
```

JWTのヘッダは`alg`（ES256固定）と`kid`（Key ID）、本体は`iss`（Team ID）と`iat`（発行時刻・秒）の2項目だけです。本番で使うときは、このJWTを20〜60分キャッシュして使い回してください。APNsは1本の接続で複数のストリームを並行して受け付けるため、接続も通知ごとに開き直す必要はありません。

### sandboxで実測した応答：トークン・本文より先に返る403

2026年10月21日に、Appleに登録していないテスト用のキーと自己署名証明書を使って、`api.sandbox.push.apple.com`へわざと誤ったリクエストを送り、返ってきた応答を記録しました。使ったのはcurl 8.7.1（macOS標準）・OpenSSL 4.0.3・Node.js 26.5.0で、デバイストークンは乱数で作った64桁の16進数です。Appleに登録済みのキーや実機を使った試験ではないため、認証を通った後の応答（200や410）はこの表に含まれていません。

| 送ったリクエスト            | 返った応答                    |
| ------------------- | ------------------------ |
| authorization ヘッダなし | 403 MissingProviderToken |
| 未登録キーで署名したJWT       | 403 InvalidProviderToken |
| 同上・iatを2時間前に設定      | 403 InvalidProviderToken |
| 同上・本文を5KBに          | 403 InvalidProviderToken |
| 認証なし・トークン4文字        | 403 MissingProviderToken |
| GETで送信              | 405 MethodNotAllowed     |
| パスを /2/device/ に    | 404 BadPath              |
| ポート2197へ送信          | 403 MissingProviderToken |
| HTTP/1.1で送信         | HTTPの応答なし                |
| 自己署名のクライアント証明書      | TLSで切断（unknown ca）       |

読み取れることは3つあります。1つ目は、今回の組み合わせでは、メソッドとパスの誤りが認証より先に405・404として返り、トークンの形式や本文サイズの誤りは認証の403に隠れたことです。Appleは検査の順序を公表していないので、これは仕様ではなく観測結果として扱ってください。本文を5KBにしても413は返らず、4文字のトークンでも400は返りませんでした。403を直したら次は400や413が出る、という順番で問題が見えてくると考えておくと、修正のたびに別のエラーが出ても慌てずに済みます。

2つ目は、JWTの`iat`が古くても、キーそのものが無効なら`ExpiredProviderToken`ではなく`InvalidProviderToken`が返ることです。`InvalidProviderToken`が出たら、時計のずれより先にKey ID・Team ID・`.p8`の組み合わせを疑ってください。3つ目は、Appleが発行していない証明書ではHTTPのステータスすら返らず、TLSの段階で「unknown ca」として切られることです。`openssl s_client`で接続すると、APNsが受け付けるクライアント証明書の発行元が本番で7件、開発環境で8件表示され、すべてApple Inc.の認証局（Apple Root CA、Apple Worldwide Developer Relations Certification Authority など）でした。証明書方式でcurlが終了コード56で落ちるなら、`.p12`の中身がApple発行の証明書か、秘密鍵と対になっているかを確かめます。

### Push Notifications Consoleによる配信状況の確認

200が返っても端末に届かないときは、Appleの[Push Notifications Console](https://developer.apple.com/documentation/usernotifications/viewing-the-status-of-push-notifications-using-metrics-and-apns)で配信状況を確認します。開発環境の応答ヘッダには`apns-unique-id`が付き、この値でコンソールのDelivery Logを引くと、配信済み・保存中・破棄のどれになったかが分かります。Delivery Logは開発環境だけの機能で、送信から最大7日間参照できます。破棄の理由には、トークンが無効になった、ユーザーが設定で通知を切った、`apns-expiration`を過ぎた、があります。

コンソールにはJWT Validator（認証トークンの検証）とDevice Token Validator（Bundle IDに対するトークンの検証）もあり、上のNode.jsスクリプトで作ったJWTが正しいかを、送信前に確かめられます（[Apple「Testing notifications using the Push Notifications Console」](https://developer.apple.com/documentation/usernotifications/testing-notifications-using-the-push-notification-console)）。コンソールからのテスト送信は、開発環境ならチームの全メンバー、本番環境はAdminの役割を持つメンバーだけが使えます。

## 有効期限の確認と更新運用

証明書について、Appleは「Provider certificates are valid for a year and you must update them to continue communicating with APNs.」と書いています（[Apple「Establishing a certificate-based connection to APNs」](https://developer.apple.com/documentation/usernotifications/establishing-a-certificate-based-connection-to-apns)）。期限はApple Developerの証明書一覧で見られますが、サーバーに置いた`.p12`がどれなのかを画面と突き合わせるより、ファイルを直接調べるほうが確実です。

### .p12の期限をopensslで確かめるコマンド

```
# 有効期限（notAfter）とBundle IDを表示する
openssl pkcs12 -in apns.p12 -clcerts -nokeys | openssl x509 -noout -subject -enddate

# 30日以内に切れるなら終了コード1（監視スクリプトに組み込める）
openssl pkcs12 -in apns.p12 -clcerts -nokeys -passin pass:"$P12_PASS" \
  | openssl x509 -noout -checkend $((30*24*60*60))
```

`-subject`の`UID=`にBundle IDが入るので、どのアプリ用の証明書かも同時に分かります。`-checkend`は指定した秒数以内に期限が来ると終了コード1を返すため、cronやCIに入れておけば更新漏れを事前に検知できます。

古い暗号方式（RC2-40）で書き出された`.p12`は、OpenSSL 3.0以降でそのまま読むと「unsupported」「RC2-40-CBC」を含むエラーで止まります。手元のOpenSSL 4.0.3でも同じエラーが出て、`-legacy`を付けると読めました。今回確認したmacOS 26.6.2の`/usr/bin/openssl`はLibreSSL 3.3.6で、このオプションなしで読めます。

### アプリ用APNs証明書の差し替えと旧証明書の失効順序

証明書の更新は、新しい証明書を発行してサーバーに配置し、送信が200で通るのを確かめてから古い証明書を失効させる、という順で行います。Appleの一覧で先に古い証明書を失効させると、新しい証明書を配置するまでの間は通知が止まります。毎年この作業をするくらいなら、更新のタイミングで`.p8`に移すのが根本的な解決です。

## 接続要件と2025年のAPNsサーバー証明書の変更

APNsへの接続はHTTP/2とTLS 1.2以上が必須です。接続先は開発環境が`api.sandbox.push.apple.com`、本番が`api.push.apple.com`で、ポートは443のほか2197も使えます。2197は、ファイアウォールでほかのHTTPS通信を止めつつAPNsだけを通したい場合のためのポートです。HTTP/1.1では応答が返らないことは、上の実測でも確かめました。

### 2025年のCA変更：信頼ストアの更新と.p12再発行の不要性

Appleは2024年10月17日に、APNsのサーバー証明書の認証局（CA）を変更すると告知しました。切り替えは開発環境が2025年1月20日、本番が2025年2月24日です。開発者に求められたのは、接続するサーバーの信頼ストアに新しいルート証明書「USERTrust RSA Certification Authority」を加えることで、告知には「you don’t need to update the APNs SSL provider certificates issued to you by Apple」とはっきり書かれています（[Apple Developer News「Apple Push Notification service server certificate update」](https://developer.apple.com/news/?id=09za8wzy)）。

「APNsの証明書更新」という言葉から、自社の`.p12`を作り直すことだと読み違えやすい告知でした。2026年10月21日に`openssl s_client`で両環境へ接続すると、サーバー証明書は「Apple Public Server RSA CA 11 – G1」が発行し、その上位が「USERTrust RSA Certification Authority」になっていました。切り替え後に接続エラーが出ているサーバーは、自社証明書ではなく、OSやJavaの信頼ストアが古いままになっていないかを確認してください。

iOSアプリの通知基盤の設計や、既存アプリの`.p8`移行を外部に頼みたい場合は、[iOSアプリ開発の支援](https://www.issoh.co.jp/service/apps/ios/)で相談を受け付けています。

## よくある質問

### APNs証明書とは何ですか？

自社のサーバーがAppleのプッシュ通知基盤APNsに接続するときに使う、Appleが発行するクライアント証明書です。アプリの通知用（developer.apple.comで証明書を発行し、秘密鍵とともに`.p12`へ書き出す）と、MDMがiPhoneやMacを管理するための証明書（identity.apple.comで発行）の2系統があり、どちらも有効期限は1年です。アプリ用には期限のない認証キー（`.p8`）という代わりの方式もあります。

### APNs-410が返ってきたらどうすればよいですか？

そのデバイストークンへの送信を止め、配信対象から外します。410はアプリの削除や端末の初期化でトークンが無効になったという通知で、再送しても届きません。レスポンスの`timestamp`はミリ秒単位の時刻で、この時刻より前に受け取ったトークンを削除し、後から再登録された同じトークンは残します。

### APNs-403のInvalidProviderTokenは何が原因ですか？

JWTの署名を検証できないときに返ります。Key IDとTeam IDと`.p8`の組み合わせが合っていない、ES256以外で署名している、のどちらかが典型です。2026年10月21日のsandboxでの実測では、登録されていないキーで`iat`を2時間前にしても`ExpiredProviderToken`ではなく`InvalidProviderToken`が返りました。時計のずれより先に3点の組み合わせを確認してください。

### APNs証明書とMDMのプッシュ証明書は同じものですか？

別物です。アプリ用はApple DeveloperでApp IDごとに発行し、自社サーバーからアプリへ通知を送るために使います。MDM用はApple Push Certificates Portalで発行し、MDM製品がデバイスを管理するために使います。MDM用は作成時と同じAppleアカウントでRenewしないと、管理中のデバイスを登録し直すことになります。

### 開発用（Sandbox）と本番用で証明書を分ける必要はありますか？

分ける必要はありません。現在の「Apple Push Notification service SSL (Sandbox & Production)」証明書は1枚で両方の環境に接続できます。`.p8`は、2025年2月より前に作ったキーなら両環境で使えますが、いま新しく作るTeam Scoped・Topic Specificのキーは環境を1つ選んで作るため、開発用と本番用の2本が必要です。分けるべきなのはデバイストークンで、開発ビルドのトークンは`api.sandbox.push.apple.com`、App StoreやTestFlightのトークンは`api.push.apple.com`へ送ります。取り違えると400の`BadDeviceToken`になります。

## 関連記事

- [FCM（Firebase Cloud Messaging）とは｜プッシュ通知の仕組み・送信方法とFID移行【2026年10月時点】](/tech/details/3448/)
- [OneSignalとは？プッシュ通知の料金と実装手順、FCM直接送信との使い分け](/tech/details/18353/)
- [Apple Developer Programとは？年会費99ドル（アプリ経由12,800円）の中身・登録方法・ログイン先](/tech/details/7317/)
- [React Native Firebaseとは｜v26の新アーキ必須化とExpo・ベア別の導入と落とし穴【2026年8月時点】](/tech/details/17052/)
- [Electron製Macアプリのコード署名とApple公証｜notarytoolとentitlements設定の実測手順](/tech/details/6874/)

---

出典: [APNs証明書とは｜取得・更新とAPNs-410/403/400エラーの原因・再送可否](<https://www.issoh.co.jp/tech/details/5280/>)（株式会社一創）
