Auto Encrypt Localhost
Automatically provisions and installs locally-trusted TLS certificates for Node.js https servers (including Polka, Express.js, etc.)
How it differs from mkcert
Auto Encrypt Localhost is similar to the Go utility mkcert but with the following important differences:
-
It’s written in pure JavaScript for Node.js.
-
It does not require certutil to be installed.
-
It uses a different technique to install its certificate authority in the system trust stores of macOS and Windows.
-
It uses enterprise policies on all platforms to get Firefox to include its certificate authority from the system trust store.
How it works
Only on first run
Creates a root certificate authority and installs it in the system trust stores.
This is enough for Safari on Mac and Chrom(ium) across all platforms but not for Firefox, which needs additional convincing.
For Firefox, creates an enterprise policy file that:
-
On Mac and Windows, turns the Certificates
→ ImportEnterpriseRoots
flag on. This makes Firefox use the system trust store on these platforms.
-
On Linux, sets the Certificates
→ Install
setting to the exact path of the Certificate Authority’s certificate file which makes Firefox import it.
💡On macOS, you will see an alert warning you that “You are making changes to the System Certificate Trust Settings” and you will have to enter your account password to authorise it.
💡On Windows, you will be prompted three times to authorise the changes you are making to your system by clicking Yes.
💡 If Firefox is running, you must restart it before it will accept your new local development certificate.
💡 Unless you specify a custom location for Auto Encrypt Localhost to store its data, all projects on your machine will share the certificate authority and localhost certificate at ~/.local/share/small-tech.org/auto-encrypt-localhost/.
On first and subsequent runs
Creates a locally-trusted TLS certificate, signed by its own root CA, if one does not already exist or if the previous one has expired and automatically uses it when creating your server.
You can reach your server via the local loopback addresses (localhost, 127.0.0.1) on the device itself and also from other devices on the local area network by using your device’s external IPv4 address(es).
System requirements
Node version 18.2.0+.
Tested and supported on:
- Linux (tested on Fedora Silverblue 37 and Ubuntu 22.04)
- macOS (tested on Intel: Monterey, M1: Ventura)
- Windows (10 and 11 under Windows Terminal and with Windows PowerShell)
💡 On macOS, if you’re using a third-party terminal application like iTerm, you must give it Full Disk Access rights or Auto Encrypt Localhost will fail to install the policy file inside Firefox. You can do this on the latest version of the operating system by adding iTerm to the list at System Settings → Privacy & Security → Full Disk Access.
💡 On Windows, Auto Encrypt Localhost will also run under WSL 2 but this is not recommended as certificates will not be automatically installed in your Windows browsers for you since your guest Linux system knows nothing about and cannot configure your host Windows environment.
Installation
Command-Line Interface (CLI)
npm install --global @small-tech/auto-encrypt-localhost
Programmatic use
npm install @small-tech/auto-encrypt-localhost
Usage
Command-Line Interface (CLI)
The information listed here is also available via the help
command.
create
Create certificates and have them installed in local trust stores if necessary:
auto-encrypt-localhost
(This is the default alias for the auto-encrypt-localhost create
command.)
serve
Run local server and serve certificate authority certificate at http://<local IPs>/.ca
:
auto-encrypt-localhost serve
(This makes it convenient to download and install your local development CA certificate on other devices like phones for testing on your local network via your server’s local IP address.)
show
Print out certificate authority and server certificate key material:
auto-encrypt-localhost show
Programmatically
-
Import the module:
import AutoEncryptLocalhost from '@small-tech/auto-encrypt-localhost'
-
Prefix your server creation code with a reference to the Auto Encrypt Localhost class:
const server = AutoEncryptLocalhost.https.createServer(…)
Example
(You can find this example in the example/ folder in the source code. Run it by typing node example
.)
import AutoEncryptLocalhost from '@small-tech/auto-encrypt-localhost'
const server = AutoEncryptLocalhost.https.createServer((request, response) => {
response.end('Hello, world!')
})
server.listen(443, () => {
console.log('Web server is running at https://localhost')
})
On first run, Auto Encrypt Localhost will create your local certificate authority and install it in the system root store and in Firefox. These actions require elevated privileges and you will be prompted for your password unless you have passwordless sudo set up for your system.
On first run and every subsequent run, Auto Encrypt Localhost will generate locally-trusted certificates if necessary and create your server using them.
Once your server is up and running, you can reach it via https://localhost, https://127.0.0.1, and via its external IPv4 address(es) on your local area network. To find the list of IP addresses that your local server is reachable from, you can run the following code in the Node interpreter:
Object.entries(os.networkInterfaces()).map(iface => iface[1].filter(addresses => addresses.family === 'IPv4').map(addresses => addresses.address)).flat()
Plain Node.js example
If you just want to use the TLS certificates without using Auto Encrypt Localhost’s https server at runtime, get the TLS certificate and private key using the AutoEncryptLocalhost.getKeyMaterial()
static method.
The KeyMaterials
object that is returned from this method has cert
and key
properties and may be passed directly as the options object to a regular Node.js https
server.
Here’s a somewhat equivalent example to the one above but using Node’s regular https
module instead of Auto Encrypt Localhost at runtime:
import https from 'node:https'
import AutoEncryptLocalhost from '@small-tech/auto-encrypt-localhost'
const keyMaterial = AutoEncryptLocalhost.getKeyMaterial()
const server = https.createServer(keyMaterial, (request, response) => {
response.end('Hello, world!')
})
server.listen(443, () => {
console.log('Web server is running at https://localhost')
})
💡 Note that if you don’t use Auto Encrypt Localhost at runtime, you won’t get some of the benefits that it provides, like automatically adding the certificate authority to Node’s trust store (for hitting your server using Node.js without certificate errors), the /.ca
convenience route that serves your local Certificate Authority’s certificate for installation into devices for local testing, and automatic HTTP to HTTPS forwarding.
Events
When using Auto Encrypt Localhost, you will not see any status messages being logged to the console. Instead, all status updates are handled by events.
For example, here’s the first server example with all events forwarded to the respective console method to be logged out:
import AutoEncryptLocalhost from '@small-tech/auto-encrypt-localhost'
const events = AutoEncryptLocalhost.events
events.onAll(events.INFORMATION, console.info)
events.onAll(events.WARNINGS, console.warn)
events.onAll(events.ERRORS, console.error)
const server = AutoEncryptLocalhost.https.createServer((request, response) => {
response.end('Hello, world!')
})
server.listen(443, () => {
console.info('\n🎉 Web server is running at https://localhost\n')
})
The onAll()
method is a convenience function that adds the supplied event hander to all events of a given type. The three even type constants (INFORMATION
, WARNINGS
, and ERRORS
) correspond to the information
, warnings
and errors
properties on the AutoEncryptLocalhost.events
object, which is an EventEmitter
subclass.
Of course, you can also listen for specific events:
import AutoEncryptLocalhost from '@small-tech/auto-encrypt-localhost'
AutoEncryptLocalhost.events.on(
events.warnings.FIREFOX_NOT_FOUND_IN_DEFAULT_LOCATION,
message => {
console.warn('Firefox could not be found in the default location.')
console.warn(message)
}
)
On Linux
To access your server on port 443, make sure you’ve disabled privileged ports:
sudo sysctl -w net.ipv4.ip_unprivileged_port_start=0
(On Linux, ports 80 and 443 require special privileges. Please see A note on Linux and the security farce that is “privileged ports”. If you want a Small Web server that handles all that for you automatically, have a play with Kitten).
Multiple servers
You are not limited to running your server on port 443. You can listen on any port you like and you can have multiple servers with the following caveat: the HTTP server that redirects HTTP calls to HTTPS and serves your local root certificate authority public key (see below) will only be created for the first server and then only if port 80 is free.
Accessing your local machine from other devices on your local area network
You can access local servers via their IPv4 address over a local area network.
This is useful when you want to test your site with different devices without having to expose your server over the Internet using a service like PageKite or ngrok. For example, if your machine’s IPv4 address on the local area network is 192.168.2.42, you can just enter that IP to access it from, say, your iPhone.
To access your local machine from a different device on your local area network, you must transfer the public key of your generated local root certificate authority to that device and install and trust it.
For example, if you’re on an iPhone, hit the /.ca
route in your browser:
http://192.168.2.42/.ca
The browser will download the local root certificate authority’s public key and prompt you to install profile on your iPhone. You then have to go to Settings → Profile Downloaded → Tap Install when the Install Profile pop-up appears showing you the mkcert certificate you downloaded. Then, go to Settings → General → About → Certificate Trust Settings → Turn on the switch next to the mkcert certificate you downloaded. You should now be able to hit https://192.168.2.42
and see your site from your iPhone.
You can also transfer your key manually. By default, you can find the key at $HOME/small-tech.org/auto-encrypt-localhost/auto-encrypt-localhost-CA.pem
after you’ve run Auto Encrypt Localhost at least once.
Developer documentation
If you want to help improve Auto Encrypt Localhost or better understand how it is structured and operates, please see the developer documentation.
Acknowledgements
Thanks to mkcert for making the previous seven version of this tool possible. It was also a very useful reference while implementing the Linux system trust store support.
Like this? Fund us!
Small Technology Foundation is a tiny, independent not-for-profit.
We exist in part thanks to patronage by people like you. If you share our vision and want to support our work, please become a patron or donate to us today and help us continue to exist.
Audience
This is small technology.
If you’re evaluating this for a “startup” or an enterprise, let us save you some time: this is not the right tool for you. This tool is for individual developers building technology for themselves and others in a non-colonial manner that respects the human rights of the people who use them.
Related projects
From lower-level to higher-level:
Auto Encrypt
Adds automatic provisioning and renewal of Let’s Encrypt TLS certificates with OCSP Stapling to Node.js https servers (including Polka, Express.js, etc.)
HTTPS
A drop-in replacement for the standard Node.js HTTPS module with automatic development-time (localhost) certificates via Auto Encrypt Localhost and automatic production certificates via Auto Encrypt.
Site.js
A tool for developing, testing, and deploying a secure static or dynamic personal web site or app with zero configuration.
Note: Deprecated. Site.js is being used to serve a number of our own web sites and isn’t going away anytime soon but all new development work is on Kitten.
Kitten
A Small Web development kit.
Create your Small Web site using plain HTML, CSS, and JavaScript then enhance it with htmx and Alpine.js, if you like.
A note on Linux and the security farce that is “privileged ports”
Linux has an outdated feature dating from the mainframe days that requires a process that wants to bind to ports < 1024 to have elevated privileges. While this was a security feature in the days of dumb terminals, today it is a security anti-feature. (macOS has dropped this requirement as of macOS Mojave.)
On modern Linux systems, you can disable privileged ports like this:
sudo sysctl -w net.ipv4.ip_unprivileged_port_start=0
Or, if you want to cling to ancient historic relics like a conservative to a racist statue, ensure your Node process has the right to bind to so-called “privileged” ports by issuing the following command before use:
sudo setcap cap_net_bind_service=+ep $(which node)
If you are wrapping your Node app into an executable binary using a module like Nexe, you will have to ensure that every build of your app has that capability set. For an example of how we do this in Site.js, see this listing.
Help wanted
There are only so many different platforms and operating systems (especially Linux distributions), I can test this on myself. If you notice something not working as it should on your favourite one, please open an issue and let me know.
Like this? Fund us!
Small Technology Foundation is a tiny, independent not-for-profit.
We exist in part thanks to patronage by people like you. If you share our vision and want to support our work, please become a patron or donate to us today and help us continue to exist.
Copyright
Copyright © 2019-present Aral Balkan, Small Technology Foundation.
License
Auto Encrypt Localhost is released under AGPL 3.0.