npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@jsenv/https-local

v4.0.14

Published

A programmatic way to generate locally trusted certificates

Readme

HTTPS Local npm package

Generate locally trusted HTTPS certificates for local development.

🔒 Certificates trusted by your operating system and browsers
🌐 Perfect for local HTTPS development
🖥️ Works on macOS, Linux, and Windows
📱 Trusted by iOS simulators too
⚡ Simple CLI and JavaScript API

Table of Contents

Quick Start

npx @jsenv/https-local init
npx @jsenv/https-local generate

Then start your server reading the generated certificate files:

import { createServer } from "node:https";
import { readFileSync } from "node:fs";

const server = createServer(
  {
    cert: readFileSync("certificate.pem"),
    key: readFileSync("private_key.pem"),
  },
  (request, response) => {
    response.end("Hello HTTPS world!");
  },
).listen(8443, () => {
  console.log("HTTPS server running at https://localhost:8443");
});

CLI

init

npx @jsenv/https-local init

Installs a root certificate authority, trusts it in your OS, your browsers and the iOS simulators currently booted, and ensures localhost is mapped to 127.0.0.1 in your hosts file. Safe to re-run — subsequent runs report the current status.

> npx @jsenv/https-local init

ℹ authority root certificate not found in filesystem
Generating authority root certificate with a validity of 20 years...
✔ authority root certificate written at /Users/you/https_local/https_local_root_certificate.crt
Adding certificate to mac keychain...
❯ sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "/Users/you/https_local/https_local_root_certificate.crt"
Password:
✔ certificate added to mac keychain
Adding certificate to firefox...
✔ certificate added to Firefox
Check if certificate is in iOS simulator "iPhone 17"...
ℹ certificate not found in iOS simulator "iPhone 17"
Adding certificate to iOS simulator "iPhone 17"...
❯ xcrun simctl keychain 3353AABB-2A54-49FA-B69D-AA4454350523 add-root-cert "/Users/you/https_local/https_local_root_certificate.crt"
✔ certificate added to iOS simulator "iPhone 17"
Check hosts file content...
✔ all ip mappings found in hosts file
> npx @jsenv/https-local init

✔ authority root certificate found in filesystem
Checking certificate validity...
✔ certificate still valid for 19 years
Detect if certificate attributes have changed...
✔ certificate attributes are the same
Check if certificate is in mac keychain...
✔ certificate found in mac keychain
Check if certificate is in Firefox...
✔ certificate found in Firefox
Check if certificate is in iOS simulator "iPhone 17"...
✔ certificate found in iOS simulator "iPhone 17"
Check hosts file content...
✔ all ip mappings found in hosts file

iOS simulator

An iOS simulator has a trust store of its own: a certificate trusted by the mac keychain is still refused by Safari inside the simulator, and a fetch towards another origin fails with TypeError: Load failed — WebKit only offers the "Visit website" exception for the page itself, not for cross-origin requests.

init adds the root certificate to every simulator booted at the time it runs, with full trust: there is nothing to enable in Settings › General › About › Certificate Trust Settings afterwards (that toggle is for certificates installed from a profile). Boot the simulator, then run init again; or add it by hand, booted standing for every running simulator:

xcrun simctl keychain booted add-root-cert "$HOME/Library/Application Support/https_local/https_local_root_certificate.crt"

The certificate stays in the simulator across reboots. It cannot be removed on its own, so cleanup leaves it there; xcrun simctl keychain <udid> reset wipes the whole simulator keychain.

generate

npx @jsenv/https-local generate

Generates a server certificate signed by the local certificate authority and writes it to files. Requires init to have been run first.

Note: Certificate files are static — they are not renewed automatically. Re-run generate after one year to replace expired files.

Options:

| Option | Description | Default | | --------------- | --------------------------------- | ----------------- | | --certificate | Path for the certificate file | certificate.pem | | --private-key | Path for the private key file | private_key.pem | | --hostnames | Comma-separated list of hostnames | localhost |

Example:

npx @jsenv/https-local generate --certificate server.pem --private-key server.key --hostnames localhost,myapp.local

The default is localhost alone, unlike requestCertificate: a file keeps the network ips of the day it was written, and they change with the network.

cleanup

npx @jsenv/https-local cleanup

Uninstalls the root certificate and removes its trust from your OS and browsers.

Certificate Expiration

| Certificate | Expires after | How to renew? | | ----------- | ------------- | ----------------- | | server | 1 year | Re-run generate | | authority | 20 years | Re-run init |

The server certificate expires after one year, which is the maximum duration allowed by web browsers.

The authority root certificate expires after 20 years. Re-running init after expiry will reinstall and re-trust a new one.

Other Devices

init trusts the authority on the machine it runs on, and in the iOS simulators booted there. Another device, a phone for instance, trusts it once the root certificate is installed on it. Until then, it shows a certificate warning; clicking through it is not enough for what needs a secure context: Chrome, for one, refuses to register a service worker on such an origin.

The jsenv dev server serves a page doing it from the phone itself (download, steps for Android and iOS, a check that it worked) when given the rootCertificate returned by requestCertificate.

By hand: copy https_local_root_certificate.crt to the device, never the .key next to it.

  • Android: in Settings, search for "CA certificate", then install the file. Android requires a screen lock and then shows "Network may be monitored".
  • iOS: send the file with AirDrop, or open a link to it in Safari, then Settings › Profile Downloaded › Install. Then Settings › General › About › Certificate Trust Settings, and turn on full trust for the certificate: without it the certificate is installed but not trusted.

The device then trusts anything signed by the authority's key, the trade-off already made on the machine. Remove it from Android's Settings › Encryption & credentials › Trusted credentials › User, or from iOS's Settings › General › VPN & Device Management.

JavaScript API

To use the JavaScript API, add the package to your dev dependencies:

npm install --save-dev @jsenv/https-local

requestCertificate

The requestCertificate function generates a fresh certificate each time it is called and returns it in memory. Because the certificate is generated on every server startup, it is always valid — as long as your server is restarted at least once a year.

Without altNames, the certificate is valid for every name of the machine: localhost, 127.0.0.1, ::1, the machine name, <name>.local and the network ips. That is what another device on the network types to reach it; a name missing from the certificate fails in the browser (ERR_CERT_COMMON_NAME_INVALID) even when the authority is trusted. The network ips are read at the time of the call: after joining another network, restart the server.

Besides certificate and privateKey, it returns the authority as rootCertificate (PEM), for a device that must trust it (see Other Devices), and rootCertificateFilePath.

import { createServer } from "node:https";
import { requestCertificate } from "@jsenv/https-local";

const { certificate, privateKey } = requestCertificate({
  altNames: ["localhost", "local.example"],
});
const server = createServer(
  { cert: certificate, key: privateKey },
  (request, response) => {
    response.end("Hello HTTPS world!");
  },
).listen(8443, () => {
  console.log("HTTPS server running at https://localhost:8443");
});

init (or installCertificateAuthority) must be called once before using this function.

It also makes the current process trust the certificate authority, as described in trustCertificateAuthority. Pass trustAuthority: false to keep the CA certificates of the process untouched:

const { certificate, privateKey } = requestCertificate({
  trustAuthority: false,
});

trustCertificateAuthority

The root certificate authority is installed in the system keychain, which browsers read — but node does not: it uses its own CA list. A node process requesting a local HTTPS server signed by the authority therefore fails with unable to verify the first certificate, even on the machine that created the authority.

trustCertificateAuthority adds the authority root certificate to the CA list of the current process:

import { trustCertificateAuthority } from "@jsenv/https-local";

trustCertificateAuthority();

const response = await fetch("https://localhost:4000");

Only the authority root certificate is added; the CA certificates node trusts by default are preserved and the rest of the system keychain is left out. Calling it twice does nothing the second time.

requestCertificate does this on its own, so this function is for processes acting only as a client — an integration test requesting a local HTTPS server started elsewhere, for instance.

init (or installCertificateAuthority) must be called once before using this function.

Note: it relies on tls.setDefaultCACertificates, available starting from node 22.19.0 and 24.5.0. On older versions the call logs a warning and does nothing; the alternative there is the NODE_EXTRA_CA_CERTS environment variable, pointing to the rootCertificateFilePath returned by requestCertificate. It must be set before the process starts, which is why it does not replace this function:

NODE_EXTRA_CA_CERTS="~/Library/Application Support/https_local/https_local_root_certificate.crt" node file.mjs

verifyHostsFile

Verifies that IP mappings important for your local server are present in the hosts file.

import { verifyHostsFile } from "@jsenv/https-local";

await verifyHostsFile({
  ipMappings: {
    "127.0.0.1": ["localhost", "local.example"],
  },
});

Auto Update Hosts

It's possible to update hosts file programmatically using tryToUpdateHostsFile:

import { verifyHostsFile } from "@jsenv/https-local";

await verifyHostsFile({
  ipMappings: {
    "127.0.0.1": ["localhost", "local.example"],
  },
  tryToUpdateHostsFile: true,
});

installCertificateAuthority

The installCertificateAuthority function generates a certificate authority valid for 20 years. This certificate authority is needed to generate local certificates that will be trusted by the operating system and web browsers.

import { installCertificateAuthority } from "@jsenv/https-local";

await installCertificateAuthority();

By default, trusting the root certificate is a manual process. See BenMorel/dev-certificates for instructions. This can also be done programmatically as shown in Auto Trust.

Auto Trust

It's possible to trust root certificate programmatically using tryToTrust:

import { installCertificateAuthority } from "@jsenv/https-local";

await installCertificateAuthority({
  tryToTrust: true,
});