This article explains how to use
nginx-proxy to create a reverse proxy which automatically updates as containers are started and stopped. Note this is just one option for the reverse proxy.
The goal of this article is to
- start with a basic reverse proxy
- add SSL encryption with a signed (or self-signed) certificate
- Optionally, use LetsEncrypt.org to automatically generate signed certificates
Below are steps to configure nginx-proxy on your docker host. The tutorial assumes the following:
|sub-domain for virtual hosts||demo.example.com|
Create a Reverse Proxy on port 80
Wildcard DNS: Create DNS records in the domain to point to the docker host.
This wildcard will be used for all virtual hosts on this machine. If you intend to also use other subdomains for this host, you can add hostnames in other domains. For example, if "sampletown" has their own domain, then you could also add these RR's to sampletown's domain:
Because USAS and USPS run in separate containers they each must have their own virtual domain.
Create a directory on the docker host, e.g.
/data/proxy/and create the following
Some newer versions of nginx may contribute to some network issues. This has not been verified, but to pull an older version use:
Different information about the images can be seen here: https://hub.docker.com/r/jwilder/nginx-proxy go to the tags column to see the versions
The volume definitions are not strictly necessary at this point. However, adding them here is harmless and will make the subsequent instructions easier.
Run the proxy:
You should now be able to visit: http://sampletown.demo.example.com/. However, you'll receive a "503 Service Temporarily Unavailable" because nginx-proxy does not know how to discover our service containers.
In your district's compose project, add (or modify)
docker-compose.override.ymlfile to define VIRTUAL_HOST and VIRTUAL_PORT environments for each service:
The nginx-proxy container will monitor docker events. Each time a container starts or stops, which has a VIRTUAL_HOST variable, it will create a new nginx configuration which reverse proxies port 80 for the virtual domain to 8080 of the container.
Rebuild the district's containers with:
After the apps start, you should be able to reach the applications at:and
- Repeat the previous two steps for each school district.
If the host is not working, you can check the nginx-proxy log files with:
HTTPS proxy on port 443
If you wish to use LetsEncrypt.org for automated certificate signing, then skip this section.
Now that we have a reverse proxy, we can secure the port using HTTPS. In this example, we are creating a wildcard certificate to match the wildcard DNS entry. In this example, the "Common Name" is "
Create a certificate and CSR in the proxy's ./certs directory (this volume was mounted in the proxy's docker-compose.yml file above).
Send the CSR to your favorite signing authority, or self sign it:
Configure nginx to listen on port 443. Add port mapping to the proxy's docker-compose.yml file:
Recreate the proxy container with:
This exposes port 443 for SSL. We are leaving port 80 exposed because the nginx-proxy will automatically redirect port 80 to 443. Now we can access our application at:/. If the cert is self-signed, you'll get a browser warning. It will go away when/if you have the certificate signed.
After receiving the signed certificate from the signing authority, replace the
.crt file created above with the signed certificate. In the example above, the self-signed certificate is named
demo.example.com.crt. That file should be replaced with the file from the signing authority. Note: The names of the certificate files are important. The certificate file name must be must match the domain name it applies to. Again, from the above example, the wildcard domain name is *.demo.example.com so the certificate and keys must be named
By convention, nginx-proxy will use the domain name to find the most specific certificate first and then drop prefixes until it finds a match. In this case, it will look for sampletown.demo.example.com.crt and then
demo.example.com.crt . This allows you to have different signed certificates for different domain names on the same proxy.
Automatic Signed Certificates with LetsEncrypt.org
LetsEncrypt.org is a service which automates the process of creating, signing, installing and renewing Domain Validation (DV) Certificates. These are certificates provide the lowest level of host verification but do ensure encrypted traffic to the users browser.
The steps below show how to configure an extra container to automatically create and install certificates using jrcs/letsencrypt-nginx-proxy-companion. This is a non-intrusive way to add letsencrypt to an existing proxy configuration.
Pulling new images
You must expose port 80 and 443 of your docker host to the outside via your firewall. That is, the docker host must have a public IP address and be accessible on both port 80 and 443 to the outside. DNS entries must exist in the global DNS for the virtual host(s) which point to the docker host's IP address. When your host makes a certificate request, LetsEncrypts service will callback to your host for verification. If the remote service can not reach your host, then they can not verify your control of the domain name and the signing request will fail.
Steps to enable LetsEncrypt
First, if you haven't already exposed port 80 and 443 on the nginx-proxy. The proxy's docker-compose.yml should look like this:
Notice that the companion shares the volumes with the nginx-proxy container. The companion container will monitor docker events and automatically create certificates and place them in the correct directories for the primary nginx server.
Recreate the proxy with
For each district you want to have a certificate created for, edit the
docker-compose.override.ymlfile to create the LETSENCRYPT_HOST and LETSENCRYPT_EMAIL variables, like:
The companion image will create, sign and install a certificate for any service with the LETSENCRYPT_HOST variable. In most cases, the VIRTUAL_HOST and LETSENCRYPT_HOST will be set to the same value.
Restart the district's services with:
Because nginx-proxy and the companion container use separate environment variables, you can use a traditionally signed certificate for some hosts (see previous section) and letsencrypt certificates for others. For example, you might use a wildcard certificate for "*.demo.example.com" hosts and a letsencrypt certificate for a district's "vanity domain" (usas.sampletown.org and usps.sampletown.org).
Updating NGINX Proxy
If you want to update the version of NGINX, possibly due to a security advisory, the following steps are suggested
Determine the current image being used and check the NGINX version
Get image from docker-compose file
Go to the directory containing the docker-compose files for the proxy. In this example, it is /data/proxy
Check the nginx version of that image
Pull new image and check version
Note the version to pull depends on the version of nginx proxy being used. The image to pull will match the image name found in the docker-compose file
Activate it in your container
Make sure the container shows the new NGINX version
Note that the container name includes the name of the directory where the proxy files are placed. In this example, the directory has /proxy, so we can grep on this.
Miscellaneous tips and tidbits:
Issues with LetsEncrypt
LetsEncrypt no longer supports ACMEv1 for certificate management. If your site stops automatically renewing/generating certificates, this may appear in the logs:
To resolve this, make sure you have the latest images. See https://github.com/JrCs/docker-letsencrypt-nginx-proxy-companion
To check if it successful, go to the certs/accounts directory. You will see something like this if it is using ACMEv2 - in this case our proxy docker-compose.yml is in /data/proxy:
Checking site configuration
- Check http/2 and ALPN configuration: https://tools.keycdn.com/http2-test
- Check SSL, Chain, and Security: https://www.ssllabs.com/ssltest/
Force certificate renewal for all:
Get certificate status - note in this case, I did a force renew on 3/30, so 90 days is 6/28.
We recommend making adjustments to the timeout configuration.