You're a developer, not a sysadmin. But your project needs to be deployed somewhere, and that somewhere is usually a Linux server - whether it's a VPS, a cPanel hosting account, or a bare metal machine your client already has.

Here's the practical minimum you need to know to deploy and keep alive Node.js and Python apps without becoming a DevOps engineer.

cPanel: The Reality

A lot of client projects end up on shared cPanel hosting. It's not glamorous, but it's what many clients already pay for. Here's how to make it work.

Node.js on cPanel

Most modern cPanel installations include a Node.js app manager. The setup:

  1. Go to Setup Node.js App in cPanel
  2. Select your Node.js version (18+ recommended)
  3. Set the application root (where your code lives)
  4. Set the startup file (usually server.js or app.js)
  5. Click Create

cPanel uses Phusion Passenger under the hood. Your app runs behind Apache, so you don't configure ports - Passenger handles the reverse proxy.

For a Next.js app, the startup file looks like:

// server.js - production Next.js on cPanel
const { createServer } = require('http');
const next = require('next');

const app = next({ dev: false });
const handle = app.getRequestHandler();

app.prepare().then(() => {
    createServer((req, res) => handle(req, res)).listen(0); // Passenger manages the port
});

Python on cPanel

cPanel's Python support uses Passenger WSGI. For Django:

  1. Go to Setup Python App in cPanel
  2. Select Python version (3.10+)
  3. Set application root and startup file
  4. Create, then install requirements via the cPanel terminal

The WSGI entry point:

# passenger_wsgi.py
import sys
import os

sys.path.insert(0, os.path.dirname(__file__))
os.environ['DJANGO_SETTINGS_MODULE'] = 'config.settings.production'

from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()

cPanel Cron Jobs

Need scheduled tasks? cPanel's Cron Jobs interface is straightforward:

# Run a Django management command every hour
0 * * * * cd /home/user/myproject && /home/user/virtualenv/myproject/bin/python manage.py process_queue >> /home/user/logs/cron.log 2>&1

# Run a Node.js script daily at midnight
0 0 * * * cd /home/user/myapp && /opt/cpanel/ea-nodejs18/bin/node scripts/cleanup.js >> /home/user/logs/cleanup.log 2>&1

Always redirect output to a log file. When a cron job fails silently, the log is your only clue.

VPS: The Full Control Option

When cPanel isn't enough (or isn't available), a VPS gives you complete control. Here's the minimum viable setup.

Initial Server Setup

# Update everything
sudo apt update && sudo apt upgrade -y

# Create a non-root user
sudo adduser deploy
sudo usermod -aG sudo deploy

# Set up SSH key auth (from your local machine)
ssh-copy-id deploy@your-server-ip

# Disable password auth
sudo nano /etc/ssh/sshd_config
# Set: PasswordAuthentication no
sudo systemctl restart sshd

# Basic firewall
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable

That's the security baseline: no root login, no password auth, firewall allowing only SSH and HTTP/HTTPS.

Nginx as Reverse Proxy

Nginx sits in front of your application and handles SSL, static files, and request routing:

# /etc/nginx/sites-available/myapp
server {
    listen 80;
    server_name myapp.com www.myapp.com;

    # Redirect to HTTPS
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name myapp.com www.myapp.com;

    ssl_certificate /etc/letsencrypt/live/myapp.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/myapp.com/privkey.pem;

    # Static files (served directly by Nginx - fast)
    location /static/ {
        alias /home/deploy/myapp/static/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    location /media/ {
        alias /home/deploy/myapp/media/;
        expires 7d;
    }

    # Proxy to application
    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;
    }
}

Enable it:

sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t  # Test configuration
sudo systemctl reload nginx

SSL with Let's Encrypt

Free SSL in two commands:

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d myapp.com -d www.myapp.com

Certbot automatically configures Nginx and sets up auto-renewal. Done.

Process Management with Supervisor

Your app needs to stay running after you close the SSH session, restart if it crashes, and start on server boot. Supervisor handles this:

; /etc/supervisor/conf.d/myapp.conf
[program:myapp]
command=/home/deploy/myapp/venv/bin/gunicorn config.wsgi:application -b 127.0.0.1:8000 -w 3
directory=/home/deploy/myapp
user=deploy
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/home/deploy/logs/myapp.log
stdout_logfile_maxbytes=10MB
stdout_logfile_backups=5
environment=DJANGO_SETTINGS_MODULE="config.settings.production"

For a Node.js app:

[program:myapp-node]
command=/usr/bin/node server.js
directory=/home/deploy/myapp
user=deploy
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/home/deploy/logs/node.log
environment=NODE_ENV="production",PORT="3000"

Control your app:

sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl status myapp        # Check status
sudo supervisorctl restart myapp       # Restart
sudo supervisorctl tail -f myapp       # Live logs

Database: PostgreSQL Setup

sudo apt install postgresql postgresql-contrib

# Create database and user
sudo -u postgres psql
postgres=# CREATE DATABASE myapp_db;
postgres=# CREATE USER myapp_user WITH PASSWORD 'secure_password';
postgres=# GRANT ALL PRIVILEGES ON DATABASE myapp_db TO myapp_user;
postgres=# \q

Automated Backups

# /home/deploy/scripts/backup_db.sh
#!/bin/bash
BACKUP_DIR="/home/deploy/backups"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
pg_dump -U myapp_user myapp_db | gzip > "$BACKUP_DIR/myapp_$TIMESTAMP.sql.gz"

# Keep only last 7 days
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +7 -delete

Add to cron:

0 3 * * * /home/deploy/scripts/backup_db.sh

Deployment Script

I use a simple bash script instead of CI/CD for most projects:

#!/bin/bash
# deploy.sh - run from your local machine
set -e

SERVER="deploy@your-server-ip"
APP_DIR="/home/deploy/myapp"

echo "Deploying..."

# Push code
rsync -avz --exclude='node_modules' --exclude='venv' --exclude='.git' \
    ./ $SERVER:$APP_DIR/

# Run remote commands
ssh $SERVER << 'EOF'
    cd /home/deploy/myapp

    # Python
    source venv/bin/activate
    pip install -r requirements.txt
    python manage.py migrate
    python manage.py collectstatic --noinput

    # Restart
    sudo supervisorctl restart myapp
EOF

echo "Done!"

Run ./deploy.sh and your changes are live. No Docker, no CI pipeline, no YAML files. Just rsync and SSH.

Monitoring: The Minimum

You need to know when your app is down. At minimum:

1. Log Rotation

# /etc/logrotate.d/myapp
/home/deploy/logs/*.log {
    daily
    rotate 14
    compress
    missingok
    notifempty
}

2. Basic Health Check

A simple cron job that checks if your app responds:

# Check every 5 minutes
*/5 * * * * curl -sf https://myapp.com/health/ > /dev/null || echo "APP DOWN" | mail -s "Alert" [email protected]

3. Disk Space Alerts

# Alert if disk is over 90%
0 */6 * * * df -h / | awk 'NR==2 && int($5)>90 {print "Disk usage: "$5}' | mail -s "Disk Alert" [email protected]

Key Takeaways

  • cPanel is perfectly fine for many projects - use its Node.js/Python app managers
  • For VPS, the minimum stack is: Nginx + Supervisor + Let's Encrypt
  • Always disable root login and password authentication
  • A simple rsync + SSH deployment script beats complex CI/CD for solo projects
  • Automated PostgreSQL backups are non-negotiable
  • Monitor at minimum: is the app responding, and is the disk full
  • Don't use Docker unless you have a specific reason to - it adds complexity for solo deployments

The goal isn't a perfect infrastructure - it's an infrastructure you can debug, maintain, and hand off without a 50-page runbook.


These patterns work for small-to-medium projects deployed by a single developer. If you're running a team or handling serious traffic, invest in proper DevOps.