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
[api]

[entryPoints.auth_api]
address = "localhost:8099" # should match c.TraefikProxy.traefik_api_url

[entrypoints.https]
address = ":443"

[entrypoints.https.http.tls]
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
[tls.stores.default.defaultCertificate]
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
[entrypoints.https.http.tls]
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
[entrypoints.httponly]
address = ":80"
[entryPoints.httponly.http.redirections.entryPoint]
to = "https"
scheme = "https"

# configure
[certificatesResolvers.letsencrypt.acme]
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
[certificatesResolvers.letsencrypt.acme.tlsChallenge]

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

# dynamic configuration
[tls.stores.default.defaultGeneratedCert]
resolver = "letsencrypt"
[tls.stores.default.defaultGeneratedCert.domain]
main = "hub.example.com"
sans = [
  # if you are serving more than one domain
  "other.service.example.com",
]

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",
                    },
                },
            },
        },
    },
}