You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NestJS微服务中FCM sendAll()报PEM私钥无起始行错误排查

NestJS调用Firebase FCM sendAll()报PEM格式错误的排查与解决

问题背景

在NestJS微服务后端调用Firebase FCM的sendAll()接口时,触发以下PEM格式错误:

error:0909006C:PEM routines:get_name:no start line

Firebase初始化过程无报错,但调用sendAll()时抛出上述异常,相关代码及错误信息如下:

Firebase初始化代码(main.ts)

const app = await NestFactory.createMicroservice<MicroserviceOptions>(AppModule, { transport: Transport.TCP, options });
const configService: ConfigService = app.get(ConfigService);

const adminConfig: ServiceAccount = {
    "projectId": configService.get<string>('FIREBASE_PROJECT_ID'),
    "privateKey": configService.get<string>('FIREBASE_PRIVATE_KEY').replace(/\\n/g, '\n'),
    "clientEmail": configService.get<string>('FIREBASE_CLIENT_EMAIL'),
};
  
firebase.initializeApp({ credential: firebase.credential.cert(adminConfig) });

await app.listen();

.env配置

FIREBASE_PRIVATE_KEY    = "-----BEGIN PRIVATE KEY-----\nMI...\n-----END PRIVATE KEY-----\n"
FIREBASE_CLIENT_EMAIL   = 'abc@my.com'
FIREBASE_PROJECT_ID     = 'xxx-genysis'

sendAll()调用代码(notification.service.ts)

import * as firebase from 'firebase-admin';

async sendNotifications(data) {
  const messages = await this.getMessages(data.tokens,data.message,data.title);

  const promises = [];
  for (let i = 0; i + 10 <= messages.length; i += 10) {
      const batch = messages.slice(i, i + 10);
      promises.push(firebase.messaging().sendAll(batch));
  }
  await Promise.all(promises);
}

错误栈信息

error:0909006C:PEM routines:get_name:no start line
Error: error:0909006C:PEM routines:get_name:no start line
    at Sign.sign (node:internal/crypto/sig:131:29)
    at Object.sign (/Applications/GenysisBuild/GenysisNest/genysis-notifications/node_modules/jsonwebtoken/node_modules/jwa/index.js:152:45)
    at Object.jwsSign [as sign] (/Applications/GenysisBuild/GenysisNest/genysis-notifications/node_modules/jsonwebtoken/node_modules/jws/lib/sign-stream.js:32:24)
    at Object.module.exports [as sign] (/Applications/GenysisBuild/GenysisNest/genysis-notifications/node_modules/jsonwebtoken/sign.js:204:16)
    at ServiceAccountCredential.createAuthJwt_ (/Applications/GenysisBuild/GenysisNest/genysis-notifications/node_modules/firebase-admin/lib/app/credential-internal.js:105:20)
    at ServiceAccountCredential.getAccessToken (/Applications/GenysisBuild/GenysisNest/genysis-notifications/node_modules/firebase-admin/lib/app/credential-internal.js:77:28)
    at FirebaseAppInternals.refreshToken (/Applications/GenysisBuild/GenysisNest/genysis-notifications/node_modules/firebase-admin/lib/app/firebase-app.js:45:49)
    at FirebaseAppInternals.getToken (/Applications/GenysisBuild/GenysisNest/genysis-notifications/node_modules/firebase-admin/lib/app/firebase-app.js:37:25)
    at AuthorizedHttpClient.getToken (/Applications/GenysisBuild/GenysisNest/genysis-notifications/node_modules/firebase-admin/lib/utils/api-request.js:612:34)
    at AuthorizedHttpClient.send (/Applications/GenysisBuild/GenysisNest/genysis-notifications/node_modules/firebase-admin/lib/utils/api-request.js:600:21)

捕获到的错误对象

{"library":"PEM routines","function":"get_name","reason":"no start line","code":"ERR_OSSL_PEM_NO_START_LINE"}

排查思路与解决方法

1. 修复私钥的格式处理逻辑

初始化时Firebase仅做基础校验,但sendAll()请求时会重新使用私钥签名JWT,此时私钥的隐性格式问题会触发错误:

  • 检查.env中私钥的外层双引号是否被保留,导致私钥开头多了引号。修改代码,额外去除首尾引号:
    "privateKey": configService.get<string>('FIREBASE_PRIVATE_KEY').replace(/\\n/g, '\n').replace(/^"|"$/g, ''),
    
  • 打印处理后的privateKey,确认是否包含正确的-----BEGIN PRIVATE KEY-----开头和-----END PRIVATE KEY-----结尾,且换行符已替换为真实换行(不是字符串\n)。

2. 验证私钥的纯净性

  • 确保私钥的每一行(包括首尾标记行)没有多余的空格、制表符或换行,尤其是-----BEGIN PRIVATE KEY-----和-----END PRIVATE KEY-----不能有任何额外字符。
  • 测试直接在代码中写入私钥(仅用于排查),排除.env加载的问题:
    const adminConfig: ServiceAccount = {
      projectId: 'xxx-genysis',
      privateKey: `-----BEGIN PRIVATE KEY-----
    

MI...
-----END PRIVATE KEY-----`,
clientEmail: 'abc@my.com',
};

### 3. 避免Firebase App重复初始化
微服务环境中,模块重复导入可能导致多次调用`initializeApp`,造成凭证实例异常。添加初始化判断:
```typescript
if (!firebase.apps.length) {
firebase.initializeApp({ credential: firebase.credential.cert(adminConfig) });
}

4. 兼容Node.js与SDK版本

部分Node.js版本与OpenSSL的PEM解析逻辑存在兼容性问题:

  • 升级Node.js到LTS版本(如18.x或20.x)
  • 调整firebase-admin SDK到稳定版本(如11.x或12.x)

5. 改用服务账号文件加载

放弃环境变量加载私钥,直接读取Firebase控制台下载的服务账号JSON文件,避免字符串处理的格式问题:

import * as serviceAccount from './service-account.json';

firebase.initializeApp({
  credential: firebase.credential.cert(serviceAccount as ServiceAccount),
});

注意将该JSON文件加入.gitignore,避免泄露敏感信息。

内容的提问来源于stack exchange,提问作者MichaelE

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 21:50:12