
Company News
Socket Named Top Sales Organization by RepVue
Socket won two 2026 Reppy Awards from RepVue, ranking in the top 5% of all sales orgs. AE Alexandra Lister shares what it's like to grow a sales career here.
greenlock-store-memory
Advanced tools
An in-memory reference implementation for account, certificate, and keypair storage strategies in Greenlock
An in-memory reference implementation of a Certificate and Keypair storage strategy for Greenlock v2.7+ (and v3)
var greenlock = require('greenlock');
// This in-memory plugin has only one option: 'cache'.
// We could have It's used so that we can peek and poke at the store.
var cache = {};
var gl = greenlock.create({
store: require('greenlock-store-memory').create({ cache: cache })
, approveDomains: approveDomains
...
});
TL;DR: Just take a look the code here, and don't over think it.
Also, you have the flexibility to get really fancy. Don't! You probably don't need to (unless you already know that you do).
DON'T BE CLEVER. Do it the dumb way first.
In most cases you're just implementing dumb storage.
If all you do is JSON.stringify() on set (save) and JSON.parse() after check (get)
and just treat it as a blob with an ID, you'll do just fine. You can always optimize later.
Promises vs Thunks ("node callbacks") vs Synchronous returns: You can use whatever style you like best. Everything is promisified under the hood.
Whenever you have neither a result, nor an error, you must always return null (instead of 'undefined').
The most important thing to keep in mind: approveDomains() is where all of the implementation-specific logic goes.
If you're writing a storage strategy (presumably why you're here), it's because you have logic in approveDomains()
that isn't supported by existing strategies. That makes it tempting to start thinking about things backwards, letting
your implementation-specific logic creep into your storage strategy. DON'T DO IT.
Keep in mind that, ultimately, it takes human decision / interaction / configuration to add, remove, or modify the collection of domains that are allowed, and how many / which domains are listed on each certificate - all of which is a completely separate process that lives outside of Greenlock (i.e uploading a site to a new folder).
The coupling between the method chosen for storage and the method chosen for approval is inherint, but keep it loose.
Lastly, it would be appropriate to include an example approveDomains() with your implementation for reference.
approveDomains() is called only when there is no certificate for a given domain in Greenlock's internal cache
and when that certificate is "renewable" (typically 15 days before expiration, which is configurable).
The user (perhaps you) will have checked in their database (or config file or file system) and retrieved relevant details (email associated with the domain, related domains that belong as altnames on the certificate, etc).
Those options will be available to all storage and challenge strategies. In fact, they can even change which strategy is used (i.e. some users using a Digital Ocean strategy for DNS challenges, others using Route53).
function approveDomains(opts) {
var info = userDb.getInfo(opts.domain);
if (!info) { throw new Error("ignoring junk request, bad domain"); }
opts.email = info.certificateOwner;
opts.subject = info.certificateSubject
opts.domains = info.certificateAltnames;
return opts; // or Promise.resolve(opts);
}
accounts.setKeypairFirst, you should implement accounts.setKeypair(). Just treat it like dumb storage.
This only gets called after a new account has already been created successfully. That will only happen when a completely new certificate is going to be issued (not renewal), and there's no user account already associate with that set of domains.
store.accounts.setKeypair = function (opts) {
console.log('accounts.setKeypair:', opts);
var id = opts.account.id || opts.email || 'default';
var keypair = opts.keypair;
cache.accountKeypairs[id] = JSON.stringify({
privateKeyPem: keypair.privateKeyPem
, privateKeyJwk: keypair.privateKeyJwk
});
return null; // or Promise.resolve(null);
};
accounts.checkKeypairWhatever you did above, you just do the opposite instead. Tada!
store.accounts.checkKeypair = function (opts) {
console.log('accounts.checkKeypair:', opts);
var id = opts.account.id || opts.email || 'default';
var keyblob = cache.accountKeypairs[id];
if (!keyblob) { return null; }
return JSON.parse(keyblob);
};
You should probably skip this and not worry about it.
However, if you have a special need for it, or if you want to shave off an ACME API call,
you can save the account kid (a misnomer intended to mean "key id", but actually refers
to an arbitrary ACME URL, used to identify the account).
store.accounts.set = function (opts) {
console.log('accounts.set:', opts);
return null;
};
store.accounts.check = function (opts) {
var id = opts.account.id || opts.email || 'default';
console.log('accounts.check:', opts);
return null;
};
If you don't implement these the account key will be used to "recover" the kid as necessary.
You don't have to worry though, it doesn't create a duplicate accounts or have any other negative
side affects other than an additional API call as needed.
Each certificate is supposed to have a unique keypair, which must not be the same as the account keypair.
Again, just treat it like a blob in dumb storage and you'll be fine.
This is the same as accounts.setKeypair(), but using a different idea.
You could even use the same data store in most cases because the IDs aren't likely to clash.
store.certificates.setKeypair = function (opts) {
console.log('certificates.setKeypair:', opts);
var id = opts.certificate.kid || opts.certificate.id || opts.subject;
var keypair = opts.keypair;
cache.certificateKeypairs[id] = JSON.stringify({
privateKeyPem: keypair.privateKeyPem
, privateKeyJwk: keypair.privateKeyJwk
});
return null;
};
You know the drill. Same as accounts.checkKeypair(), but a different ID.
This isn't called until after the certificate retrieval is successful.
Note: Every account must have a unique account key and account keys are not allowed to be used as certificate keys. However, you could use the same certificate key for all domains on a device (i.e. a server) or an account.
store.certificates.checkKeypair = function (opts) {
console.log('certificates.checkKeypair:', opts);
var id = opts.certificate.kid || opts.certificate.id || opts.subject;
var keyblob = cache.certificateKeypairs[id];
if (!keyblob) { return null; }
return JSON.parse(keyblob);
};
Whenever the ACME process completes successfully, you get a shiny new certificate with all of the domains you requested.
It's a good idea to save them - otherwise you run the risk of running up your rate limit and getting blocked as your server restarts, respawns, auto-scales, etc.
store.certificates.set = function (opts) {
console.log('certificates.set:', opts);
var id = opts.certificate.id || opts.subject;
var pems = opts.pems;
cache.certificates[id] = JSON.stringify({
cert: pems.cert
, chain: pems.chain
, subject: pems.subject
, altnames: pems.altnames
, issuedAt: pems.issuedAt // a.k.a. NotBefore
, expiresAt: pems.expiresAt // a.k.a. NotAfter
});
return null;
};
Note that chain is likely to be the same for all certificates issued by a service,
but there's no guarantee. The service may rotate which keys do the signing, for example.
Lastly, you just need a way to fetch the result of all the work you've done.
store.certificates.check = function (opts) {
console.log('certificates.check:', opts);
var id = opts.certificate.id || opts.subject;
var certblob = cache.certificates[id];
if (!certblob) { return null; }
return JSON.parse(certblob);
};
There you go - you basically just have 8 setter and getter functions that usually act as dumb storage, but that you can tweak with custom options if you need to.
Remember: Keep It Stupid-Simple
:D
FAQs
An in-memory reference implementation for account, certificate, and keypair storage strategies in Greenlock
We found that greenlock-store-memory demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Company News
Socket won two 2026 Reppy Awards from RepVue, ranking in the top 5% of all sales orgs. AE Alexandra Lister shares what it's like to grow a sales career here.

Security News
NIST will stop enriching most CVEs under a new risk-based model, narrowing the NVD's scope as vulnerability submissions continue to surge.

Company News
/Security News
Socket is an initial recipient of OpenAI's Cybersecurity Grant Program, which commits $10M in API credits to defenders securing open source software.