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.
# 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
# /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
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)\"}"
Your router's NAT blocks incoming connections. You need to forward ports 80 (HTTP) and 443 (HTTPS) to your server's local IP.
# 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
# /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