In the world of high-performance web servers, NGINX is a popular choice because its lightweight and efficient architecture enables it to handle large loads of traffic. With the introduction of the shared dictionary function as part of the NGINX JavaScript module (njs), NGINX’s performance capabilities reach the next level.
In this blog post, we explore the njs shared dictionary’s functionality and benefits, and show how to set up NGINX Open Source without the need to restart when rotating SSL/TLS certificates.
Shared Dictionary Basics and Benefits
The new js_shared_dict_zone
directive allows NGINX Open Source users to enable shared memory zones for efficient data exchange between worker processes. These shared memory zones act as key-value dictionaries, storing dynamic configuration settings that can be accessed and modified in real-time.
Key benefits of the shared dictionary include:
- Minimal Overhead and Easy to Use – Built directly into njs, it’s easy to provision and utilize with an intuitive API and straightforward implementation. It also helps you simplify the process of managing and sharing data between worker processes.
- Lightweight and Efficient – Integrates seamlessly with NGINX, leveraging its event-driven, non-blocking I/O model. This approach reduces memory usage, and improves concurrency, enabling NGINX to handle many concurrent connections efficiently.
- Scalability – Leverages NGINX’s ability to scale horizontally across multiple worker processes so you can share and synchronize data across those processes without needing complex inter-process communication mechanisms. The time-to-live (TTL) setting allows you to manage records in shared dictionary entries by removing them from the zone due to inactivity. The
evict
parameter removes the oldest key-value pair to make space for new entries.
SSL Rotation with the Shared Dictionary
One of the most impactful use cases for the shared dictionary is SSL/TLS rotation. When using js_shared_dict_zone
, there’s no need to restart NGINX in the event of an SSL/TLS certificate or key update. Additionally, it gives you a REST-like API to manage certificates on NGINX.
Below is an example of the NGINX configuration file that sets up the HTTPS server with the js_set
and ssl_certificate
directives. The JavaScript handlers use js_set
to read the SSL/TLS certificate or key from a file.
This configuration snippet uses the shared dictionary to store certificates and keys in shared memory as a cache. If the key is not present, then it reads the certificate or key from the disk and puts it into the cache.
You can also expose a location that clears the cache. Once files on the disk are updated (e.g., the certificates and keys are renewed), the shared dictionary enforces reading from the disk. This adjustment allows rotating certificates/keys without the need to restart the NGINX process.
http {
…
js_shared_dict_zone zone=kv:1m;
server {
…
# Sets an njs function for the variable. Returns a value of cert/key
js_set $dynamic_ssl_cert main.js_cert;
js_set $dynamic_ssl_key main.js_key;
# use variable’s data
ssl_certificate data:$dynamic_ssl_cert;
ssl_certificate_key data:$dynamic_ssl_key;
# a location to clear cache
location = /clear {
js_content main.clear_cache;
# allow 127.0.0.1;
# deny all;
}
…
}
And here is the JavaScript implementation for rotation of SSL/TLS certificates and keys using js_shared_dict_zone
:
function js_cert(r) {
if (r.variables[‘ssl_server_name’]) {
return read_cert_or_key(r, ‘.cert.pem’);
} else {
return ”;
}
}
function js_key(r) {
if (r.variables[‘ssl_server_name’]) {
return read_cert_or_key(r, ‘.key.pem’);
} else {
return ”;
}
}
/**
* Retrieves the key/cert value from Shared memory or fallback to disk
*/
function read_cert_or_key(r, fileExtension) {
let data = ”;
let path = ”;
const zone = ‘kv’;
let certName = r.variables.ssl_server_name;
let prefix = ‘/etc/nginx/certs/’;
path = prefix + certName + fileExtension;
r.log(‘Resolving ${path}’);
const key = [‘certs’, path].join(‘:’);
const cache = zone && ngx.shared && ngx.shared[zone];
if (cache) {
data = cache.get(key) || ”;
if (data) {
r.log(`Read ${key} from cache`);
return data;
}
}
try {
data = fs.readFileSync(path, ‘utf8’);
r.log(‘Read from cache’);
} catch (e) {
data = ”;
r.log(`Error reading from file:${path}. Error=${e}`);
}
if (cache && data) {
try {
cache.set(key, data);
r.log(‘Persisted in cache’);
} catch (e) {
const errMsg = `Error writing to shared dict zone: ${zone}. Error=${e}`;
r.log(errMsg);
}
}
return data
}
By sending the /clear
request, the cache is invalidated and NGINX loads the SSL/TLS certificate or key from the disk on the next SSL/TLS handshake. Additionally, you can implement a js_content
that takes an SSL/TLS certificate or key from the request while persisting and updating the cache too.
The full code of this example can be found in the njs GitHub repo.
Get Started Today
The shared dictionary function is a powerful tool for your application’s programmability that brings significant advantages in streamlining and scalability. By harnessing the capabilities of js_shared_dict_zone
, you can unlock new opportunities for growth and efficiently handle increasing traffic demands.
Ready to supercharge your NGINX deployment with js_shared_dict_zone
? You can upgrade your NGINX deployment with js_shared_dict_zone
to unlock new use cases and learn more about this feature in our documentation. In addition, you can see a complete example of a shared dictionary function in the recently introduced njs-acme project, which enables the njs module runtime to work with ACME providers.
If you’re interested in getting started with NGINX Open Source and have questions, join NGINX Community Slack – introduce yourself and get to know this community of NGINX users!