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
Go to the Google Cloud Console
Create a new project or select an existing one
Click APIs and Services and then credentials
Click create new credential and click on OAuth Client ID
Configure the OAuth consent screen
Create OAuth 2.0 credentials (Client ID and Client Secret)
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.