Skip to content

DanielDent/docker-nginx-ssl-proxy

Repository files navigation

SSL Front-End Proxy With Automatic Free Certificate Management

Zero configuration required - set up SSL in 30 seconds. Out of the box A rating at SSL labs. HTTP/2 enabled for increased performance.

This image contains nginx along with some glue code to automatically obtain and renew a free DV SSL certificate from Let's Encrypt.

It is configured by setting two environment variables:

  • UPSTREAM - The IP address or hostname (and optional port) of the upstream server to proxy requests towards.
  • SERVERNAME - The hostname to listen to. The system will automatically obtain an SSL certificate for this hostname.

An optional EXTRANAMES variable can be provided with a list of additional domains to request as subject-alternative-names for the certificate.

Certificates from Let's Encrypt are issued with a 90 day expiration. This image will automatically renew the certificate when it is 60 days old.

Prior versions of this image used simp_le. It has been changed to use certbot due to reliability issues with simp_le.

Optional: Certificate Key, Chain, and Profile

By default, this image requests RSA certificates for compatibility with prior versions of this image and with older TLS clients. To request an ECDSA certificate instead, set:

CERTBOT_KEY_TYPE: ecdsa

The following environment variables control Certbot certificate issuance:

  • CERTBOT_KEY_TYPE - Certificate key type to request. Supported values are rsa and ecdsa. Defaults to rsa.
  • CERTBOT_RSA_KEY_SIZE - RSA key size used when CERTBOT_KEY_TYPE is rsa. Defaults to 2048.
  • CERTBOT_ELLIPTIC_CURVE - ECDSA curve used when CERTBOT_KEY_TYPE is ecdsa. Defaults to secp256r1.
  • CERTBOT_PREFERRED_CHAIN - Optional Certbot preferred chain subject common name, passed to --preferred-chain.
  • CERTBOT_PREFERRED_PROFILE - Optional ACME profile name, passed to --preferred-profile.

Let's Encrypt issues ECDSA subscriber certificates from ECDSA intermediates and RSA subscriber certificates from RSA intermediates. Leaving CERTBOT_PREFERRED_CHAIN unset uses Let's Encrypt's default chain, which is usually the safest compatibility choice. Prefer setting CERTBOT_PREFERRED_CHAIN only when you have verified that your clients trust the requested chain.

This image serves a single certificate/key pair. It does not request or serve simultaneous RSA and ECDSA certificates.

Changing certificate options for an existing /etc/letsencrypt volume may not replace an unexpired certificate immediately because the renewal loop uses --keep-until-expiring. The new settings will apply when Certbot next renews or reissues the lineage. To force an immediate migration, run a one-time forced renewal inside the container with the same domains and desired Certbot options; do not configure forced renewal permanently in the service loop, because repeated forced issuance can hit Let's Encrypt rate limits.

WARNING - HSTS Strict-Transport-Security Header

This image's default configuration includes a Strict-Transport-Security header with expiry set to 1 year. Visitors' browsers will cache this header and will refuse to connect except over SSL. Eventually, you may wish to have your domain included in browser HSTS Preload lists.

This header can be customized with the HSTS_HEADER variable. If set to skip, no HSTS header will be used. If set to any other value, the value of the HSTS_HEADER variable will be used as the header's value. e.g. to prepare for HSTS preload, you could set HSTS_HEADER to max-age=31536000; includeSubDomains; preload.

Example Use (via Docker Compose)

Create a compose.yaml file as follows:

services:
  app:
    image: nginx:stable
    expose:
      - "80"

  nginx-ssl-proxy:
    image: danieldent/nginx-ssl-proxy
    restart: always
    environment:
      UPSTREAM: app:80
      SERVERNAME: test.example.com
      EXTRANAMES: www.test.example.com,test2.example.com
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - letsencrypt:/etc/letsencrypt

volumes:
  letsencrypt:

Then simply docker compose up.

Optional: Upstream DNS Re-resolution

The proxy can use nginx dynamic upstream DNS resolution. This is useful when UPSTREAM is a Docker or Compose service name whose container IP can change after a recreate.

By default, this is enabled automatically when the container is using Docker's embedded DNS server at 127.0.0.11, which is typical on Docker user-defined networks, including the default network created by modern Compose. Otherwise, the image keeps nginx's traditional startup-time upstream lookup behavior so names supplied through /etc/hosts continue to work.

The following environment variables can be used to tune this behavior:

  • NGINX_UPSTREAM_RESOLVE - Controls dynamic upstream DNS re-resolution. Defaults to auto. Set to on to force dynamic re-resolution, or off to force nginx's startup-time lookup behavior.
  • NGINX_RESOLVERS - Space-separated DNS resolvers for nginx to use when re-resolving UPSTREAM. If unset, nameservers are read from /etc/resolv.conf, falling back to 127.0.0.11.
  • NGINX_RESOLVER_VALID - How long nginx should consider DNS answers valid. Defaults to 30s.
  • NGINX_RESOLVER_TIMEOUT - DNS lookup timeout. Defaults to 5s.
  • NGINX_UPSTREAM_ZONE_SIZE - Shared memory zone size required by nginx for dynamic upstream resolution. Defaults to 64k.

Existing upstream connections are not forcibly moved when DNS changes. New upstream connections use the refreshed address after nginx re-resolves the name.

Optional: OCSP Stapling

OCSP stapling is disabled by default. Let's Encrypt turned off its OCSP service in August 2025 and no longer includes OCSP URLs in its certificates, so enabling stapling does not help the default Let's Encrypt certificate flow.

If you use a custom certificate from a CA that still provides OCSP, or a certificate with OCSP Must-Staple, set OCSP_STAPLING to on.

The following environment variables can be used to tune OCSP DNS behavior when stapling is enabled:

  • NGINX_OCSP_RESOLVERS - Space-separated DNS resolvers for OCSP stapling checks. If unset, this uses NGINX_RESOLVERS.
  • NGINX_OCSP_RESOLVER_VALID - How long nginx should consider OCSP DNS answers valid. Defaults to 300s.
  • NGINX_OCSP_RESOLVER_TIMEOUT - OCSP DNS lookup timeout. Defaults to 5s.

Optional: Enable Simple Authentication

If the DO_AUTH environment variable is set to required, the proxy implements a simple authentication system.

A user meeting any of these three criteria will be allowed access to the proxied service:

  • Users coming from an IP or CIDR range listed in the space-separated WHITELIST_IPS variable.
  • Users presenting a cookie named magic_ssl_proxy_auth set to the value of the COOKIE_VALUE variable.
  • Users providing HTTP Basic Authentication credentials, username admin with a password matching the PROXY_PASSWORD variable.

A user that correctly authenticates with HTTP Basic Authentication will have their magic_ssl_proxy_auth cookie set so that they are not required to re-authenticate.

By default, no IPs are whitelisted. When authentication is enabled, the COOKIE_VALUE and PROXY_PASSWORD values will be chosen randomly if they are not provided. If randomly chosen, the randomly chosen values will be output to the console during container startup. The PROXY_PASSWORD value will also be available in the /tmp/proxy_password file within the container, while the chosen COOKIE_VALUE will be available in the /etc/nginx/auth_part1.conf file.

Nginx limits the length of your COOKIE_VALUE for performance reasons. If your COOKIE_VALUE is too long, nginx will refuse to start and will display errors relating to server_names_hash_bucket_size and server_names_hash_max_size. If you have difficulties, try decreasing the legnth of your cookie or add directives to your Nginx configuration to increase the maximum size.

Optional: Use upstream X-Forwarded-For headers

When configuring IP based authentication, be mindful that upstream reverse proxies and your Docker configuration may result in an apparent source IP that does not match the client's true IP address. Directly exposing this image to the internet (e.g. via the ports directive as in the above example) will remove one source of potential problems with IP based authentication.

Set the REAL_IP_RECURSIVE environment variable to on to cause Nginx to parse & use upstream X-Forwarded-For headers.

The list of IPs or CIDR ranges from which Nginx will trust the X-Forwarded-For variable can be specified in the SET_REAL_IP_FROM variable.

As a convenience, setting SET_REAL_IP_FROM_CLOUDFLARE to yes will cause Cloudflare's reverse proxy source IP addresses to be appended to the provided SET_REAL_IP_FROM list.

Optional: Adjust request size limits & buffer size

The NGINX_CLIENT_MAX_BODY_SIZE and NGINX_CLIENT_BODY_BUFFER_SIZE variables can be used to set nginx's client_max_body_size and client_body_buffer_size directives. This is most commonly required when users are uploading files to the proxied service.

E.g. With NGINX_CLIENT_MAX_BODY_SIZE set to 100m, nginx will allow a maximum body size of 100 megabytes. When requests are larger than client_body_buffer_size, nginx buffers the request using a temporary file. A larger client_body_buffer_size will use more memory, but will also reduce disk I/O. In many scenarios, a larger buffer size will result in increased performance. Different tradeoffs will be appropriate for different environments and use cases.

Optional: Add Arbitrary Nginx Config

The /etc/nginx/main_location.conf file provides a place to add arbitrary Nginx configuration directives to the main location block in the Nginx configuration file. The file is empty and can be safely overwritten in a downstream image or using a Docker volume.

Certificate Data

A /etc/letsencrypt volume is used to maintain certificate data. An account_key.json file holds the key to your Let's Encrypt account - which provides a convenient way to revoke a certificate.

Customizing

Nginx configuration can be customized by editing proxy.conf and placing a new copy of it at /etc/nginx/conf.d/default.conf.

Example Dockerfile:

FROM danieldent/nginx-ssl-proxy
COPY proxy.conf /etc/nginx/conf.d/default.conf

SSL Settings

Reasonable defaults have been chosen using the TLS Reference configurator intermediate profile for nginx and OpenSSL. The default configuration enables TLS 1.2 and TLS 1.3, uses modern AEAD cipher suites for TLS 1.2, and allows clients to choose their preferred cipher from the configured list. Very old clients without TLS 1.2 support will be unable to connect with the default settings.

Security Headers

By default the proxy adds a conservative set of response security headers:

  • X-Content-Type-Options: nosniff
  • X-Frame-Options: SAMEORIGIN
  • Referrer-Policy: strict-origin-when-cross-origin

These can be disabled by setting the SECURITY_HEADERS variable to skip. See the OWASP Secure Headers Project for background on these headers.

Note that nginx adds these headers rather than replacing any sent by the upstream. If your upstream server already emits the same headers, responses will contain duplicate values, which browsers handle inconsistently. In that case either stop the upstream from sending them or set SECURITY_HEADERS to skip here.

Optional: Content-Security-Policy & Permissions-Policy

A Content-Security-Policy is one of the strongest defenses against cross-site scripting and injection, but an effective policy is specific to the application being proxied, so none is sent by default. Set the CSP_HEADER variable to have the proxy add a Content-Security-Policy header, e.g.:

CSP_HEADER: "default-src 'self'; img-src 'self' data:; object-src 'none'"

Similarly, set PERMISSIONS_POLICY_HEADER to add a Permissions-Policy header controlling access to browser features, e.g.:

PERMISSIONS_POLICY_HEADER: "geolocation=(), camera=(), microphone=()"

Both are unset by default and are independent of the SECURITY_HEADERS variable. As with the other security headers, these are added to (not merged with) any header of the same name sent by the upstream.

Dependencies

  • nginx - proxy server
  • certbot - for handling certificate creation & validation (+ some wrappers in this image)
  • envplate - for allowing use of environment variables in Nginx configuration
  • s6-overlay - for PID 1, process supervision, zombie reaping

Issues, Contributing

If you run into any problems with this image, please check for issues on GitHub. Please file a pull request or create a new issue for problems or potential improvements.

License

Copyright 2015-2018 Daniel Dent.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use these files except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Third-party contents included in builds of the image are licensed separately.

About

SSL Front-End Proxy With Automatic Free Certificate Management

Resources

License

Stars

203 stars

Watchers

10 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors