Secure Your Lab

The following instructions will allow the use fo HTTPS in your lab address.

Navigate to the AWS Console. Go to the Security Groups settings and select your instance. Edit the inbound rules to include HTTP and HTTPS, then save.

Obtain administrative rights on your Ubuntu server by requesting root access:

$ sudo -i

Create a DH parameter for OpenSSL:

$ openssl dhparam -out /etc/jupyterhub/dhparam.pem 2048

Change access to the system administrator only:

$ chmod 600 /etc/jupyterhub/dhparam.pem

Link this file to the system folder:

$ ln -s /etc/jupyterhub/dhparam.pem /etc/ssl/certs/dhparam.pem

Install Certbot:

$ apt update
$ apt install software-properties-common
$ add-apt-repository universe
$ apt update
$ apt install certbot python3-certbot-nginx

Generate an SSL Certificate using Certbot:

$ certbot certonly --nginx

You will be prompted to enter an email and a domain (the newly added custom domain, for example: "YOUR-DOMAIN")

You should see:

  • Congratulations! Your certificate and chain have been sav /etc/letsencrypt/live/YOUR-DOMAIN/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/YOUR-DOMAIN/privkey.pem

Install nginx to set up a reverse proxy, so you do not have to type ":8000" at the end of the URL:

$ apt install nginx

Create a configuration file for nginx:

$ nano /etc/nginx/sites-available/jupyterhub.conf

Copy and paste the following code, based on Mozilla Configuration Generator, into this file. Be careful to modify the text to include your domain wherever "YOUR-DOMAIN" is present.

# top-level http config for websocket headers
# If Upgrade is defined, Connection = upgrade
# If Upgrade is empty, Connection = close
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
# HTTP server to redirect all 80 traffic to SSL/HTTPS
server {
listen 80;
listen [::]:80;
server_name YOUR-DOMAIN;
# Tell all requests to port 80 to be 302 redirected to HTTPS
return 302 https://$host$request_uri;
# HTTPS server to handle JupyterHub
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name YOUR-DOMAIN;
ssl_certificate YOUR-DOMAIN;
ssl_certificate_key YOUR-DOMAIN;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
ssl_session_tickets off;
ssl_dhparam /etc/ssl/certs/dhparam.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
# HSTS (ngx_http_headers_module is required) (63072000 seconds)
add_header Strict-Transport-Security "max-age=63072000" always;
#add_header Strict-Transport-Security max-age=1576800;
# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
# Managing literal requests to the JupyterHub front end
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# websocket headers
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# Managing requests to verify letsencrypt host
location ~ /.well-known {
allow all;

Use CTRL+O then enter to overwrite the document and CTRL+X to exit.

Unlink the existing default file:

$ unlink /etc/nginx/sites-enabled/default

Link the new nginx file for JupyterHub:

$ ln -s /etc/nginx/sites-available/jupyterhub.conf /etc/nginx/sites-enabled/jupyterhub.conf

Start nginx:

$ systemctl start nginx.service

Edit JupyterHub configuration file:

$ nano /etc/jupyterhub/

Add the following to force JupyterHub to only listen to local connections (

$ c.JupyterHub.bind_url = ''

Use CTRL+O then enter to overwrite the document and CTRL+X to exit.

Restart JupyterHub:

$ systemctl restart jupyterhub.service