fastify-acme
Implement ACME protocol (plugin) for Fastify.
Installation
npm install fastify-acme
Register ACME Account
Before running the server, you need to register an ACME account. You can do this by installing the package globally and using the fastify-acme-reg CLI or programmatically by calling registerAcmeAccount.
Using CLI
npm install -g fastify-acme
fastify-acme-reg
The CLI prompts for an ACME provider and uses letsencrypt when the answer is empty.
If your CA requires External Account Binding, the CLI will also prompt for the EAB kid and hmacKey.
Programmatically
import { registerAcmeAccount } from 'fastify-acme'
const certDir = './cert'
const email = 'your-email@example.com'
await registerAcmeAccount(certDir, email)
Providers that require External Account Binding can pass it as part of the registration options:
await registerAcmeAccount(certDir, email, undefined, {
provider: 'google',
externalAccountBinding: {
kid: process.env.GOOGLE_ACME_EAB_KID!,
hmacKey: process.env.GOOGLE_ACME_EAB_HMAC_KEY!
}
})
ACME Providers
Let's Encrypt is used by default. The supported provider names are letsencrypt, buypass, google, and zerossl. Register the account and request the certificate with the same provider. Some providers, including Google Trust Services, require External Account Binding for new accounts:
const provider = 'google' as const
await registerAcmeAccount(certDir, email, undefined, {
provider,
externalAccountBinding: {
kid: process.env.GOOGLE_ACME_EAB_KID!,
hmacKey: process.env.GOOGLE_ACME_EAB_HMAC_KEY!
}
})
const certAndKey = await getCertAndKey(certDir, domain, unsecure.log, provider)
secure.register(fastifyAcmeSecurePlugin, { certDir, domain, provider })
Account keys and account URLs for non-default providers are stored separately in certDir. Existing Let's Encrypt account files keep their original names.
Usage
HTTP Server Example
import fastify from 'fastify'
import { fastifyAcmeSecurePlugin, fastifyAcmeUnsecurePlugin, getCertAndKey } from 'fastify-acme'
const certDir = './cert'
const domain = 'example.com'
const unsecure = fastify({ logger: true })
unsecure.register(fastifyAcmeUnsecurePlugin, { redirectDomain: domain })
await unsecure.listen({ port: 80 })
const certAndKey = await getCertAndKey(certDir, domain, unsecure.log)
const secure = fastify({
logger: true,
https: {
key: certAndKey.pkey,
cert: certAndKey.cert
}
})
secure.register(fastifyAcmeSecurePlugin, { certDir, domain })
secure.get('/', {}, async (_req, resp) => {
resp.send('Hello, World!')
})
void secure.listen({ port: 443 })
HTTP/2 Server Example
import fastify from 'fastify'
import { fastifyAcmeSecurePlugin, fastifyAcmeUnsecurePlugin, getCertAndKey } from 'fastify-acme'
const certDir = './cert'
const domain = 'example.com'
const unsecure = fastify({ logger: true })
unsecure.register(fastifyAcmeUnsecurePlugin, { redirectDomain: domain })
await unsecure.listen({ port: 80 })
const certAndKey = await getCertAndKey(certDir, domain, unsecure.log)
const secure = fastify({
logger: true,
http2: true,
https: {
allowHTTP1: true,
key: certAndKey.pkey,
cert: certAndKey.cert
}
})
secure.register(fastifyAcmeSecurePlugin, { certDir, domain })
secure.get('/', {}, async (_req, resp) => {
resp.send('Hello, World!')
})
void secure.listen({ port: 443 })
The HTTP server must be reachable from the Internet on port 80. In a multi-instance deployment, route every /.well-known/acme-challenge/* request to the instance currently obtaining the certificate, or use a shared challenge store and certificate directory.
License
ISC