Enabling HTTPS with TraefikProxy#

When running JupyterHub, you almost always want to use TLS (HTTPS). Traefik has a few ways to do that. The first tricky bit is that traefik separates dynamic configuration from static configuration, and some configuration needs to go in static, while some goes in dynamic.

Static configuration#

If you are using externally-managed traefik (c.TraefikProxy.should_start = False), you must write the static configuration file yourself. The only static configuration required by juptyerhub-traefik-proxy is the creation of the entrypoints for the api and jupyterhub itself:

# static configuration

# enable API

address = "localhost:8099" # should match c.TraefikProxy.traefik_api_url

address = ":443"

options = "default"

jupyterhub-traefik-proxy can take care of the rest because it will apply its dynamic configuration when JupyterHub starts.

Manual SSL#

Configuring SSL with your own certificates works the same with traefik proxy as any other JupyterHub proxy implementation:

c.JupyterHub.ssl_cert = "/path/to/ssl.cert"
c.JupyterHub.ssl_key = "/path/to/ssl.key"

This will set the traefik dynamic configuration:

# dynamic configuration
certFile = "path/to/cert.crt"
keyFile  = "path/to/cert.key"

If you don’t tell jupyterhub about these files, you will need to set this configuration yourself in dynamic configuration (Traefik ignores TLS configuration in the “static” configuration file). Passing the certificates via JupyterHub configuration assumes the options = "default" static configuration:

# static configuration
options = "default"

If you use your own static and dynamic configuration files, you don’t have to use the ‘default’ TLS options or tell jupyterhub anything about your TLS configuration.

Let’s Encrypt#

Traefik supports using Let’s Encrypt for automatically issuing and renewing certificates. It’s great!

To configure traefik to use let’s encrypt, first we need to register a certificate resolver in static configuration:

# static configuration

# redirect all http requests to https
address = ":80"
to = "https"
scheme = "https"

# configure
email = "you@example.com"
storage = "acme.json" # file where certificates are stored
# use the staging server to test your deployment
# uncomment this when you are ready for production
caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"

# tlsChallenge means you don't need an http endpoint

And in your extra dynamic configuration, specify the domain(s) you want certificates for:

# dynamic configuration
resolver = "letsencrypt"
main = "hub.example.com"
sans = [
  # if you are serving more than one domain

If you are using JupyterHub-managed traefik (c.TraefikProxy.should_start = True), you can specify this same configuration via TraefikProxy’s extra_static_config and extra_dynamic_config:

c.TraefikProxy.traefik_entrypoint = "https"
c.TraefikProxy.extra_static_config = {
    "entryPoints": {
        "http": {
            "address": ":80"
        "https": {
            "http": {
                "tls": {
                    "options": "default"
    "certificatesResolvers": {
        "letsencrypt": {
            "acme": {
                "email": "you@example.com",
                "storage": "acme.json",
            "tlsChallenge": {},

c.TraefikProxy.extra_dynamic_config = {
    "tls": {
        "stores": {
            "default": {
                "defaultGeneratedCert": {
                    "resolver": "letsencrypt",
                    "domain": {
                        "main": "hub.example.com",