Chapter 4 — Self-Hosting (Bare Metal / Home Server)

Self-hosting means running a web server on hardware you physically control — a spare PC, a Raspberry Pi, or a rack server in your closet. It's the most educational option and gives maximum control.

When Self-Hosting Makes Sense

When it does NOT make sense: Production websites serving external users. Your home internet has no SLA, dynamic IP, limited upload bandwidth, and a single point of failure (power outage = site down). Use self-hosting for learning, internal tools, and development — not for serving customers.

Setting Up Nginx on Linux

# Install Nginx (Ubuntu/Debian)
sudo apt update && sudo apt install -y nginx

# Start and enable
sudo systemctl start nginx
sudo systemctl enable nginx

# Verify it's running
curl http://localhost
# Should return the Nginx welcome page HTML

Virtual Hosts (Serving Multiple Sites)

# /etc/nginx/sites-available/mysite.conf
server {
    listen 80;
    server_name mysite.example.com;
    root /var/www/mysite;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }

    # For a backend app (reverse proxy)
    location /api/ {
        proxy_pass http://127.0.0.1:3000;
        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 the site
sudo ln -s /etc/nginx/sites-available/mysite.conf /etc/nginx/sites-enabled/
sudo nginx -t          # Test configuration
sudo systemctl reload nginx

Dynamic DNS (Solving the Dynamic IP Problem)

Home internet usually gives you a dynamic IP that changes periodically. Dynamic DNS services map a hostname to your current IP.

# Using ddclient with Cloudflare (install: sudo apt install ddclient)
# /etc/ddclient.conf
protocol=cloudflare
zone=example.com
login=your-email@example.com
password=your-cloudflare-api-token
use=web, web=https://api.ipify.org
mysite.example.com

# Or use a cron job with curl
*/5 * * * * curl -s "https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records/RECORD_ID" \
  -X PATCH \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  --data "{\"content\":\"$(curl -s https://api.ipify.org)\"}"

Port Forwarding

Your router's NAT blocks incoming connections. You need to forward ports 80 (HTTP) and 443 (HTTPS) to your server's local IP.

  1. Give your server a static local IP (e.g., 192.168.1.100) via DHCP reservation
  2. In your router admin panel: forward external port 80 → 192.168.1.100:80
  3. Forward external port 443 → 192.168.1.100:443
  4. Test from outside your network (use your phone on mobile data)

Let's Encrypt (Free SSL/TLS)

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

# Obtain certificate (Nginx plugin auto-configures)
sudo certbot --nginx -d mysite.example.com

# Auto-renewal is set up automatically via systemd timer
sudo systemctl status certbot.timer

# Manual renewal test
sudo certbot renew --dry-run

Running Your App as a systemd Service

# /etc/systemd/system/myapp.service
[Unit]
Description=My Web Application
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/node /opt/myapp/server.js
Restart=always
RestartSec=5
Environment=NODE_ENV=production
Environment=PORT=3000

# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/myapp/data

[Install]
WantedBy=multi-user.target

# Enable and start
sudo systemctl daemon-reload
sudo systemctl enable myapp
sudo systemctl start myapp
sudo systemctl status myapp    # Check it's running
sudo journalctl -u myapp -f   # View logs
systemd is your process manager on Linux. It handles starting your app on boot, restarting on crash, logging, and resource limits. Learn it well — you'll use it on every VPS and server you manage.