This repository provides a JavaScript library to work with ACME providers such as Let's Encrypt for NJS. The source code is compatible with the ngx_http_js_module
runtime. This allows for the automatic generation and renewal of TLS/SSL certificates for NGINX.
Requires at least njs-0.8.2
, which is included with NGINX since nginx-1.25.3
.
NOTE: Some ACME providers have strict rate limits. Please consult with your provider. For Let's Encrypt refer to their rate-limits documentation.
There are a few ways of using this repo. You can:
acme.js
from the latest Releaseacme.js
file to use with your NGINX installationacme.js
using a locally installed Node.js toolkit to use with your NGINX installationEach option above is detailed in each section below.
You can download the latest released acme.js
file from the Releases page. Typically you would place this in the path /usr/lib/nginx/njs_modules/acme.js
in your NGINX server. See the example nginx.conf to see how to integrate it into your NGINX configuration.
To integrate the downloaded acme.js
file into a Docker image, you can add the following to your Dockerfile:
RUN mkdir -p /usr/lib/nginx/njs_modules/
RUN curl -L -o /usr/lib/nginx/njs_modules/acme.js https://github.com/nginx/njs-acme/releases/download/v1.0.0/acme.js
To create an Nginx+NJS+njs-acme Docker image, simply run:
% make docker-build
...
=> exporting to image
=> => exporting layers
=> => writing image ...
=> => naming to docker.io/nginx/nginx-njs-acme
This will build an image with a recent version of NGINX, required njs version, and the acme.js
file installed at /usr/lib/nginx/njs_modules/
.
The image will be tagged nginx/nginx-njs-acme
, where you can use it in place of a standard nginx
image.
When running the container, we advise mounting the /etc/nginx/njs-acme/
directory in a Docker volume so that the cert/key are retained between deployments of your nginx
container. The docker-compose.yml
file in this directory shows an example of doing this using the certs
volume.
acme.js
With DockerIf you want to use your own NGINX installation and do not want to have to worry about installing Node.js and other build dependencies, then you can run this command:
make docker-copy
This will build the full image and copy the acme.js
file to the local dist/
directory. You can then include this file in your NGINX deployments.
acme.js
Without DockerIf you have Node.js and NPM installed on your computer, you can run this command to generate acme.js
directly:
make build
This will generate dist/acme.js
, where you can then integrate it into your existing NGINX / NJS environment.
You can use environment variables or NGINX js_var
directives to control the behavior of the njs-acme
.
In the case where both are defined, environment variables take precedence. Environment variables are in ALL_CAPS
, whereas the nginx config variable is the same name, just prefixed with a dollar sign and $lower_case
.
For example, NJS_ACME_SERVER_NAMES
(env var) is the same as $njs_acme_server_names
(js_var).
The value of the variable NJS_ACME_DIRECTORY_URI
(js_var $njs_acme_directory_uri
) defaults to Let's Encrypt's Staging environment. When you are finished testing with their staging environment, you will need to define/change the value of this to your ACME provider's production environment. In Let's Encrypt's case the production URL is https://acme-v02.api.letsencrypt.org/directory
.
You will need to remove the staging certificate from your NGINX server's filesystem when changing from staging to production. It is located in /etc/nginx/njs-acme/
by default (controlled by the variable NJS_ACME_DIR
).
NJS_ACME_ACCOUNT_EMAIL
(env)\
$njs_acme_account_email
(js_var)\
Your email address to send to the ACME provider.\
value: Any valid email address\
default: none (you must specify this!)
NJS_ACME_SERVER_NAMES
(env)\
$njs_acme_server_names
(js_var)\
The hostname or list of hostnames to request the certificate for.\
value: Space-separated list of hostnames, e.g. www1.mydomain.com www2.mydomain.com
\
default: none (you must specify this!)
NJS_ACME_VERIFY_PROVIDER_HTTPS
(env)\
$njs_acme_verify_provider_https
(js_var)\
Verify the ACME provider certificate when connecting.
value: false | true
default: true
NJS_ACME_DIRECTORY_URI
(env)\
$njs_acme_directory_uri
(js_var)\
ACME directory URL.
value: {Any valid URL}
default: https://acme-staging-v02.api.letsencrypt.org/directory
NJS_ACME_DIR
(env)\
$njs_acme_dir
(js_var)\
Path to store ACME-related files such as keys, certificate requests, certificates, etc.
value: Any valid system path writable by the `nginx` user.
default: /etc/nginx/njs-acme/
NJS_ACME_CHALLENGE_DIR
(env)\
$njs_acme_challenge_dir
(js_var)\
Path to store ACME-related challenge responses.
value: Any valid system path writable by the `nginx` user.
default: `${NJS_ACME_DIR}/challenge/`
NJS_ACME_ACCOUNT_PRIVATE_JWK
(env)\
$njs_acme_account_private_jwk
(js_var)\
Path to fetch/store the account private JWK.
value: Path to the private JWK
default: ${NJS_ACME_DIR}/account_private_key.json
NJS_ACME_SHARED_DICT_ZONE_NAME
(env)\
$njs_acme_shared_dict_zone_name
(js_var)\
Shared Dictionary Zone name .
value: Zone name used as in `js_shared_dict_zone` directive
default: acme`
There are a few pieces that are required to be present in your nginx.conf
file. The file at examples/nginx.conf
shows them all.
NOTE: The examples here use js_var
for configuration variables, but keep in mind you can use the equivalent environment variables instead if that works better in your environment. See the Configuration Variables section above for specifics.
nginx.conf
Rootload_module modules/ngx_http_js_module.so;
http
Sectionjs_path "/usr/lib/nginx/njs_modules/";
js_fetch_trusted_certificate /etc/ssl/certs/ISRG_Root_X1.pem;
acme.js
into the acme
namespace.
js_import acme from acme.js;
resolver 127.0.0.11 ipv6=off; # docker-compose
js_shared_dict_zone zone=acme:1m
js_shared_dict_zone
name, then you need to define the variable $njs_acme_shared_dict_zone_name
with the name you would like to use. You can also use the environment variable NJS_ACME_SHARED_DICT_ZONE_NAME
.
js_var $njs_acme_shared_dict_zone_name acme;
server
Section(s)NJS_ACME_ACCOUNT_EMAIL
.
js_var $njs_acme_account_email test@example.com;
NJS_ACME_SERVER_NAMES
.
js_var $njs_acme_server_names 'proxy.nginx.com proxy2.nginx.com';
Set and use variables to hold the certificate and key paths using Javascript.
js_set $dynamic_ssl_cert acme.js_cert;
js_set $dynamic_ssl_key acme.js_key;
ssl_certificate data:$dynamic_ssl_cert;
ssl_certificate_key data:$dynamic_ssl_key;
location
Blockslocation ~ "^/\.well-known/acme-challenge/[-_A-Za-z0-9]{22,128}$" {
js_content acme.challengeResponse;
}
Named location to contain the js_periodic
directive to automatically request or renew certificates if necessary.
NOTE: This runs at the end of each interval, so your server will not be ready for a minute. If this is a problem for your use case, see the ALTERNATIVE below.
location @acmePeriodicAuto { js_periodic acme.clientAutoMode interval=1m; }
ALTERNATIVE: The
js_periodic
command runs after the interval period has elapsed, not at the beginning. If your use case requires immediate certificate provisioning, then use the followinglocation {}
block instead. This location exposes the endpoint/acme/auto
, which triggers the certificate provisioning process when requested.location = /acme/auto { allow 127.0.0.1; # Adjust for your needs deny all; js_periodic acme.clientAutoMode interval=1m; js_content acme.clientAutoModeHTTP; }
Use one location block or the other.
This project uses Babel and Rollup to compile TypeScript sources into a single JavaScript file for njs
. It uses Mocha with nginx-testing for running integration tests against the NGINX server. This project uses njs-typescript-starter to write NJS modules and integration tests in TypeScript.
The ACME RESTful client is implemented using ngx.fetch, crypto API, PKI.js APIs in the NJS runtime.
There is a docker-compose.yml
file in the project root directory that brings up an ACME server, a challenge server, a Node.js container for rebuilding the acme.js
file when source files change, and an NGINX container. The built acme.js
file is shared between the Node.js and NGINX containers. The NGINX container will reload when the acme.js
file changes.
If you use VSCode or another devcontainer-compatible editor, then run the following:
code .
Choose to "Reopen in container" and the services specified in the docker-compose.yml
file will start. Editing and saving source files will trigger a rebuild of the acme.js
file, and NGINX will reload its configuration.
If you just want to start the development environment using Docker (no devcontainer) then run:
make docker-devup
To follow these steps, you will need to have Node.js version 14.15 or greater installed on your system.
Install dependencies:
npm ci
Start the watcher:
npm run watch
Edit the source files. When you save a change, the watcher will rebuild ./dist/acme.js
or display errors.
Start a test environment in Docker:
make docker-devup
Optionally you can watch for nginx
log file in a separate shell:
docker compose logs -f nginx
When started initially, nginx will not have certificates at all. If you use the example config, you will need to wait one minute for the js_periodic
directive to invoke acme.clientAutoMode
to create the certificate.
Send an HTTP request to nginx running in Docker:
curl -vik --resolve proxy.nginx.com:8000:127.0.0.1 http://proxy.nginx.com:8000/
Send an HTTPS request to nginx running in Docker to test a new certificate:
curl -vik --resolve proxy.nginx.com:4443:127.0.0.1 https://proxy.nginx.com:4443
Test with openssl
:
openssl s_client -servername proxy.nginx.com -connect localhost:4443 -showcerts
Display content of certificates
docker compose exec -it nginx ls -la /etc/nginx/njs-acme/
The docker-compose file uses volumes to persist artifacts (account keys, certificate, keys). Additionally, letsencrypt/pebble is used for testing in Docker, so you don't need to open up port 80 for challenge validation.
If the reference implementation does not meet your needs, then you can build your own flows using this project as a library of convenience functions.
Look at clientAutoMode
in src/index.ts
to see how you can use the convenience functions to build a ACME client implementation. There are some additional methods in src/examples.ts
showing how to use the ACME account creation APIs or generating Certificate Signing Requests on demand.
Path | Description |
---|---|
src | Contains your source code that will be compiled to the dist/ directory. |
integration-tests | Integration tests. |
unit-tests | Unit tests for code in src/ . |
Please see the contributing guide for guidelines on how to best contribute to this project.
© F5, Inc. 2023