Jupyterhub + Nginx Installation
Ubuntu x86_64 Server Setup with Google OAuth and Nginx Reverse Proxy
Table of Contents
- Prerequisites
- Python Installation
- Miniconda Setup
- JupyterHub Installation
- Google OAuth Configuration
- JupyterHub Configuration
- Systemd Service Setup
- Nginx Configuration
- Security Setup
- Verification
- Troubleshooting
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.