Hosting Your Own Cloud Server on Linux

Running your own VPS (Virtual Private Server) gives you full control over your web presence. This guide covers the complete stack: from choosing a provider to securing and monitoring your server.

Architecture Overview

Cloud Server Architecture 🌐 User Browser ☁ Internet DNS lookup 🏷 DNS / Domain Registrar + nameservers resolves to IP 🖥 VPS (Ubuntu/Debian Linux) IP: 203.0.113.42 | RAM: 2GB | CPU: 2 vCPU 🔒 Firewall (ufw) + Fail2Ban 🌐 Nginx / Apache (Web Server) 🔐 Let's Encrypt SSL/TLS (HTTPS) App (Node/Python) DB (MySQL/Postgres) 📊 Monitoring htop / Netdata / Prometheus Grafana dashboard 💾 Backups rsync / cron / S3 bucket Open ports: 22 (SSH) | 80 (HTTP) | 443 (HTTPS) Closed: everything else via ufw default deny incoming

Choosing a Cloud Provider

ProviderCheapest VPSOS OptionsBest For
DigitalOcean$4/mo (512MB RAM)Ubuntu, Debian, FedoraBeginners, clean UI
Hetzner€3.79/mo (2GB RAM)Ubuntu, Debian, CentOSBest price/performance in EU
Linode (Akamai)$5/mo (1GB RAM)Ubuntu, Debian, ArchReliable, good docs
Vultr$2.50/mo (512MB)Many distrosGlobal locations
AWS EC2Free tier 1 yearAmazon Linux, UbuntuEnterprise, complex

Initial Server Setup (Ubuntu 22.04)

Step 1 — First Login & System Update

# Connect via SSH (replace IP with your server's IP) $ ssh root@203.0.113.42 # Update package list and upgrade all packages root@vps:~# apt update && apt upgrade -y # Install essential tools root@vps:~# apt install -y curl wget git vim ufw fail2ban htop unzip net-tools

Step 2 — Create a Non-Root Admin User

Security rule: Never run your daily work as root. Create a sudo user and disable root SSH login.
# Create user (replace 'george' with your username) root@vps:~# adduser george root@vps:~# usermod -aG sudo george # Copy SSH keys to new user root@vps:~# rsync --archive --chown=george:george ~/.ssh /home/george # Test: open new terminal and connect as new user $ ssh george@203.0.113.42 george@vps:~$ sudo whoami # should print "root"

Step 3 — SSH Hardening

george@vps:~$ sudo nano /etc/ssh/sshd_config # Change these settings: PermitRootLogin no # disable root login PasswordAuthentication no # force key-based auth only Port 2222 # optional: change from default 22 AllowUsers george # only allow specific users MaxAuthTries 3 LoginGraceTime 20 george@vps:~$ sudo systemctl restart sshd

Step 4 — SSH Key Generation (on your local machine)

# On your LOCAL machine, not the server: local$ ssh-keygen -t ed25519 -C "george@myserver" -f ~/.ssh/id_server # Copy public key to server local$ ssh-copy-id -i ~/.ssh/id_server.pub george@203.0.113.42 # Now connect with key (no password needed) local$ ssh -i ~/.ssh/id_server george@203.0.113.42

Firewall Configuration (ufw)

ufw (Uncomplicated Firewall) is a simplified frontend to iptables. The strategy: deny everything by default, allow only what you need.

UFW Setup

# Deny all incoming, allow all outgoing (default policy) george@vps:~$ sudo ufw default deny incoming george@vps:~$ sudo ufw default allow outgoing # Allow SSH FIRST before enabling (otherwise you'll lock yourself out!) george@vps:~$ sudo ufw allow 2222/tcp # or 22 if unchanged # Allow HTTP and HTTPS george@vps:~$ sudo ufw allow 80/tcp george@vps:~$ sudo ufw allow 443/tcp # Enable firewall george@vps:~$ sudo ufw enable # View rules george@vps:~$ sudo ufw status verbose # Delete a rule if needed george@vps:~$ sudo ufw delete allow 8080/tcp # Rate-limit SSH to prevent brute force (max 6 attempts / 30 seconds) george@vps:~$ sudo ufw limit 2222/tcp

Fail2Ban — Auto-block Brute Force Attackers

george@vps:~$ sudo nano /etc/fail2ban/jail.local # Add this configuration: [DEFAULT] bantime = 1h # ban for 1 hour findtime = 10m # within 10 minutes maxretry = 5 # after 5 failed attempts [sshd] enabled = true port = 2222 filter = sshd logpath = /var/log/auth.log [nginx-http-auth] enabled = true george@vps:~$ sudo systemctl enable --now fail2ban # View banned IPs: george@vps:~$ sudo fail2ban-client status sshd # Unban an IP: george@vps:~$ sudo fail2ban-client set sshd unbanip 192.168.1.100

Domain Management & DNS

DNS Resolution Flow

DNS Resolution Flow: "mysite.com" → 203.0.113.42 Browser local cache Recursive Resolver (ISP) 8.8.8.8, 1.1.1.1 Root NS 13 root servers a.root-servers.net TLD NS Manages ".com" a.gtld-servers.net Authoritative NS Your registrar's NS ns1.namecheap.com ⑤ Returns IP: 203.0.113.42 (cached for TTL seconds) Common DNS Record Types A: hostname → IPv4 AAAA: hostname → IPv6 CNAME: alias → canonical name MX: mail server TXT: text (SPF, DKIM) NS: nameserver for zone PTR: reverse DNS

DNS Records — Practical Setup

# ── In your domain registrar's DNS panel ────────────────────────────────── # Replace 203.0.113.42 with your VPS IP # A record: root domain → VPS IP mysite.com. IN A 203.0.113.42 # CNAME: www → root domain (redirect www to non-www) www.mysite.com. IN CNAME mysite.com. # MX record: email (lower priority number = higher priority) mysite.com. IN MX 10 mail.mysite.com. # TXT: SPF record (prevent email spoofing) mysite.com. IN TXT "v=spf1 ip4:203.0.113.42 -all" # TTL recommendation: 300 during changes, 3600 (1h) stable

Check & Debug DNS

# Query A record for a domain $ dig mysite.com A +short # Trace full DNS resolution path $ dig mysite.com +trace # Check from a specific resolver (8.8.8.8 = Google DNS) $ dig @8.8.8.8 mysite.com # nslookup alternative $ nslookup mysite.com # Check if DNS has propagated globally (web tool equivalent) $ for ns in 8.8.8.8 1.1.1.1 9.9.9.9; do echo -n "$ns: "; dig @$ns mysite.com A +short done

Web Server Setup (Nginx)

Install & Configure Nginx

george@vps:~$ sudo apt install -y nginx # Enable and start nginx george@vps:~$ sudo systemctl enable --now nginx # Test config syntax before applying george@vps:~$ sudo nginx -t # Create a virtual host (server block) for mysite.com george@vps:~$ sudo nano /etc/nginx/sites-available/mysite.com
# /etc/nginx/sites-available/mysite.com server { listen 80; listen [::]:80; server_name mysite.com www.mysite.com; root /var/www/mysite; index index.html index.php; # Main location block location / { try_files $uri $uri/ =404; } # Proxy requests to Node.js app on port 3000 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; } # Security headers add_header X-Frame-Options DENY; add_header X-Content-Type-Options nosniff; add_header X-XSS-Protection "1; mode=block"; # Gzip compression gzip on; gzip_types text/plain text/css application/json application/javascript; gzip_min_length 256; # Logging access_log /var/log/nginx/mysite.access.log; error_log /var/log/nginx/mysite.error.log; }
# Enable site: symlink to sites-enabled george@vps:~$ sudo ln -s /etc/nginx/sites-available/mysite.com \ /etc/nginx/sites-enabled/ # Create web root george@vps:~$ sudo mkdir -p /var/www/mysite george@vps:~$ echo "<h1>Hello from mysite.com!</h1>" | sudo tee /var/www/mysite/index.html # Reload (zero-downtime config reload) george@vps:~$ sudo nginx -t && sudo systemctl reload nginx

SSL/TLS Certificates (HTTPS)

Let's Encrypt provides free, automatically-renewing SSL certificates. Certbot is the official client.

TLS Handshake Diagram

TLS 1.3 Handshake Browser Server ① ClientHello (TLS version, cipher suites, random) ② ServerHello + Certificate + Key Exchange ③ ServerFinished (encrypted) ④ ClientFinished (encrypted) + HTTP Request ⑤ HTTP Response (encrypted) 🔐 All data encrypted after step ② — only 1 RTT in TLS 1.3!

Install SSL with Certbot

# Install certbot and nginx plugin george@vps:~$ sudo apt install -y certbot python3-certbot-nginx # Obtain certificate for your domain (automatic nginx config) george@vps:~$ sudo certbot --nginx -d mysite.com -d www.mysite.com \ --email you@email.com --agree-tos --no-eff-email # Certbot automatically: # 1. Obtains cert from Let's Encrypt via HTTP-01 challenge # 2. Updates nginx config to redirect HTTP → HTTPS # 3. Sets up auto-renewal cron job # Test auto-renewal (dry run) george@vps:~$ sudo certbot renew --dry-run # Check certificate expiry george@vps:~$ sudo certbot certificates # Manual renewal george@vps:~$ sudo certbot renew

Nginx HTTPS Config (after certbot)

# /etc/nginx/sites-available/mysite.com (HTTPS version) server { listen 80; server_name mysite.com www.mysite.com; return 301 https://$host$request_uri; # redirect HTTP to HTTPS } server { listen 443 ssl http2; server_name mysite.com www.mysite.com; ssl_certificate /etc/letsencrypt/live/mysite.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/mysite.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; # HSTS: browser remembers HTTPS for 1 year add_header Strict-Transport-Security "max-age=31536000" always; root /var/www/mysite; index index.html; location / { try_files $uri $uri/ =404; } }

Server Monitoring & Maintenance

Essential Monitoring Commands

# Real-time CPU, memory, processes george@vps:~$ htop # Disk usage george@vps:~$ df -h # disk free by filesystem george@vps:~$ du -sh /var/* # size of /var subdirectories # Memory george@vps:~$ free -h # Network connections george@vps:~$ ss -tlnp # listening ports george@vps:~$ netstat -an | grep ESTABLISHED # Live log tailing george@vps:~$ sudo tail -f /var/log/nginx/access.log george@vps:~$ sudo journalctl -u nginx -f # systemd journal # Failed login attempts george@vps:~$ sudo grep "Failed password" /var/log/auth.log | tail -20 # Current logins george@vps:~$ who && last -n 10

Automated Backups with rsync + cron

# Create backup script george@vps:~$ sudo nano /usr/local/bin/backup.sh #!/bin/bash BACKUP_DIR="/mnt/backup" DATE=$(date +%Y%m%d_%H%M) SRC="/var/www /etc/nginx /home/george" # Incremental backup with hard links (space-efficient) rsync -az --delete --link-dest=$BACKUP_DIR/latest \ $SRC $BACKUP_DIR/$DATE/ ln -snf $BACKUP_DIR/$DATE $BACKUP_DIR/latest # Keep only last 30 daily backups find $BACKUP_DIR -maxdepth 1 -type d -mtime +30 -exec rm -rf {} \; george@vps:~$ sudo chmod +x /usr/local/bin/backup.sh # Schedule with cron: daily at 3:30 AM george@vps:~$ sudo crontab -e # Add line: 30 3 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

System Security Audit Checklist

CategoryCheckCommand
SSHRoot login disabledgrep PermitRootLogin /etc/ssh/sshd_config
SSHPassword auth disabledgrep PasswordAuthentication /etc/ssh/sshd_config
FirewallUFW activesudo ufw status
UpdatesAuto security updatessudo apt install unattended-upgrades
SSLCertificate validsudo certbot certificates
Fail2BanActive & watching SSHsudo fail2ban-client status
PortsOnly needed ports opensudo ss -tlnp
UpdatesOS up to datesudo apt list --upgradable