ติดตั้ง VPS สำหรับ production

ทุกครั้งที่ตั้ง VPS ใหม่ ผมทำเหมือนเดิมเสมอ: patch, lock down, ติดตั้ง Docker, ตั้ง backup คู่มือนี้คือ playbook — ทุกขั้นที่ผมรันจริง ตามลำดับ

0. ก่อนเริ่ม

ก่อน SSH เข้าครั้งแรก ผมจะ:

  • ตั้ง hostname ใน cloud console
  • ใส่ SSH key (ไม่ใช้ password!)
  • จด public IPv4 และ private IP (ถ้ามี VPC)
  • สร้าง DNS A record ชี้ไป public IP

1. 60 วินาทีแรก

apt update && apt upgrade -y
apt install -y ufw fail2ban unattended-upgrades curl wget git htop vim
timedatectl set-timezone Asia/Bangkok

ตั้ง timezone ก่อน Logs ไม่มีความหมายถ้า timestamp ไม่ถูก

2. ล็อค SSH

sed -i 's/#PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config

# Restart — เปิด session ค้างไว้จนกว่าจะ verify ได้ว่า connection ใหม่ทำงาน
systemctl restart sshd

อย่าปิด session ปัจจุบัน เปิด terminal ใหม่แล้ว verify ว่า SSH เข้าได้ด้วย key ถ้ามีอะไรพัง session เดิมคือสายชีวิต

3. Firewall (UFW)

ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp comment 'SSH'
ufw allow 80/tcp comment 'HTTP'
ufw allow 443/tcp comment 'HTTPS'
ufw enable

4. fail2ban

ติดตั้งไปแล้วในขั้น 1 — defaults ก็พอ Logs อยู่ที่ /var/log/fail2ban.log

5. อัปเดตความปลอดภัยอัตโนมัติ

dpkg-reconfigure -plow unattended-upgrades

6. ติดตั้ง Docker

curl -fsSL https://get.docker.com | sh
usermod -aG docker $USER
docker run hello-world

7. ติดตั้ง Caddy (reverse proxy + auto HTTPS)

apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
apt update && apt install -y caddy

8. Backup อัตโนมัติ (restic → B2)

apt install -y restic

export B2_ACCOUNT_ID="..."
export B2_ACCOUNT_KEY="..."
restic -r b2:my-bucket:vps-snapshots init

# Cron: nightly 3am
cat > /etc/cron.d/restic-backup <<'EOF'
0 3 * * * root /usr/local/bin/restic-backup.sh
EOF

Script backup:

  • /etc
  • /home
  • /var/lib/docker/volumes (mounted read-only)
  • /srv (ที่เก็บ compose files)

9. Monitoring (Netdata)

docker run -d --name netdata \
  --pid=host \
  -v /proc:/host/proc:ro \
  -v /sys:/host/sys:ro \
  -v /var/run/docker.sock:/var/run/docker.sock:ro \
  --restart unless-stopped \
  --cap-add SYS_PTRACE \
  --security-opt apparmor=unconfined \
  netdata/netdata

Dashboard ที่ http://<ip>:19999

10. Checklist สุดท้าย

  • SSH key-only login
  • Firewall เปิด เปิดเฉพาะ 22/80/443
  • fail2ban ทำงาน
  • Unattended upgrades เปิด
  • Docker ทำงานโดยไม่ต้อง sudo
  • Caddy ติดตั้งแล้ว
  • Backup ตั้งค่าและทดสอบแล้ว (ทำ restore จริง!)
  • Monitoring live
  • DNS ชี้ไป IP แล้ว
  • จด IP/role/tags ไว้ที่ไหนสักที่ (ผมเก็บใน servers.md private repo)

ต่อไป

  • ตั้ง WireGuard สำหรับ admin access
  • ตั้ง Crowdsec สำหรับ shared blocklists
  • เชื่อม Uptime Kuma สำหรับ service health

คู่มือนี้จะโตไปเรื่อย ๆ ตามที่ผม refine PRs ยินดีรับ