Jupyterhub + Nginx Installation

Ubuntu x86_64 Server Setup with Google OAuth and Nginx Reverse Proxy

Prerequisites

  • Ubuntu server (x86_64 architecture)

  • Root access

  • Domain name pointing to server IP

  • SSL certificates (recommend Let's Encrypt)

  • Basic firewall configuration (ports 80/443 open)

Python Installation

apt update && apt install python3

Note: Python 3 is required as a base dependency for JupyterHub components.

Miniconda Setup

# Create installation directory
mkdir -p /opt/miniconda3

# Download and install Miniconda
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O /opt/miniconda3/miniconda.sh
bash /opt/miniconda3/miniconda.sh -b -u -p /opt/miniconda3
rm /opt/miniconda3/miniconda.sh

# Initialize Conda
source /opt/miniconda3/bin/activate
conda init --all

Important: After running these commands, close and reopen your shell session to activate Conda properly.

JupyterHub Installation

# Create dedicated Conda environment
conda create --name jhub_env && conda activate jhub_env

# Install specific Python version with systemd compatibility
conda install python=3.10

# Core components
conda install -c conda-forge jupyterhub
conda install jupyterlab notebook

# Authentication and process management
conda install -c conda-forge oauthenticator jupyterhub-systemdspawner

Why SystemdSpawner? Provides better isolation and resource management using systemd's control groups, also you can create ephemeral user accoutns without needing to create linux system accounts.

Google OAuth Configuration

  1. Go to the Google Cloud Console

  2. Create a new project or select an existing one

  3. Click APIs and Services and then credentials

  4. Click create new credential and click on OAuth Client ID

  5. Configure the OAuth consent screen

  6. Create OAuth 2.0 credentials (Client ID and Client Secret)

  7. Add authorized redirect URI: https://your.domain.com/hub/oauth_callback

JupyterHub Configuration

# Generate config template
mkdir /etc/jupyterhub
jupyterhub --generate-config -f /etc/jupyterhub/jupyterhub_config.py

Edit /etc/jupyterhub/jupyterhub_config.py:

# Authentication
c.JupyterHub.authenticator_class = 'google'
c.OAuthenticator.oauth_callback_url = "https://your.domain.com/hub/oauth_callback"
c.OAuthenticator.client_id = "[CLIENT_ID]"  # From Google Cloud
c.OAuthenticator.client_secret = "[CLIENT_SECRET]"  # From Google Cloud
c.OAuthenticator.allow_all = True # Allow any successfully authenticated user to login
c.Authenticator.allow_existing_users = True

# Admin Configuration
c.Authenticator.admin_users = set(['admin1', 'admin2', 'admin3'])
c.JupyterHub.load_roles = [
    {
        'name': 'admin-users',
        'scopes': ['admin-ui', 'admin:users', 'admin:servers', 'admin:groups'],
        'users': ['admin1@ncsu.edu', 'admin2@ncsu.edu', 'admin3@ncsu.edu'],
        'groups': ['admin-group']
    }
]

# General Configuration
c.JupyterHub.data_files_path = '/opt/miniconda3/envs/jhub_env/share/jupyterhub'
c.Spawner.cmd = ['/opt/miniconda3/envs/jhub_env/bin/jupyterhub-singleuser']
c.JupyterHub.ip = '127.0.0.1'

# Systemd Integration
c.JupyterHub.spawner_class = 'systemdspawner.SystemdSpawner'
c.SystemdSpawner.dynamic_users = True  # Creates temporary user accounts
c.SystemdSpawner.unit_extra_properties = {'RuntimeDirectoryPreserve': 'no'}

Systemd Service Setup

# Create service directory
mkdir -p /srv/jupyterhub

/etc/systemd/system/jupyterhub.service:

[Unit]
Description=JupyterHub Service
After=network.target

[Service]
User=root
Group=root
WorkingDirectory=/srv/jupyterhub
ExecStart=/bin/bash -c 'source /opt/miniconda3/etc/profile.d/conda.sh && conda activate jhub_env && export JPY_COOKIE_SECRET=$(openssl rand -hex 32) && /opt/miniconda3/envs/jhub_env/bin/jupyterhub -f /etc/jupyterhub/jupyterhub_config.py'
Restart=always
RestartSec=10
Environment="PATH=/opt/miniconda3/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

[Install]
WantedBy=multi-user.target

Security Note: The JPY_COOKIE_SECRET is regenerated at each service start, invalidating existing user sessions.

Nginx Configuration

apt install nginx -y

/etc/nginx/sites-available/jupyterhub.conf:

server {
    listen 80;
    server_name your.domain.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name your.domain.com;

    ssl_certificate /etc/pki/tls/certs/your.domain.com.crt;
    ssl_certificate_key /etc/pki/tls/private/your.domain.com.key;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # WebSocket support
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

Enable systemctl service and nginx configuration:

systemctl daemon-reload
systemctl enable jupyterhub
systemctl start jupyterhub
ln -s /etc/nginx/sites-available/jupyterhub.conf /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx

Security Setup

# Configure UFW firewall rules
sudo ufw allow 80/tcp    # HTTP traffic
sudo ufw allow 443/tcp   # HTTPS traffic
sudo ufw enable          # Activate firewall

# Verify firewall status
sudo ufw status verbose

Verification

systemctl status jupyterhub
journalctl -u jupyterhub -f  # Monitor logs
curl -I https://your.domain.com  # Test HTTPS connection

Troubleshooting

Common Issues:

  • OAuth Errors: Verify callback URL matches exactly

  • SSL Issues: Check certificate paths and permissions

  • Service Failures: Validate Conda environment activation

  • User Sessions: Cookie secret reset on service restart

Log Investigation:

journalctl -u jupyterhub -e  # Show recent logs
tail -f /var/log/nginx/error.log  # Nginx errors

Final Note: Always test configuration changes in a staging environment before production deployment.

Did you find this article valuable?

Support shambu2k's blog by becoming a sponsor. Any amount is appreciated!