diff --git a/README.md b/README.md index bec0dcf..e391e06 100644 --- a/README.md +++ b/README.md @@ -177,7 +177,9 @@ and keys in shared memory, then set variable with the zone name. ## Automatic Certificate Renewal -NGINX and NJS do not yet have a mechanism for running code on a time interval, which presents a challenge for certificate renewal. One workaround to this is to set something up to periodically request `/acme/auto` from the NGINX server. +### NGINX opensource + +NGINX opensource and NJS do not yet have a mechanism for running code on a time interval, which presents a challenge for certificate renewal. One workaround to this is to set something up to periodically request `/acme/auto` from the NGINX server. If running directly on a host, you can use `cron` to schedule a periodic request. When deploying in Kubernetes you can use a [liveness-check](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-a-liveness-http-request). If you are running in a `docker` context, you can use Docker's `healthcheck:` functionality to do this. @@ -197,6 +199,47 @@ service: This configuration will request `/acme/auto` every 90 seconds. If the certificate is nearing expiry, it will be automatically renewed. +### NGINX Plus + +If using NGINX Plus, certificate renewal is automatically managed through active healthchecks to send a `GET` request to `/acme/auto` every 90 seconds. The relevant configuration section is + +```nginx + # Internal upstream for automated certificates renewal - requires NGINX Plus + upstream acme_auto_renewal { + zone acme_auto_renewal 64k; + server 127.0.0.1:10080; + } + + ## Internal certificates renewal server - requires NGINX Plus + # GETs proxy.nginx.com:8000/acme/auto every 90 seconds to automatically renews certificates + server { + listen 127.0.0.1:10080; + + location / { + internal; + health_check interval=90; + health_check uri=/internal_auto_renewal; + proxy_pass http://acme_auto_renewal; + } + + location = /internal_auto_renewal { + proxy_set_header Host proxy.nginx.com; + proxy_pass http://127.0.0.1:8000/acme/auto; + } + } +``` + +This configuration will request `/acme/auto` every 90 seconds. If the certificate is nearing expiry, it will be automatically renewed. + +In order to use NGINX Plus and automated certificate renewal you will need to use `examples/nginxplus.conf` as the main configuration file by running: + +``` +cp examples/nginxplus.conf examples/nginx.conf +``` + +and then following the installation instructions above here + + ## Advanced ### Serving challenges directly If you do not wish to use `js_content acme.challengeResponse` to respond to challenge requests, then you can serve them directly with NGINX. Just be sure that the `root` directive value in your location block matches the value of `$njs_acme_challenge_dir`. diff --git a/examples/nginx.conf b/examples/nginx.conf index e2d522e..0899ed5 100644 --- a/examples/nginx.conf +++ b/examples/nginx.conf @@ -28,7 +28,7 @@ http { js_shared_dict_zone zone=acme:1m; server { - listen 0.0.0.0:8000; # testing with 8000 should be 80 in prod, pebble usees httpPort in dev/pebble/config.json + listen 0.0.0.0:8000; # testing with 8000 should be 80 in prod, pebble uses httpPort in dev/pebble/config.json listen 443 ssl; server_name proxy.nginx.com; diff --git a/examples/nginxplus.conf b/examples/nginxplus.conf new file mode 100644 index 0000000..4f56108 --- /dev/null +++ b/examples/nginxplus.conf @@ -0,0 +1,104 @@ +daemon off; +user nginx; + +load_module modules/ngx_http_js_module.so; + +error_log /dev/stdout debug; + +events { +} + +http { + # Internal upstream for automated certificates renewal - Requires NGINX Plus + upstream acme_auto_renewal { + zone acme_auto_renewal 64k; + server 127.0.0.1:10080; + } + + ## Internal certificates renewal server - Requires NGINX Plus + # GETs proxy.nginx.com:8000/acme/auto every 90 seconds to automatically renews certificates + server { + listen 127.0.0.1:10080; + + location / { + internal; + health_check interval=90; + health_check uri=/internal_auto_renewal; + proxy_pass http://acme_auto_renewal; + } + + location = /internal_auto_renewal { + proxy_set_header Host proxy.nginx.com; + proxy_pass http://127.0.0.1:8000/acme/auto; + } + } + + js_path "/usr/lib/nginx/njs_modules/"; + js_fetch_trusted_certificate /etc/ssl/certs/ISRG_Root_X1.pem; + + js_import acme from acme.js; + + # One `resolver` directive must be defined. + resolver 127.0.0.11 ipv6=off; # docker-compose + # resolver 1.1.1.1 1.0.0.1 [2606:4700:4700::1111] [2606:4700:4700::1001] valid=300s; # Cloudflare + # resolver 8.8.8.8 8.8.4.4; # Google + # resolver 172.16.0.23; # AWS EC2 Classic + # resolver 169.254.169.253; # AWS VPC + resolver_timeout 5s; + + ## advanced use-case for njs-acme to optionally use shared dict to cache cert/key pairs + # then provide the same zone name in $njs_acme_shared_dict_zone_name + # zone size should be enough to store all certs and keys; 1MB should be enough to store 100 certs/keys + js_shared_dict_zone zone=acme:1m; + + server { + listen 0.0.0.0:8000; # testing with 8000 should be 80 in prod, pebble uses httpPort in dev/pebble/config.json + listen 443 ssl; + server_name proxy.nginx.com; + + # The full set of configuration variables. These can also be defined in + # environment variables. Use the same name as below, just UPPER_CASE. + ## Mandatory Variables + js_var $njs_acme_server_names "proxy.nginx.com proxy2.nginx.com"; + js_var $njs_acme_account_email "test@example.com"; + + ## Optional Variables + # js_var $njs_acme_dir /etc/nginx/njs-acme; + # js_var $njs_acme_challenge_dir /etc/nginx/njs-acme/challenge; + # js_var $njs_acme_account_private_jwk /etc/nginx/njs-acme/account_private_key.json; + # js_var $njs_acme_directory_uri https://pebble/dir; + # js_var $njs_acme_verify_provider_https false; + + ## advanced use-case + # # use a `js_shared_dict_zone` to store certs/keys in memory + js_var $njs_acme_shared_dict_zone_name acme; + + 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 = / { + return 200 "hello server_name:$server_name\nssl_session_id:$ssl_session_id\n"; + } + + location ~ "^/\.well-known/acme-challenge/[-_A-Za-z0-9]{22,128}$" { + js_content acme.challengeResponse; + } + + location = /acme/auto { + js_content acme.clientAutoMode; + } + + location = /csr/new { + js_content acme.createCsrHandler; + } + + location = /acme/new-acct { + js_content acme.acmeNewAccount; + } + } +}