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.