VPS Ubuntu Management Skill
This skill covers the full lifecycle of Ubuntu VPS management.
Load the relevant reference file for detailed commands:
| Topic |
Reference file |
Load when... |
| Firewall & network |
references/firewall.md |
UFW, iptables, open ports, rate limiting |
| Security hardening |
references/security.md |
SSH, fail2ban, kernel params, auditing, rootkits, ClamAV |
| Backups |
references/backups.md |
rsync, Borg, rclone, cloud sync, backup testing |
| Monitoring & alerts |
references/monitoring.md |
disk, memory, CPU, log analysis, alerting, health checks |
| Cron jobs |
references/cron.md |
crontab syntax, scheduling, debugging, systemd timers |
| Web server |
references/webserver.md |
Nginx, SSL, PM2, reverse proxy, compression, rate limiting |
| Containers |
references/containers.md |
Docker, Docker Compose, networking, image cleanup |
Server Provisioning — Fresh Ubuntu VPS
Run these steps immediately after first SSH login to a new server:
# 1. Update system
apt update && apt upgrade -y
# 2. Set hostname and timezone
hostnamectl set-hostname myserver
timedatectl set-timezone UTC # or your preferred timezone
# 3. Create deploy user (never run apps as root)
adduser deploy
usermod -aG sudo deploy
# 4. Copy SSH key to deploy user
mkdir -p /home/deploy/.ssh
cp /root/.ssh/authorized_keys /home/deploy/.ssh/
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh && chmod 600 /home/deploy/.ssh/authorized_keys
# 5. Firewall — allow SSH before enabling
ufw default deny incoming && ufw default allow outgoing
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable
# 6. Harden SSH (see references/security.md for full config)
sed -i 's/#PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
sshd -t && systemctl reload ssh
# 7. Install essentials
apt install -y curl wget git unzip htop tmux fail2ban ufw \
certbot python3-certbot-nginx logwatch unattended-upgrades
# 8. Enable automatic security updates
dpkg-reconfigure --priority=low unattended-upgrades
# 9. Set up fail2ban
cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
systemctl enable --now fail2ban
Quick Diagnostics — Run These First
When a user reports a server problem, start with:
# System overview
uptime && free -h && df -h
# Top processes
ps aux --sort=-%cpu | head -15
# Network connections
ss -tulpn
# Recent errors
journalctl -p err -n 50 --no-pager
# Failed systemd services
systemctl --failed
# Last logins and failed SSH attempts
last -n 20
grep "Failed password" /var/log/auth.log | tail -20
Server Health Scorecard
Before doing anything else on a VPS, run this mental checklist:
Emergency Procedures
Server is unreachable / SSH locked out
- Use VPS provider's web console (DigitalOcean, Hetzner, Vultr all have one)
- Check if UFW blocked your IP:
ufw status then ufw allow from YOUR_IP
- fail2ban may have banned you:
fail2ban-client status sshd then fail2ban-client set sshd unbanip YOUR_IP
- Check SSH service:
systemctl status ssh
Disk 100% full — server broken
# Find what's eating space
du -sh /* 2>/dev/null | sort -rh | head -20
du -sh /var/log/* | sort -rh | head -10
# Emergency cleanup
journalctl --vacuum-size=100M # trim systemd logs
apt clean # clear apt cache
docker system prune -f 2>/dev/null # clear Docker if installed
find /tmp -mtime +7 -delete # old temp files
# Rotate logs immediately
logrotate -f /etc/logrotate.conf
Server under attack / high load
# See what's hammering the server
netstat -ntu | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head -20
# Block an IP immediately
ufw deny from ATTACKER_IP
# Find CPU-hungry processes
ps aux --sort=-%cpu | head -10
# Kill runaway process
kill -9 PID
Memory exhausted / OOM killer
# Check OOM kill history
dmesg | grep -i "killed process"
grep -i "out of memory" /var/log/syslog | tail -20
# See what's using RAM
ps aux --sort=-%mem | head -15
free -h
# Add emergency swap (if none exists)
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
Safety Rules — Always Follow
Before running any destructive command, state it and ask for confirmation:
rm -rf anything — STOP, confirm path and scope
ufw reset — will drop ALL firewall rules
iptables -F — flushes all rules, server becomes open
- Editing
/etc/ssh/sshd_config — test before reload or you risk lockout
reboot / shutdown — confirm timing with user
- Dropping a database — always confirm and verify backup exists first
docker system prune -a — removes all unused images, not just dangling
Always test SSH config before restarting:
sshd -t # test config validity — ALWAYS run before: systemctl restart ssh
Tailscale VPN Setup & SSH Connection
Install Tailscale
curl -fsSL https://tailscale.com/install.sh | sh
tailscale up --ssh # enable Tailscale SSH (optional)
tailscale status # verify connection
tailscale ip -4 # get Tailscale IPv4 address
Keep-Alive Configuration (prevents drop after inactivity)
Local ~/.ssh/config — prevents client-side timeout:
Host vps-tailscale
HostName 100.x.x.x
User youruser
ServerAliveInterval 60
ServerAliveCountMax 10
TCPKeepAlive yes
ConnectTimeout 30
VPS /etc/ssh/sshd_config — prevents server-side timeout:
ClientAliveInterval 120
ClientAliveCountMax 10
TCPKeepAlive yes
Always run sshd -t && systemctl reload ssh after editing.
Why it drops: VPS providers (Hetzner, DO, Vultr) NAT-timeout idle UDP connections
in 60-90 seconds, which silently kills the WireGuard tunnel Tailscale uses.
ServerAliveInterval 60 keeps SSH traffic flowing through the tunnel, preventing the drop.
Reconnect Command (if session drops anyway)
tailscale status # verify tunnel is up
ssh vps-tailscale # reconnect using config alias
WireGuard (manual alternative to Tailscale)
apt install wireguard -y
# Generate keys
wg genkey | tee /etc/wireguard/private.key | wg pubkey > /etc/wireguard/public.key
chmod 600 /etc/wireguard/private.key
# Config: /etc/wireguard/wg0.conf
[Interface]
PrivateKey = <server-private-key>
Address = 10.0.0.1/24
ListenPort = 51820
[Peer]
PublicKey = <client-public-key>
AllowedIPs = 10.0.0.2/32
# Enable
systemctl enable --now wg-quick@wg0
ufw allow 51820/udp
Unattended Upgrades
apt install unattended-upgrades -y
dpkg-reconfigure --priority=low unattended-upgrades
# Verify enabled
cat /etc/apt/apt.conf.d/20auto-upgrades
# Should show:
# APT::Periodic::Update-Package-Lists "1";
# APT::Periodic::Unattended-Upgrade "1";
# Check what will be upgraded
unattended-upgrades --dry-run --debug
# View upgrade log
cat /var/log/unattended-upgrades/unattended-upgrades.log
Log Rotation Quick Reference
# Check existing configs
ls /etc/logrotate.d/
# Test config (dry run)
logrotate -d /etc/logrotate.d/myapp
# Force immediate rotation
logrotate -f /etc/logrotate.conf
# Example: /etc/logrotate.d/myapp
/var/log/myapp/*.log {
daily
rotate 14
compress
delaycompress
missingok
notifempty
create 0640 www-data www-data
postrotate
systemctl reload myapp > /dev/null 2>&1 || true
endscript
}
1---2name: vps-ubuntu3description: Manage, secure, monitor, and maintain Ubuntu VPS servers. Use this skill whenever the user mentions VPS, Ubuntu server, Linux server, firewall, UFW, iptables, fail2ban, SSH hardening, security hardening, server security, intrusion detection, rootkit detection, rkhunter, chkrootkit, ClamAV, auditd, server backups, rsync backup, borgbackup, automated backups, rclone, cron jobs, crontab, scheduled tasks, disk space, df, du, disk usage, out of disk, memory usage, swap, RAM, server monitoring, htop, netstat, ss, open ports, system health, server performance, log rotation, logwatch, unattended upgrades, automatic updates, server hardening, sysctl, kernel parameters, brute force protection, Nginx, Apache, SSL certificates, Certbot, Let's Encrypt, PM2, systemd services, process management, server deployment, Docker, containers, Docker Compose, Tailscale, WireGuard, VPN, server provisioning, or any task involving managing a Linux/Ubuntu VPS or dedicated server.4---56# VPS Ubuntu Management Skill78This skill covers the full lifecycle of Ubuntu VPS management.9Load the relevant reference file for detailed commands:1011| Topic | Reference file | Load when... |12|---|---|---|13| Firewall & network | `references/firewall.md` | UFW, iptables, open ports, rate limiting |14| Security hardening | `references/security.md` | SSH, fail2ban, kernel params, auditing, rootkits, ClamAV |15| Backups | `references/backups.md` | rsync, Borg, rclone, cloud sync, backup testing |16| Monitoring & alerts | `references/monitoring.md` | disk, memory, CPU, log analysis, alerting, health checks |17| Cron jobs | `references/cron.md` | crontab syntax, scheduling, debugging, systemd timers |18| Web server | `references/webserver.md` | Nginx, SSL, PM2, reverse proxy, compression, rate limiting |19| Containers | `references/containers.md` | Docker, Docker Compose, networking, image cleanup |2021---2223## Server Provisioning — Fresh Ubuntu VPS2425Run these steps immediately after first SSH login to a new server:2627```bash28# 1. Update system29apt update && apt upgrade -y3031# 2. Set hostname and timezone32hostnamectl set-hostname myserver33timedatectl set-timezone UTC # or your preferred timezone3435# 3. Create deploy user (never run apps as root)36adduser deploy37usermod -aG sudo deploy3839# 4. Copy SSH key to deploy user40mkdir -p /home/deploy/.ssh41cp /root/.ssh/authorized_keys /home/deploy/.ssh/42chown -R deploy:deploy /home/deploy/.ssh43chmod 700 /home/deploy/.ssh && chmod 600 /home/deploy/.ssh/authorized_keys4445# 5. Firewall — allow SSH before enabling46ufw default deny incoming && ufw default allow outgoing47ufw allow 22/tcp48ufw allow 80/tcp49ufw allow 443/tcp50ufw enable5152# 6. Harden SSH (see references/security.md for full config)53sed -i 's/#PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config54sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config55sshd -t && systemctl reload ssh5657# 7. Install essentials58apt install -y curl wget git unzip htop tmux fail2ban ufw \59 certbot python3-certbot-nginx logwatch unattended-upgrades6061# 8. Enable automatic security updates62dpkg-reconfigure --priority=low unattended-upgrades6364# 9. Set up fail2ban65cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local66systemctl enable --now fail2ban67```6869---7071## Quick Diagnostics — Run These First7273When a user reports a server problem, start with:7475```bash76# System overview77uptime && free -h && df -h7879# Top processes80ps aux --sort=-%cpu | head -158182# Network connections83ss -tulpn8485# Recent errors86journalctl -p err -n 50 --no-pager8788# Failed systemd services89systemctl --failed9091# Last logins and failed SSH attempts92last -n 2093grep "Failed password" /var/log/auth.log | tail -2094```9596---9798## Server Health Scorecard99100Before doing anything else on a VPS, run this mental checklist:101102- [ ] **Disk**: `df -h` — any partition >80% full?103- [ ] **Memory**: `free -h` — swap in use? High?104- [ ] **Load**: `uptime` — load average > number of CPUs?105- [ ] **Firewall**: `ufw status` — active and rules correct?106- [ ] **Updates**: `apt list --upgradable 2>/dev/null | wc -l` — security updates pending?107- [ ] **Failed services**: `systemctl --failed` — anything crashed?108- [ ] **Auth log**: `grep "Failed password" /var/log/auth.log | wc -l` — brute force?109- [ ] **Backups**: last backup timestamp — recent and verified?110- [ ] **SSL certs**: `certbot certificates` — expiry within 30 days?111- [ ] **Docker**: `docker system df` — disk consumed by images/volumes?112- [ ] **Tailscale**: `tailscale status` — connected to mesh?113114---115116## Emergency Procedures117118### Server is unreachable / SSH locked out1191. Use VPS provider's web console (DigitalOcean, Hetzner, Vultr all have one)1202. Check if UFW blocked your IP: `ufw status` then `ufw allow from YOUR_IP`1213. fail2ban may have banned you: `fail2ban-client status sshd` then `fail2ban-client set sshd unbanip YOUR_IP`1224. Check SSH service: `systemctl status ssh`123124### Disk 100% full — server broken125```bash126# Find what's eating space127du -sh /* 2>/dev/null | sort -rh | head -20128du -sh /var/log/* | sort -rh | head -10129130# Emergency cleanup131journalctl --vacuum-size=100M # trim systemd logs132apt clean # clear apt cache133docker system prune -f 2>/dev/null # clear Docker if installed134find /tmp -mtime +7 -delete # old temp files135136# Rotate logs immediately137logrotate -f /etc/logrotate.conf138```139140### Server under attack / high load141```bash142# See what's hammering the server143netstat -ntu | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head -20144145# Block an IP immediately146ufw deny from ATTACKER_IP147148# Find CPU-hungry processes149ps aux --sort=-%cpu | head -10150151# Kill runaway process152kill -9 PID153```154155### Memory exhausted / OOM killer156```bash157# Check OOM kill history158dmesg | grep -i "killed process"159grep -i "out of memory" /var/log/syslog | tail -20160161# See what's using RAM162ps aux --sort=-%mem | head -15163free -h164165# Add emergency swap (if none exists)166fallocate -l 2G /swapfile167chmod 600 /swapfile168mkswap /swapfile169swapon /swapfile170```171172---173174## Safety Rules — Always Follow175176Before running any destructive command, state it and ask for confirmation:177- `rm -rf` anything — STOP, confirm path and scope178- `ufw reset` — will drop ALL firewall rules179- `iptables -F` — flushes all rules, server becomes open180- Editing `/etc/ssh/sshd_config` — test before reload or you risk lockout181- `reboot` / `shutdown` — confirm timing with user182- Dropping a database — always confirm and verify backup exists first183- `docker system prune -a` — removes all unused images, not just dangling184185Always test SSH config before restarting:186```bash187sshd -t # test config validity — ALWAYS run before: systemctl restart ssh188```189190---191192## Tailscale VPN Setup & SSH Connection193194### Install Tailscale195```bash196curl -fsSL https://tailscale.com/install.sh | sh197tailscale up --ssh # enable Tailscale SSH (optional)198tailscale status # verify connection199tailscale ip -4 # get Tailscale IPv4 address200```201202### Keep-Alive Configuration (prevents drop after inactivity)203204**Local `~/.ssh/config`** — prevents client-side timeout:205```206Host vps-tailscale207 HostName 100.x.x.x208 User youruser209 ServerAliveInterval 60210 ServerAliveCountMax 10211 TCPKeepAlive yes212 ConnectTimeout 30213```214215**VPS `/etc/ssh/sshd_config`** — prevents server-side timeout:216```217ClientAliveInterval 120218ClientAliveCountMax 10219TCPKeepAlive yes220```221Always run `sshd -t && systemctl reload ssh` after editing.222223**Why it drops**: VPS providers (Hetzner, DO, Vultr) NAT-timeout idle UDP connections224in 60-90 seconds, which silently kills the WireGuard tunnel Tailscale uses.225`ServerAliveInterval 60` keeps SSH traffic flowing through the tunnel, preventing the drop.226227### Reconnect Command (if session drops anyway)228```bash229tailscale status # verify tunnel is up230ssh vps-tailscale # reconnect using config alias231```232233### WireGuard (manual alternative to Tailscale)234```bash235apt install wireguard -y236237# Generate keys238wg genkey | tee /etc/wireguard/private.key | wg pubkey > /etc/wireguard/public.key239chmod 600 /etc/wireguard/private.key240241# Config: /etc/wireguard/wg0.conf242[Interface]243PrivateKey = <server-private-key>244Address = 10.0.0.1/24245ListenPort = 51820246247[Peer]248PublicKey = <client-public-key>249AllowedIPs = 10.0.0.2/32250251# Enable252systemctl enable --now wg-quick@wg0253ufw allow 51820/udp254```255256---257258## Unattended Upgrades259260```bash261apt install unattended-upgrades -y262dpkg-reconfigure --priority=low unattended-upgrades263264# Verify enabled265cat /etc/apt/apt.conf.d/20auto-upgrades266# Should show:267# APT::Periodic::Update-Package-Lists "1";268# APT::Periodic::Unattended-Upgrade "1";269270# Check what will be upgraded271unattended-upgrades --dry-run --debug272273# View upgrade log274cat /var/log/unattended-upgrades/unattended-upgrades.log275```276277---278279## Log Rotation Quick Reference280281```bash282# Check existing configs283ls /etc/logrotate.d/284285# Test config (dry run)286logrotate -d /etc/logrotate.d/myapp287288# Force immediate rotation289logrotate -f /etc/logrotate.conf290291# Example: /etc/logrotate.d/myapp292/var/log/myapp/*.log {293 daily294 rotate 14295 compress296 delaycompress297 missingok298 notifempty299 create 0640 www-data www-data300 postrotate301 systemctl reload myapp > /dev/null 2>&1 || true302 endscript303}304```