How to Run Jupyter Notebook on a VPS: Remote Python Data Science Environment with SSL

How to Run Jupyter Notebook on a VPS: Remote Python Data Science Environment with SSL

Running JupyterLab on a VPS gives you a cloud-based Python data science environment accessible from any browser — no local Python installation required, no laptop performance limits, and datasets that stay on the server. Particularly valuable for large datasets (too big for laptop RAM), long-running training jobs, and team collaboration on shared notebooks.

Why VPS for Jupyter

  • Large datasets: Process datasets exceeding laptop RAM on a VPS with 4–16 GB RAM
  • Long-running jobs: Notebooks run overnight without keeping your laptop open
  • Team collaboration: Multiple team members access the same JupyterLab instance and shared data
  • Access from anywhere: Browser-based — work from any device

Step 1: Install Miniconda

wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
bash Miniconda3-latest-Linux-x86_64.sh -b -p /opt/miniconda3
echo 'export PATH="/opt/miniconda3/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc

Step 2: Create Data Science Environment

conda create -n datascience python=3.11 -y
conda activate datascience

conda install -y jupyterlab numpy pandas matplotlib seaborn \
  scikit-learn scipy notebook ipywidgets

pip install plotly xgboost lightgbm polars duckdb sqlalchemy psycopg2-binary

Step 3: Configure JupyterLab Security

jupyter lab --generate-config

# Generate hashed password (enter and confirm when prompted)
python3 -c "from jupyter_server.auth import passwd; print(passwd())"
nano ~/.jupyter/jupyter_lab_config.py
c.ServerApp.ip = '127.0.0.1'           # Bind to localhost only — Nginx proxies
c.ServerApp.port = 8888
c.ServerApp.password = 'argon2:YOUR_HASHED_PASSWORD_HERE'
c.ServerApp.open_browser = False
c.ServerApp.root_dir = '/home/deploy/notebooks'
c.ServerApp.token = ''                  # Disable token auth (using password instead)

Step 4: Nginx Reverse Proxy with SSL

sudo nano /etc/nginx/sites-available/jupyter
server {
    listen 80;
    server_name jupyter.yourdomain.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name jupyter.yourdomain.com;

    location / {
        proxy_pass http://127.0.0.1:8888;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        # Long timeout for WebSocket kernel connections
        proxy_read_timeout 86400s;
        proxy_send_timeout 86400s;
    }
}
sudo ln -s /etc/nginx/sites-available/jupyter /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d jupyter.yourdomain.com

Step 5: systemd Service for Auto-Start

sudo nano /etc/systemd/system/jupyterlab.service
[Unit]
Description=JupyterLab Server
After=network.target

[Service]
Type=simple
User=deploy
WorkingDirectory=/home/deploy/notebooks
ExecStart=/opt/miniconda3/envs/datascience/bin/jupyter lab
Environment="PATH=/opt/miniconda3/envs/datascience/bin:/usr/local/bin:/usr/bin"
Restart=on-failure
RestartSec=10s

[Install]
WantedBy=multi-user.target
mkdir -p /home/deploy/notebooks
sudo systemctl enable --now jupyterlab
# Visit: https://jupyter.yourdomain.com

Connect to a Database from Jupyter

import pandas as pd
from sqlalchemy import create_engine

engine = create_engine('postgresql://user:password@localhost:5432/mydb')
df = pd.read_sql("SELECT * FROM orders WHERE created_at > '2025-01-01'", engine)
df.head()

Query Large Datasets with DuckDB

import duckdb

# Query a 50 GB CSV directly without loading into memory
result = duckdb.query("""
    SELECT
        date_trunc('month', order_date) as month,
        SUM(revenue) as monthly_revenue,
        COUNT(*) as order_count
    FROM '/home/deploy/data/orders_2025.csv'
    GROUP BY 1
    ORDER BY 1
""").df()
print(result)

Schedule Notebook Execution via Cron

sudo crontab -e
# Run a notebook daily at 6 AM and export as HTML
0 6 * * * /opt/miniconda3/envs/datascience/bin/jupyter nbconvert \
  --to html --execute /home/deploy/notebooks/daily_report.ipynb \
  --output /var/www/reports/daily_$(date +\%Y\%m\%d).html

JupyterHub for Multi-User Teams

pip install jupyterhub

# JupyterHub creates separate kernel environments per Linux user
# Each user logs in with their Linux username and password
jupyterhub --generate-config
# Edit jupyterhub_config.py for multi-user setup and run

Getting Started

JupyterLab runs well on 2–4 GB RAM for typical data science workloads. USA VPS plans at VPS.DO with NVMe storage keep pandas and DuckDB data loading fast — both benefit significantly from fast sequential disk I/O when processing large files.

Conclusion

JupyterLab on a VPS creates a cloud data science workbench that runs 24/7, handles datasets larger than laptop RAM, and is accessible from any browser. The setup — conda environment, password-protected JupyterLab, Nginx SSL reverse proxy, and systemd for auto-start — is production-appropriate and runs reliably without ongoing maintenance. For teams, JupyterHub extends this to multi-user environments with separate authentication and kernel spaces.

Fast • Reliable • Affordable VPS - DO It Now!

Get top VPS hosting with VPS.DO’s fast, low-cost plans. Try risk-free with our 7-day no-questions-asked refund and start today!