Linux: Capstone Project

This is the final project of the course. You'll combine all skills from the 25 preceding lessons — starting from a bare machine to build a fully functional Web development server.

📋 Prerequisites: You should already know

1. What You'll Learn


2. Xiaoming's "Go Live Tomorrow" Challenge

(1) The Pain Point: New Server, Must Go Live Tomorrow

Xiaoming's company bought a cloud server. The boss said: "Go live tomorrow." Facing a bare machine with only an IP and root password, Xiaoming needed to apply everything he'd learned.

(2) 10 Steps from Bare Metal to Production

Xiaoming made a plan:

BASH

1. SSH login           → 6. Deploy Node.js app
2. User creation + SSH hardening → 7. Configure HTTPS certificate
3. Firewall config     → 8. Configure PM2 process manager
4. Install Nginx       → 9. Automated backup
5. Configure reverse proxy → 10. Health check + alerting

(3) The Benefit: Delivered in 3 Hours

Xiaoming started at 2 PM, executed the plan step by step, and completed the full deployment by 5 PM. The boss tested it and replied: "Good, ready to go live."


3. Knowledge Points

(1) Server Initialization Checklist

BASH
□ SSH login (root password)
□ Update system (apt update && apt upgrade)
□ Create regular user (useradd -m -s /bin/bash)
□ Configure sudo (usermod -aG sudo)
□ SSH key authentication
□ Disable root + password login
□ Firewall (ufw: 22/80/443)
□ fail2ban protection
□ Automatic security updates
⚠️ Warning: Before disabling password login, make sure SSH keys are properly configured and tested. Otherwise you'll be locked out of the server. Recommended order: 1) Configure keys; 2) Log in successfully with keys; 3) Confirm everything works, then disable password login. Always keep an active SSH session as a safety net.

(2) LEMP Stack (Linux + Nginx + MySQL + PHP/Node.js/Python)

BASH
# Nginx
sudo apt install nginx
sudo systemctl enable --now nginx

# If using MySQL
sudo apt install mysql-server
sudo mysql_secure_installation

# Application deployment comparison
Method Best For Pros Cons
systemd service Any language Native, mature, stable Slightly complex config
PM2 Node.js Zero config, built-in monitoring Node.js only
Gunicorn/uWSGI Python Good performance, WSGI standard Needs Nginx
Docker Compose Complex apps Consistent env, easy to scale Requires Docker knowledge

(3) ▶ Example: Deploy a Node.js App to Nginx

BASH
#!/bin/bash
# Deploy a Node.js app to production

# 1. Create app directory
sudo mkdir -p /opt/webapp
sudo chown -R alice:alice /opt/webapp

# 2. Clone app files (assuming already developed)
git clone https://github.com/example/webapp.git /opt/webapp

# 3. Install dependencies and build
cd /opt/webapp
npm install
npm run build

# 4. Configure PM2
npm install -g pm2
pm2 start server.js --name webapp -i max
pm2 save
pm2 startup

# 5. Configure Nginx reverse proxy
sudo tee /etc/nginx/sites-available/webapp << 'EOF'
server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
        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;
    }

    location /static/ {
        alias /opt/webapp/public/;
        expires 30d;
    }
}
EOF

sudo ln -sf /etc/nginx/sites-available/webapp /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginx

Output:

TEXT
npm WARN deprecated package@1.0.0
added 245 packages in 12s
Building production bundle...
[PM2] Process webapp started
┌────┬──────────┬─────────┬─────────┐
│ id │ name     │ status  │ cpu     │
├────┼──────────┼─────────┼─────────┤
│ 0  │ webapp   │ online  │ 0%      │
└────┴──────────┴─────────┴─────────┘
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

(4) ▶ Example: HTTPS (Let's Encrypt + certbot)

💡 Tip: certbot automatically adds a systemd timer or cron job to renew certificates. After configuration, test the renewal process with sudo certbot renew --dry-run. It's also wise to set a calendar reminder to manually verify renewal succeeded about 30 days before expiry (around day 60), in case a config change broke auto-renewal.

BASH
# Install certbot
sudo apt install certbot python3-certbot-nginx -y

# Obtain certificate (automatically modifies Nginx config)
sudo certbot --nginx -d example.com -d www.example.com

# View certificates
sudo certbot certificates

# Verify auto-renewal
sudo certbot renew --dry-run

# Certificate auto-renews (certbot adds a systemd timer)
sudo systemctl list-timers | grep certbot

Output:

TEXT
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Requesting a certificate for example.com and www.example.com
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/example.com/fullchain.pem

Found the following certs:
  Certificate Name: example.com
    Expiry Date: 2026-10-05 10:23:45+00:00 (VALID: 89 days)

Congratulations, all simulated renewals succeeded:
  /etc/letsencrypt/live/example.com/fullchain.pem

certbot.timer  certbot.service  Sat 2026-07-08 00:00:00 UTC  13h left

(5) ▶ Example: Health Check Script

BASH
#!/bin/bash
# health-check.sh - Multi-service health check

check_service() {
    local name="$1"
    local url="$2"
    local expect="${3:-200}"

    local status=$(curl -s -o /dev/null -w "%{http_code}" "$url" 2>/dev/null)

    if [ "$status" = "$expect" ]; then
        echo "[OK] $name: $status (healthy)"
        return 0
    else
        echo "[FAIL] $name: $status (unhealthy)"
        return 1
    fi
}

echo "=== System Health Check ==="
echo "Time: $(date)"
echo ""

# System resources
echo "CPU: $(top -bn1 | grep "Cpu(s)" | awk '{print $2}')%"
echo "Memory: $(free -h | grep Mem | awk '{print $3 "/" $2}')"
echo "Disk: $(df -h / | tail -1 | awk '{print $3 "/" $2 " (" $5 ")"}')"
echo ""

# Web services
check_service "Nginx" "http://localhost/nginx_status" 200
check_service "WebApp" "http://localhost:3000/health" 200
check_service "HTTPS" "https://example.com/" 200

echo ""
echo "=== Check Complete ==="

Output:

TEXT
=== System Health Check ===
Time: Tue Jul  7 10:30:00 UTC 2026

CPU: 2.3%
Memory: 1.2Gi/3.9Gi
Disk: 12G/50G (24%)

[OK] Nginx: 200 (healthy)
[OK] WebApp: 200 (healthy)
[OK] HTTPS: 200 (healthy)

=== Check Complete ===

(6) ▶ Example: Complete Deployment Script

⚠️ Warning: The curl -fsSL URL | bash - pattern executes a remote script directly on your machine, which carries supply-chain attack risk — if the source is compromised or DNS is hijacked, you could execute malicious code. In production, the safer approach is to download and review the script first, then execute: curl -fsSL URL -o setup.sh && less setup.sh && bash setup.sh.

BASH
#!/bin/bash
# setup-server.sh - Build a Web dev server from scratch
# Usage: sudo ./setup-server.sh example.com

set -euo pipefail

DOMAIN="${1:-example.com}"
APP_DIR="/opt/webapp"
APP_PORT=3000
ADMIN_USER="admin"

echo "========================================"
echo "    Build Web Dev Server from Scratch"
echo "    Domain: $DOMAIN"
echo "========================================"

# 1. System update
echo ""
echo ">>> 1/10 Updating system..."
apt update && apt upgrade -y

# 2. Create regular user
echo ""
echo ">>> 2/10 Creating admin user..."
if ! id "$ADMIN_USER" &>/dev/null; then
    useradd -m -s /bin/bash "$ADMIN_USER"
    usermod -aG sudo "$ADMIN_USER"
    echo "Set password for $ADMIN_USER:"
    passwd "$ADMIN_USER"

    # Note: Manually add your SSH public key to /home/$ADMIN_USER/.ssh/authorized_keys
    mkdir -p /home/$ADMIN_USER/.ssh
    cp /root/.ssh/authorized_keys /home/$ADMIN_USER/.ssh/ 2>/dev/null || true
    chown -R $ADMIN_USER:$ADMIN_USER /home/$ADMIN_USER/.ssh
    chmod 700 /home/$ADMIN_USER/.ssh
    chmod 600 /home/$ADMIN_USER/.ssh/authorized_keys
fi

# 3. SSH hardening
echo ""
echo ">>> 3/10 Hardening SSH..."
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup
sed -i 's/^PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
sed -i 's/^#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
sed -i 's/^#PubkeyAuthentication yes/PubkeyAuthentication yes/' /etc/ssh/sshd_config
systemctl restart sshd

# 4. Firewall
echo ""
echo ">>> 4/10 Configuring firewall..."
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw --force enable

# 5. fail2ban
echo ""
echo ">>> 5/10 Installing fail2ban..."
apt install -y fail2ban
cat > /etc/fail2ban/jail.d/sshd.conf << EOF
[sshd]
enabled = true
maxretry = 3
bantime = 86400
findtime = 600
EOF
systemctl enable --now fail2ban

# 6. Install Nginx
echo ""
echo ">>> 6/10 Installing Nginx..."
apt install -y nginx
systemctl enable --now nginx

# 7. Install Node.js
echo ""
echo ">>> 7/10 Installing Node.js..."
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt install -y nodejs
npm install -g pm2

# 8. Configure Nginx reverse proxy
echo ""
echo ">>> 8/10 Configuring Nginx..."
cat > /etc/nginx/sites-available/$DOMAIN << EOF
server {
    listen 80;
    server_name $DOMAIN www.$DOMAIN;

    location / {
        proxy_pass http://127.0.0.1:$APP_PORT;
        proxy_http_version 1.1;
        proxy_set_header Upgrade \$http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host \$host;
        proxy_cache_bypass \$http_upgrade;
        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;
    }
}
EOF

ln -sf /etc/nginx/sites-available/$DOMAIN /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default
nginx -t && systemctl reload nginx

# 9. HTTPS certificate
echo ""
echo ">>> 9/10 Configuring HTTPS..."
apt install -y certbot python3-certbot-nginx
certbot --nginx --non-interactive --agree-tos \
    --email admin@$DOMAIN \
    -d $DOMAIN -d www.$DOMAIN \
    || echo "Warning: certbot failed, run manually: sudo certbot --nginx -d $DOMAIN"

# 10. Health check script
echo ""
echo ">>> 10/10 Setting up health check..."
mkdir -p /opt/scripts

cat > /opt/scripts/health-check.sh << 'EOF'
#!/bin/bash
WEBHOOK="https://hooks.slack.com/services/YOUR/WEBHOOK"

for svc in nginx sshd fail2ban; do
    if ! systemctl is-active --quiet "$svc"; then
        echo "[$(date)] FAIL: $svc is down" >> /var/log/health.log
    fi
done

DISK=$(df / | tail -1 | awk '{print $5}' | tr -d '%')
[ "$DISK" -gt 85 ] && echo "[$(date)] WARNING: Disk usage ${DISK}%" >> /var/log/health.log
EOF

chmod +x /opt/scripts/health-check.sh

# Add to cron
(crontab -l 2>/dev/null; echo "*/10 * * * * /opt/scripts/health-check.sh > /dev/null 2>&1") | crontab -

echo ""
echo "========================================"
echo "Server setup complete!"
echo ""
echo "Deployment info:"
echo "  Admin user: $ADMIN_USER"
echo "  Nginx: http://$DOMAIN"
echo "  HTTPS: https://$DOMAIN (if certbot succeeded)"
echo "  Node.js: $(node --version)"
echo "  PM2: $(pm2 --version)"
echo ""
echo "Next steps:"
echo "  1. Deploy your app code to $APP_DIR"
echo "  2. Start the app: pm2 start $APP_DIR/server.js --name webapp"
echo "  3. Log in as $ADMIN_USER (root login is disabled)"
echo "========================================"

Output:

TEXT
========================================
    Build Web Dev Server from Scratch
    Domain: example.com
========================================

>>> 1/10 Updating system...
...
========================================
Server setup complete!
Admin user: admin
Nginx: http://example.com
Node.js: v20.11.0
========================================

(7) ▶ Comprehensive Example: Full Deployment Verification

BASH
#!/bin/bash
# verify-deploy.sh - Post-deployment verification

echo "========================================"
echo "    Deployment Verification"
echo "    Time: $(date)"
echo "========================================"
echo ""

errors=0

check() {
    local desc="$1"
    shift
    if "$@" &>/dev/null; then
        echo "  [OK] $desc"
    else
        echo "  [FAIL] $desc"
        errors=$((errors + 1))
    fi
}

echo "1. System Services"
echo "-----------"
check "Nginx running" systemctl is-active nginx
check "SSH running" systemctl is-active sshd
check "fail2ban running" systemctl is-active fail2ban
echo ""

echo "2. Network"
echo "--------"
check "Port 80 listening" ss -tlnp grep -q ":80 "
check "Port 443 listening" ss -tlnp grep -q ":443 "
check "ufw enabled" ufw status grep -q active
echo ""

echo "3. HTTP Service"
echo "-------------"
check "Local Nginx responding" curl -s -o /dev/null -w "%{http_code}" http://localhost grep -q 200
check "App process running" pm2 list 2>/dev/null grep -q "online" || pgrep -f "node server" > /dev/null
echo ""

echo "4. Security"
echo "--------"
check "Root SSH disabled" grep "^PermitRootLogin" /etc/ssh/sshd_config grep -q "no"
check "Password login disabled" grep "^PasswordAuthentication" /etc/ssh/sshd_config grep -q "no"
echo ""

echo "5. Resources"
echo "--------"
echo "  CPU: $(top -bn1 | grep 'Cpu(s)' | awk '{print $2"%"}' )"
echo "  Memory: $(free -h | grep Mem | awk '{print $3"/"$2}')"
echo "  Disk: $(df -h / | tail -1 | awk '{print $3"/"$2"("$5")"}')"
echo ""

echo "========================================"
if [ $errors -eq 0 ]; then
    echo "All verifications passed!"
else
    echo "$errors check(s) failed — please investigate"
fi
echo "========================================"

Output:

TEXT
========================================
    Deployment Verification
    Time: Tue Jul  7 10:45:00 UTC 2026
========================================

1. System Services
-----------
  [OK] Nginx running
  [OK] SSH running
  [OK] fail2ban running

2. Network
--------
  [OK] Port 80 listening
  [OK] Port 443 listening
  [OK] ufw enabled

3. HTTP Service
-------------
  [OK] Local Nginx responding
  [OK] App process running

4. Security
--------
  [OK] Root SSH disabled
  [OK] Password login disabled

5. Resources
--------
  CPU: 2.3%
  Memory: 1.2Gi/3.9Gi
  Disk: 12G/50G(24%)

========================================
All verifications passed!
========================================

❓ FAQ

Q: What should be the first thing to do when deploying a server? A: Update the system (apt update && apt upgrade), then create a regular user and configure SSH key authentication. Never work as root long-term.

Q: What's the relationship between Nginx reverse proxy and the application? A: Nginx is the "front door" and the application is "the people inside." Users access Nginx on port 80/443 → Nginx decides which backend app to forward the request to. Nginx handles static files, SSL termination, and load balancing; the app focuses on business logic.

Q: Do HTTPS certificates need periodic renewal? A: Let's Encrypt certificates are valid for 90 days. certbot automatically adds a systemd timer for renewal. Verify with: systemctl list-timers | grep certbot. Manual renewal: sudo certbot renew.

Q: What metrics should I monitor on a server? A: Core metrics: CPU usage, memory consumption, disk space (>80% alert), disk I/O, network traffic, whether services are running, and SSL certificate expiry.

Q: How do I verify security after deployment? A: Run security scanning tools: 1) sudo ufw status to check ports. 2) ss -tlnp to check service listen addresses. 3) grep "Failed password" /var/log/auth.log | wc -l to check attack volume. 4) Use https://www.ssllabs.com/ssltest/ to test SSL configuration.


📖 Summary


📝 Exercises

  1. Basic (⭐): Using a VM or cloud server, execute the setup-server.sh script from scratch, deploy a simple Node.js or Python Web app, and access it through the Nginx reverse proxy
  2. Intermediate (⭐⭐): Configure a Let's Encrypt HTTPS certificate (use --dry-run for testing), write and deploy a health check script, and set up alerting (email or webhook)
  3. Challenge (⭐⭐⭐⭐⭐): Configure a complete backup strategy (cron + rsync remote backup) and verify backups are restorable, then write an automated deployment verification script that checks all service statuses, security configurations, and performance metrics, and outputs a formatted acceptance report
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏