Jupyter Notebook Server Installation on Linux
Jupyter Notebook and JupyterLab provide interactive computing environments for data science and machine learning that can be deployed on a Linux VPS and accessed remotely via a browser. This guide covers installing JupyterLab, securing it with a password, exposing it through Nginx with SSL, managing Python kernels, and running it as a persistent systemd service.
Prerequisites
- Ubuntu 20.04/22.04 or CentOS/Rocky Linux 8/9
- Python 3.8+ installed
- Nginx installed (for reverse proxy)
- A domain or server IP for remote access
- Let's Encrypt SSL certificate (optional but recommended)
Installing JupyterLab
# Create a dedicated user for Jupyter (recommended)
sudo useradd -m -s /bin/bash jupyter
sudo -u jupyter -i
# As the jupyter user — create a virtual environment
python3 -m venv ~/jupyter-env
source ~/jupyter-env/bin/activate
# Install JupyterLab and common data science packages
pip install --upgrade pip
pip install jupyterlab notebook ipywidgets
pip install numpy pandas matplotlib scikit-learn
# Verify installation
jupyter lab --version
jupyter notebook --version
Password Protection
# Generate a secure password hash
python3 -c "from jupyter_server.auth import passwd; print(passwd())"
# Enter and confirm your password
# Output: argon2:$argon2id$v=19$m=10240...
# Or use the built-in configuration command
jupyter lab --generate-config
# Creates: ~/.jupyter/jupyter_lab_config.py
# Set the password hash in the config
jupyter lab password
# Enter your password when prompted
# Saves to: ~/.jupyter/jupyter_server_config.json
Configure Jupyter via the config file:
cat > ~/.jupyter/jupyter_lab_config.py << 'EOF'
c.ServerApp.ip = '127.0.0.1' # Bind to localhost only (Nginx will proxy)
c.ServerApp.port = 8888
c.ServerApp.open_browser = False
c.ServerApp.notebook_dir = '/home/jupyter/notebooks'
c.ServerApp.allow_root = False
c.ServerApp.token = '' # Disable token auth when using password
EOF
mkdir -p ~/notebooks
Nginx Reverse Proxy with SSL
Configure Nginx to proxy JupyterLab with SSL:
sudo tee /etc/nginx/sites-available/jupyter << 'EOF'
# Redirect HTTP to HTTPS
server {
listen 80;
server_name jupyter.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name jupyter.example.com;
ssl_certificate /etc/letsencrypt/live/jupyter.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/jupyter.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
# Security headers
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
location / {
proxy_pass http://127.0.0.1:8888;
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;
# Required for Jupyter WebSocket connections
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400;
}
}
EOF
sudo ln -s /etc/nginx/sites-available/jupyter /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Obtain an SSL certificate:
sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d jupyter.example.com
Running as a Systemd Service
# Create the service file
sudo tee /etc/systemd/system/jupyterlab.service << 'EOF'
[Unit]
Description=JupyterLab Server
After=network.target
[Service]
Type=simple
User=jupyter
WorkingDirectory=/home/jupyter
ExecStart=/home/jupyter/jupyter-env/bin/jupyter lab \
--config=/home/jupyter/.jupyter/jupyter_lab_config.py
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable jupyterlab
sudo systemctl start jupyterlab
# Check status
sudo systemctl status jupyterlab
sudo journalctl -u jupyterlab -f
Managing Kernels
JupyterLab supports multiple language kernels. Add Python virtual environments as kernels:
# As the jupyter user
source ~/jupyter-env/bin/activate
# Install ipykernel in a project virtualenv
python3 -m venv ~/projects/ml-project
source ~/projects/ml-project/bin/activate
pip install ipykernel pandas scikit-learn
python -m ipykernel install --user --name ml-project --display-name "ML Project (Python 3.10)"
# Install R kernel
sudo apt-get install -y r-base
R -e 'install.packages("IRkernel"); IRkernel::installspec(user=TRUE)'
# List installed kernels
jupyter kernelspec list
# Remove a kernel
jupyter kernelspec remove ml-project
Install additional kernels:
# Julia kernel
# Install Julia first, then:
julia -e 'using Pkg; Pkg.add("IJulia")'
# Bash kernel
pip install bash_kernel
python -m bash_kernel.install
GPU Kernels with CUDA
# Create a GPU-enabled environment
python3 -m venv ~/jupyter-env-gpu
source ~/jupyter-env-gpu/bin/activate
# Install PyTorch with CUDA support
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu124
# Install ipykernel and register as a Jupyter kernel
pip install ipykernel jupyter
python -m ipykernel install --user --name gpu-pytorch --display-name "PyTorch GPU"
# Test CUDA availability in a notebook:
# import torch
# print(torch.cuda.is_available())
# print(torch.cuda.get_device_name(0))
Troubleshooting
Cannot connect to Jupyter after starting Nginx
# Check if JupyterLab is actually running
sudo systemctl status jupyterlab
curl http://127.0.0.1:8888 # Should return HTML
# Check Nginx configuration
sudo nginx -t
sudo tail -f /var/log/nginx/error.log
WebSocket connection fails (kernel doesn't start)
# Ensure Nginx is passing the Upgrade header
# Verify these lines in the Nginx config:
# proxy_http_version 1.1;
# proxy_set_header Upgrade $http_upgrade;
# proxy_set_header Connection "upgrade";
"Permission denied" on notebook directory
# Fix ownership of notebooks directory
sudo chown -R jupyter:jupyter /home/jupyter/notebooks
# Check service is running as the jupyter user
ps aux | grep jupyter
Kernel keeps dying (out of memory)
# Check available memory
free -h
# Add a swap file if memory is limited
sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
SSL certificate errors after renewal
# Reload Nginx after certbot renewal
sudo systemctl reload nginx
# Set up auto-renewal hook
echo "post_hook = systemctl reload nginx" >> /etc/letsencrypt/renewal/jupyter.example.com.conf
Conclusion
A properly configured JupyterLab server with Nginx reverse proxy and SSL provides a secure, persistent remote data science environment on any Linux VPS. Adding multiple kernels for different Python environments, R, and GPU-accelerated PyTorch makes the setup suitable for diverse machine learning and data analysis workflows.


