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
- Lesson 1: Linux Introduction
- Lesson 2: Installing Linux
- Lesson 3: Terminal Basics
- Lesson 4: Filesystem Navigation
- Lesson 5: File and Directory Operations
- Lesson 6: Setting Up a Dev Environment
- Lesson 7: Text Editors
- Lesson 8: File Permissions
- Lesson 9: User and Group Management
- Lesson 10: Environment Variables and PATH
- Lesson 11: Multi-User Dev Environment
- Lesson 12: Standard Streams and Redirection
- Lesson 13: Pipes and Filters
- Lesson 14: Text Search
- Lesson 15: Text Processing
- Lesson 16: Server Log Analysis
- Lesson 17: Process Management
- Lesson 18: Package Management
- Lesson 19: Archives and Compression
- Lesson 20: Bash Scripting Basics
- Lesson 21: Bash Scripting Advanced
- Lesson 22: Automated Backup Script
- Lesson 23: Networking Basics
- Lesson 24: Firewall and Security
- Lesson 25: Task Automation
1. What You'll Learn
- Server initialization full workflow
- LEMP stack setup
- Deploying Node.js/Python applications
- HTTPS certificate configuration
- Monitoring and alerting system
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:
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
□ 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
(2) LEMP Stack (Linux + Nginx + MySQL + PHP/Node.js/Python)
# 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
#!/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:
TEXTnpm 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)
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.
# 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:
TEXTSaving 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
#!/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
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.
#!/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
#!/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 statusto check ports. 2)ss -tlnpto check service listen addresses. 3)grep "Failed password" /var/log/auth.log | wc -lto check attack volume. 4) Use https://www.ssllabs.com/ssltest/ to test SSL configuration.
📖 Summary
- Server initialization 10-step flow: system update → user → SSH hardening → firewall → fail2ban → Nginx → app → HTTPS → backup → monitoring
- Nginx reverse proxy:
proxy_pass http://127.0.0.1:3000 - certbot:
--nginx -d example.comfor automatic HTTPS - PM2:
pm2 start app.js -i maxfor multi-process management - Health check:
curl -o /dev/null -w "%{http_code}" http://localhost/health
📝 Exercises
- Basic (⭐): Using a VM or cloud server, execute the
setup-server.shscript from scratch, deploy a simple Node.js or Python Web app, and access it through the Nginx reverse proxy - Intermediate (⭐⭐): Configure a Let's Encrypt HTTPS certificate (use
--dry-runfor testing), write and deploy a health check script, and set up alerting (email or webhook) - 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