For detailed changes from the prior release, click on the version number, and its link will bring up a GitHub listing of changes. Use git log on the command line for details.


0.9.0 2018-06-15

JupyterHub 0.9 is a major upgrade of JupyterHub. There are several changes to the database schema, so make sure to backup your database and run:

jupyterhub upgrade-db

after upgrading jupyterhub.

The biggest change for 0.9 is the switch to asyncio coroutines everywhere instead of tornado coroutines. Custom Spawners and Authenticators are still free to use tornado coroutines for async methods, as they will continue to work. As part of this upgrade, JupyterHub 0.9 drops support for Python < 3.5 and tornado < 5.0.


  • Require Python >= 3.5

  • Require tornado >= 5.0

  • Use asyncio coroutines throughout

  • Set status 409 for conflicting actions instead of 400, e.g. creating users or groups that already exist.

  • timestamps in REST API continue to be UTC, but now include ‘Z’ suffix to identify them as such.

  • REST API User model always includes servers dict, not just when named servers are enabled.

  • server info is no longer available to oauth identification endpoints, only user info and group membership.

  • User.last_activity may be None if a user has not been seen, rather than starting with the user creation time which is now separately stored as User.created.

  • static resources are now found in $PREFIX/share/jupyterhub instead of share/jupyter/hub for improved consistency.

  • Deprecate .extra_log_file config. Use pipe redirection instead:

    jupyterhub &>> /var/log/jupyterhub.log
  • Add JupyterHub.bind_url config for setting the full bind URL of the proxy. Sets ip, port, base_url all at once.

  • Add JupyterHub.hub_bind_url for setting the full host+port of the Hub. hub_bind_url supports unix domain sockets, e.g. unix+http://%2Fsrv%2Fjupyterhub.sock

  • Deprecate JupyterHub.hub_connect_port config in favor of JupyterHub.hub_connect_url. hub_connect_ip is not deprecated and can still be used in the common case where only the ip address of the hub differs from the bind ip.


  • Spawners can define a .progress method which should be an async generator. The generator should yield events of the form:

      "message": "some-state-message",
      "progress": 50,

    These messages will be shown with a progress bar on the spawn-pending page. The async_generator package can be used to make async generators compatible with Python 3.5.

  • track activity of individual API tokens

  • new REST API for managing API tokens at /hub/api/user/tokens[/token-id]

  • allow viewing/revoking tokens via token page

  • User creation time is available in the REST API as User.created

  • Server start time is stored as Server.started

  • Spawner.start may return a URL for connecting to a notebook instead of (ip, port). This enables Spawners to launch servers that setup their own HTTPS.

  • Optimize database performance by disabling sqlalchemy expire_on_commit by default.

  • Add python -m jupyterhub.dbutil shell entrypoint for quickly launching an IPython session connected to your JupyterHub database.

  • Include User.auth_state in user model on single-user REST endpoints for admins only.

  • Include Server.state in server model on REST endpoints for admins only.

  • Add Authenticator.blacklist for blacklisting users instead of whitelisting.

  • Pass c.JupyterHub.tornado_settings['cookie_options'] down to Spawners so that cookie options (e.g. expires_days) can be set globally for the whole application.

  • SIGINFO (ctrl-t) handler showing the current status of all running threads, coroutines, and CPU/memory/FD consumption.

  • Add async Spawner.get_options_form alternative to .options_form, so it can be a coroutine.

  • Add JupyterHub.redirect_to_server config to govern whether users should be sent to their server on login or the JuptyerHub home page.

  • html page templates can be more easily customized and extended.

  • Allow registering external OAuth clients for using the Hub as an OAuth provider.

  • Add basic prometheus metrics at /hub/metrics endpoint.

  • Add session-id cookie, enabling immediate revocation of login tokens.

  • Authenticators may specify that users are admins by specifying the admin key when return the user model as a dict.

  • Added “Start All” button to admin page for launching all user servers at once.

  • Services have an info field which is a dictionary. This is accessible via the REST API.

  • JupyterHub.extra_handlers allows defining additonal tornado RequestHandlers attached to the Hub.

  • API tokens may now expire. Expiry is available in the REST model as expires_at, and settable when creating API tokens by specifying expires_in.


  • Remove green from theme to improve accessibility
  • Fix error when proxy deletion fails due to route already being deleted
  • clear ?redirects from URL on successful launch
  • disable send2trash by default, which is rarely desirable for jupyterhub
  • Put PAM calls in a thread so they don’t block the main application in cases where PAM is slow (e.g. LDAP).
  • Remove implicit spawn from login handler, instead relying on subsequent request for /user/:name to trigger spawn.
  • Fixed several inconsistencies for initial redirects, depending on whether server is running or not and whether the user is logged in or not.
  • Admin requests for /user/:name (when admin-access is enabled) launch the right server if it’s not running instead of redirecting to their own.
  • Major performance improvement starting up JupyterHub with many users, especially when most are inactive.
  • Various fixes in race conditions and performance improvements with the default proxy.
  • Fixes for CORS headers
  • Stop setting .form-control on spawner form inputs unconditionally.
  • Better recovery from database errors and database connection issues without having to restart the Hub.
  • Fix handling of ~ character in usernames.
  • Fix jupyterhub startup when getpass.getuser() would fail, e.g. due to missing entry in passwd file in containers.


0.8.1 2017-11-07

JupyterHub 0.8.1 is a collection of bugfixes and small improvements on 0.8.


  • Run tornado with AsyncIO by default
  • Add jupyterhub --upgrade-db flag for automatically upgrading the database as part of startup. This is useful for cases where manually running jupyterhub upgrade-db as a separate step is unwieldy.
  • Avoid creating backups of the database when no changes are to be made by jupyterhub upgrade-db.


  • Add some further validation to usernames - / is not allowed in usernames.
  • Fix empty logout page when using auto_login
  • Fix autofill of username field in default login form.
  • Fix listing of users on the admin page who have not yet started their server.
  • Fix ever-growing traceback when re-raising Exceptions from spawn failures.
  • Remove use of deprecated bower for javascript client dependencies.

0.8.0 2017-10-03

JupyterHub 0.8 is a big release!

Perhaps the biggest change is the use of OAuth to negotiate authentication between the Hub and single-user services. Due to this change, it is important that the single-user server and Hub are both running the same version of JupyterHub. If you are using containers (e.g. via DockerSpawner or KubeSpawner), this means upgrading jupyterhub in your user images at the same time as the Hub. In most cases, a

pip install jupyterhub==version

in your Dockerfile is sufficient.


  • JupyterHub now defined a Proxy API for custom proxy implementations other than the default. The defaults are unchanged, but configuration of the proxy is now done on the ConfigurableHTTPProxy class instead of the top-level JupyterHub. TODO: docs for writing a custom proxy.

  • Single-user servers and services (anything that uses HubAuth) can now accept token-authenticated requests via the Authentication header.

  • Authenticators can now store state in the Hub’s database. To do so, the authenticate method should return a dict of the form

        'username': 'name',
        'state': {}

    This data will be encrypted and requires JUPYTERHUB_CRYPT_KEY environment variable to be set and the Authenticator.enable_auth_state flag to be True. If these are not set, auth_state returned by the Authenticator will not be stored.

  • There is preliminary support for multiple (named) servers per user in the REST API. Named servers can be created via API requests, but there is currently no UI for managing them.

  • Add LocalProcessSpawner.popen_kwargs and LocalProcessSpawner.shell_cmd for customizing how user server processes are launched.

  • Add Authenticator.auto_login flag for skipping the “Login with…” page explicitly.

  • Add JupyterHub.hub_connect_ip configuration for the ip that should be used when connecting to the Hub. This is promoting (and deprecating) DockerSpawner.hub_ip_connect for use by all Spawners.

  • Add Spawner.pre_spawn_hook(spawner) hook for customizing pre-spawn events.

  • Add JupyterHub.active_server_limit and JupyterHub.concurrent_spawn_limit for limiting the total number of running user servers and the number of pending spawns, respectively.


  • more arguments to spawners are now passed via environment variables (.get_env()) rather than CLI arguments (.get_args())
  • internally generated tokens no longer get extra hash rounds, significantly speeding up authentication. The hash rounds were deemed unnecessary because the tokens were already generated with high entropy.
  • JUPYTERHUB_API_TOKEN env is available at all times, rather than being removed during single-user start. The token is now accessible to kernel processes, enabling user kernels to make authenticated API requests to Hub-authenticated services.
  • Cookie secrets should be 32B hex instead of large base64 secrets.
  • pycurl is used by default, if available.


So many things fixed!

  • Collisions are checked when users are renamed
  • Fix bug where OAuth authenticators could not logout users due to being redirected right back through the login process.
  • If there are errors loading your config files, JupyterHub will refuse to start with an informative error. Previously, the bad config would be ignored and JupyterHub would launch with default configuration.
  • Raise 403 error on unauthorized user rather than redirect to login, which could cause redirect loop.
  • Set httponly on cookies because it’s prudent.
  • Improve support for MySQL as the database backend
  • Many race conditions and performance problems under heavy load have been fixed.
  • Fix alembic tagging of database schema versions.


  • End support for Python 3.3


0.7.2 - 2017-01-09


  • Support service environment variables and defaults in jupyterhub-singleuser for easier deployment of notebook servers as a Service.
  • Add --group parameter for deploying jupyterhub-singleuser as a Service with group authentication.
  • Include URL parameters when redirecting through /user-redirect/


  • Fix group authentication for HubAuthenticated services

0.7.1 - 2017-01-02


  • Spawner.will_resume for signaling that a single-user server is paused instead of stopped. This is needed for cases like DockerSpawner.remove_containers = False, where the first API token is re-used for subsequent spawns.
  • Warning on startup about single-character usernames, caused by common set('string') typo in config.


  • Removed spurious warning about empty next_url, which is AOK.

0.7.0 - 2016-12-2


  • Implement Services API #705
  • Add /api/ and /api/info endpoints #675
  • Add documentation for JupyterLab, pySpark configuration, troubleshooting, and more.
  • Add logging of error if adding users already in database. #689
  • Add HubAuth class for authenticating with JupyterHub. This class can be used by any application, even outside tornado.
  • Add user groups.
  • Add /hub/user-redirect/... URL for redirecting users to a file on their own server.


  • Always install with setuptools but not eggs (effectively require pip install .) #722
  • Updated formatting of changelog. #711
  • Single-user server is provided by JupyterHub package, so single-user servers depend on JupyterHub now.


  • Fix docker repository location #719
  • Fix swagger spec conformance and timestamp type in API spec
  • Various redirect-loop-causing bugs have been fixed.


  • Deprecate --no-ssl command line option. It has no meaning and warns if used. #789
  • Deprecate %U username substitution in favor of {username}. #748
  • Removed deprecated SwarmSpawner link. #699


0.6.1 - 2016-05-04

Bugfixes on 0.6:

  • statsd is an optional dependency, only needed if in use
  • Notice more quickly when servers have crashed
  • Better error pages for proxy errors
  • Add Stop All button to admin panel for stopping all servers at once

0.6.0 - 2016-04-25

  • JupyterHub has moved to a new jupyterhub namespace on GitHub and Docker. What was juptyer/jupyterhub is now jupyterhub/jupyterhub, etc.
  • jupyterhub/jupyterhub image on DockerHub no longer loads the in an ONBUILD step. A new jupyterhub/jupyterhub-onbuild image does this
  • Add statsd support, via c.JupyterHub.statsd_{host,port,prefix}
  • Update to traitlets 4.1 @default, @observe APIs for traits
  • Allow disabling PAM sessions via c.PAMAuthenticator.open_sessions = False. This may be needed on SELinux-enabled systems, where our PAM session logic often does not work properly
  • Add Spawner.environment configurable, for defining extra environment variables to load for single-user servers
  • JupyterHub API tokens can be pregenerated and loaded via JupyterHub.api_tokens, a dict of token: username.
  • JupyterHub API tokens can be requested via the REST API, with a POST request to /api/authorizations/token. This can only be used if the Authenticator has a username and password.
  • Various fixes for user URLs and redirects

0.5 - 2016-03-07

  • Single-user server must be run with Jupyter Notebook ≥ 4.0
  • Require --no-ssl confirmation to allow the Hub to be run without SSL (e.g. behind SSL termination in nginx)
  • Add lengths to text fields for MySQL support
  • Add Spawner.disable_user_config for preventing user-owned configuration from modifying single-user servers.
  • Fixes for MySQL support.
  • Add ability to run each user’s server on its own subdomain. Requires wildcard DNS and wildcard SSL to be feasible. Enable subdomains by setting JupyterHub.subdomain_host = 'https://jupyterhub.domain.tld[:port]'.
  • Use for local communication instead of localhost, avoiding issues with DNS on some systems.
  • Fix race that could add users to proxy prematurely if spawning is slow.


0.4.1 - 2016-02-03

Fix removal of /login page in 0.4.0, breaking some OAuth providers.

0.4.0 - 2016-02-01

  • Add Spawner.user_options_form for specifying an HTML form to present to users, allowing users to influence the spawning of their own servers.
  • Add Authenticator.pre_spawn_start and Authenticator.post_spawn_stop hooks, so that Authenticators can do setup or teardown (e.g. passing credentials to Spawner, mounting data sources, etc.). These methods are typically used with custom Authenticator+Spawner pairs.
  • 0.4 will be the last JupyterHub release where single-user servers running IPython 3 is supported instead of Notebook ≥ 4.0.

0.3 - 2015-11-04

  • No longer make the user starting the Hub an admin
  • start PAM sessions on login
  • hooks for Authenticators to fire before spawners start and after they stop, allowing deeper interaction between Spawner/Authenticator pairs.
  • login redirect fixes

0.2 - 2015-07-12

  • Based on standalone traitlets instead of IPython.utils.traitlets
  • multiple users in admin panel
  • Fixes for usernames that require escaping

0.1 - 2015-03-07

First preview release