How to Secure JupyterHub with Apache Basic Auth
Without Breaking Websockets (“Split Auth” Pattern)

TL;DR: Protect only /hub with Apache Basic Auth.
Leave /user and websocket routes open.
Don’t put Basic Auth on all routes!

The Problem

If you apply Basic Auth (e.g., <Location />) to the entire JupyterHub site, you will break: Result: Even after login, users get blank pages, broken kernels, or endless prompts. Nothing works reliably.

The Solution: "Split Auth" Pattern

Protect only /hub routes with Basic Auth.
Let all /user and API routes pass through without additional authentication.

Sample Apache Config (Anonymized)

<VirtualHost *:443>
    ServerName your-domain.example.com

    SSLEngine on
    SSLCertificateFile    /etc/letsencrypt/live/your-domain.example.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/your-domain.example.com/privkey.pem
    Include               /etc/letsencrypt/options-ssl-apache.conf

    # Protect only the Hub (login, control panel) routes
    <Location /hub>
        AuthType Basic
        AuthName "Workshop Access"
        AuthUserFile /etc/apache2/.htpasswd
        <RequireAny>
            Require user hub_student
            Require ip 172.17.0.0/16   # (optional: allow backend/Docker traffic)
        </RequireAny>
    </Location>

    # Allow user servers (JupyterLab/Notebook) to function freely
    <Location /user>
        Require all granted
    </Location>

    ProxyPreserveHost On
    RequestHeader set X-Forwarded-Proto "https"
    RequestHeader set X-Forwarded-Port  "443"

    RewriteEngine On
    RewriteCond %{HTTP:Upgrade} =websocket [NC]
    RewriteRule /(.*) ws://YOUR_INTERNAL_JHUB_IP:8000/$1 [P,L]

    ProxyPass        / http://YOUR_INTERNAL_JHUB_IP:8000/ retry=0
    ProxyPassReverse / http://YOUR_INTERNAL_JHUB_IP:8000/
</VirtualHost>

Why This Works

Best Practices for Extra Security

References


Last updated: 2025 – For educational and sysadmin use. This solution is tested and works with JupyterHub, DockerSpawner, and Apache2 reverse proxy.
This sample config is fully anonymized for public sharing.