Enabling https access on Nginx reverse proxy - s4/7
日本語記事準備中-シリーズ前後記事リンク追加予定
first post: Cheap Home LAN Playground Using Docker
This post is the fourth post in the series to show how I play with different services on my LAN using Docker. I have added the second Jupyter Notebook container and the third service, ghost, behind the reverse proxy in the previous post. Let me clean them up (remove the concerned files) and focus on further reverse proxy configuration. I will list all the files that should be there at the end of this post including what's newly added.
Once the clean up is done, I will work to enable https on reverse proxy access. The diagram below is what things will look like. After this post, my access to Jupyter Notebook will be on https, and so as my access to the other services I will add behind the reverse proxy later.
Cleaning up examples used in the previous post
I have added the second Jupyter Notebook and Ghost just for the sake of adding more examples on using Nginx reverse proxy along with DNS records. Let's stop them.
First, stop the two Jupyter Notebook containers by running docker compose down
at $HOME/mylan/jupyter
. Secondly, update the docker-compose.yml
file by removing "jupyter2" service section. Now I can run it again with docker compose up -d
to run just one jupyter
container.
Here is how docker-compose.yml
should look like, with "jupyter2" service section removed completely.
services:
jupyter:
container_name: jupyter
image: jupyter/base-notebook:notebook-6.5.1
user: root
ports:
- "8888:8888"
command: "start-notebook.sh --ServerApp.password='' --ServerApp.token='' --ip=0.0.0.0 --no-browser"
environment:
- "CHOWN_EXTRA=/home/jovyan"
- "CHOWN_EXTRA_OPTS=-R"
volumes:
- type: volume
source: jupyter_volume
target: /home/jovyan
volume:
nocopy: true
volumes:
jupyter_volume: {}
Thirdly, let's just shutdown ghost by moving to that directory $HOME/mylan/ghost
and run docker compose down
if it is still running.
And lastly, let me remove or rename the Nginx server configuration files for "jupyter2" and "blog". Here I am renaming from jupyter2.conf
to jupyter2.conf.bak
, and likewise for another one, blog.conf.bak
. I can restart the container by docker compose restart
at $HOME/mylan/rp
, and now the configuration files ending with .bak
are mounted all together with the other .conf
file, but not those server configurations are not loaded. You can confirm the config by running docker exec rp nginx -T
.
So now some configs were reverted and containers shutdown, here is how my directories look like.
$ cd $HOME/mylan
$ tree
.
|-jupyter
| |-docker-compose.yml
|-ghost
| |-docker-compose.yml
|-rp
| |-docker-compose.yml
| |-conf.d
| | |-jupyter.conf
| | |-blog.conf.bak
| | |-jupyter2.conf.bak
|-dns
| |-docker-compose.yml
| |-config
| | |-a-records.conf
## btw here I am actually using "find" and "sed" and making it alias
$ grep tree ~/.bash_aliases
alias tree='find . -not -path "*/\.git/*"| sed -e "s/[^-][^\/]*\// |/g" -e "s/|\([^ ]\)/|-\1/"'
Preparation for https access
Things I need to configure Nginx reverse proxy server to handle https are the private key and certificates. If you do have your own domain, it's the best to go ahead and generate certificates/key for it. I actually do have a domain and generated certificates/key through let's encrypt service.
Private CA and self-signed certificate
Let me stick to this private domain, mylan.local, in this series. In this case, this will be the steps to prepare files I will use on my reverse proxy server:
- generate a private key and certificate for my private CA, certificate authority
- generate a CSR along with the private key
- sign the CSR using CA certificate/key to generate signed certificate
- generate a DH parameter file
I will work on a separate directory $HOME/mylan/openssl
to generate necessary files.
cd $HOME/mylan
mkdir openssl
cd openssl
Now let me generate the private key/certificate for the CA.
# this will generate two files, crt and key
openssl req -x509 \
-sha256 -days 3650 \
-nodes \
-newkey rsa:2048 \
-subj "/CN=ca.mylan.local" \
-keyout rootCA.key -out rootCA.crt
# here is the result
$ ls
rootCA.crt rootCA.key
Next, generate a CSR for the domain *.mylan.local
along with the private key.
# generate a CSR for "*.mylan.local"
# along with the private key for this CSR/certificate
openssl req -new \
-newkey rsa:2048 -keyout tls.key -nodes \
-out tls.csr \
-subj "/CN=*.mylan.local"
# again, the result
$ ls
rootCA.crt rootCA.key tls.csr tls.key
Next, prepare an openssl configuration file cert.conf
that will be used when signing the CSR tls.csr
to generate the signed certificate.
Here is the content of cert.conf
.
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[ alt_names ]
DNS.1 = *.mylan.local
Finally, sign for the CSR to generate the certificate for *.mylan.local
. Check the content of the signed certificate by openssl x509 -in tls.crt -text -noout
.
openssl x509 -req \
-in tls.csr \
-CA rootCA.crt -CAkey rootCA.key \
-CAcreateserial -out tls.crt \
-days 365 \
-sha256 -extfile cert.conf
# to check the content of the signed certificate file
openssl x509 -in tls.crt -text -noout
Additionally, I will generate DH parameter file here.
# generate DH parameter file as dhparam.pem
openssl dhparam -out dhparam.pem 2048
# the end result
$ ls
cert.conf dhparam.pem rootCA.crt rootCA.key rootCA.srl tls.crt tls.csr tls.key
Configuring https
Let me go back to $HOME/mylan/rp
and start setting up https.
I will prepare a directory tls
to store files for https access. I will copy the files generated here.
cd $HOME/mylan/rp
mkdir tls
cd tls
cp $HOME/mylan/openssl/tls.key .
cp $HOME/mylan/openssl/tls.crt .
cp $HOME/mylan/openssl/dhparam.pem .
Let me also add Nginx tls.conf
file here. I am planning to mount this new tls
directory at /etc/nginx/tls
, and ssl_dhparam
, ssl_certificate
, and ssl_certificate_key
are set accordingly to correctly point to these certificate/key files prepared.
# dhparam
ssl_dhparam /etc/nginx/tls/dhparam.pem;
# options
ssl_session_cache shared:le_nginx_SSL:10m;
ssl_session_timeout 1440m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";
# cert/key
ssl_certificate /etc/nginx/tls/tls.crt;
ssl_certificate_key /etc/nginx/tls/tls.key;
I update docker-compose.yml
at $HOME/mylan/rp
to let rp
additionally mount this tls
directory. Here I also update the ports from 80 to 443.
services:
rp:
container_name: rp
image: nginx:1.23.2
ports:
- "443:443"
volumes:
- ./conf.d:/etc/nginx/conf.d
- ./tls:/etc/nginx/tls
I then update the reverse proxy config file for jupyter
access, $HOME/mylan/rp/conf.d/jupyter.conf
. I am including the tls.conf
file and changing listen
from 80 to 443.
server {
listen 443 ssl http2;
server_name jupyter.mylan.local;
# docker resolver
resolver 127.0.0.11 valid=30s;
# tls
include /etc/nginx/tls/tls.conf;
location / {
set $upstream 192.168.1.56:8888;
proxy_pass http://$upstream;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
}
Now restart the rp
docker compose and I can access https://jupyter.mylan.local
to access the service.
Adding root CA certificate on my client machine
I believe your web browser is showing you the warning message when you start accessing using https. As the message already may have told you, the browser does not trust the certificate used by this server. Copy rootCA.crt
file to your client machine, ask Google to find out how to add root CA certificate on your system, and once added your browser should no longer show warnings when accessing your services configured to use this self-signed certificate generated.
Files I have so far
As a closing of this post, let me go through the directories and files I have. Below is the output of find . -not -path "*/\.git/*"| sed -e "s/[^-][^\/]*\// |/g" -e "s/|\([^ ]\)/|-\1/"
executed at $HOME/mylan
, and my brief notes on each file. You might have some more files ending with .bak
under rp/conf.d
if you have renamed the server config files for other services covered in this series so far.
$ tree
.
|-jupyter # directory for jupyter
| |-docker-compose.yml # running Jupyter Notebook, listening on 8888
|-openssl # directory for certificate related files
| |-rootCA.crt # root CA certificate
| |-dhparam.pem # DH parameter file to use on web server
| |-tls.key # private key for the server certificate
| |-rootCA.srl # root CA serial, generated when signing CSR
| |-cert.conf # configuration for root CA to sign CSR
| |-tls.crt # signed server certificate
| |-rootCA.key # private key of root CA
| |-tls.csr # CSR presented to root CA to generate signed server certificate
|-rp # directory for reverse proxy server
| |-docker-compose.yml # running Nginx, listening on 443
| |-conf.d
| | |-jupyter.conf # Nginx server configuration for jupyter.mylan.local
| |-tls
| | |-tls.conf # Nginx server configuration related to https
| | |-dhparam.pem # DH parameter file
| | |-tls.key # private key for the certificate
| | |-tls.crt # certificate used by the server
|-dns # directory for dns
| |-docker-compose.yml # running Unbound, listening on 53 tcp and udp
| |-config
| | |-a-records.conf # custom A records, forward lookup records added here
Discussion