Cài đặt VPS lần đầu: checklist 10 bước an toàn (security hardening)

Chia sẻ bài viết

Mục lục
💡 AI-ready skill doc: bạn đang vibe code với Claude Code/Cursor/Codex/Gemini? Copy nguyên bài này paste vào AI của bạn - nó sẽ đọc như một skill document và áp dụng đúng pattern này vào project mà không cần bạn giải thích lại. Tiện, gọn, dùng được luôn.
Minh họa cài đặt VPS lần đầu checklist 10 bước an toàn

VPS mới khởi tạo là một con server "trần như nhộng": SSH cho phép password root, không firewall, không backup, không monitoring. Bài này là checklist 10 bước hardening cho VPS Ubuntu chạy production, copy-paste là xong, không lý thuyết suông. Khác với bài hướng dẫn beginner: ở đây mình focus security depth + automation cho server chạy 24/7.

Tại sao cần hardening ngay từ ngày đầu

Sự thật phũ: chỉ vài phút sau khi VPS có IP public, bot sẽ scan SSH/HTTP. Log auth.log trên 1 VPS để default sau 1 giờ có thể có vài trăm attempt login. Nếu bạn để root + password yếu, server bị compromise trước khi kịp deploy app.

Checklist dưới mất khoảng 30-45 phút lần đầu, sau đó scriptable cho mọi VPS mới. Mình áp dụng pattern này cho tất cả node của TND và khách thuê Cloud VPS tại TND.

Bước 1: SSH key + disable password

Phòng tuyến đầu tiên. Cách 90% bot không vào được dù brute-force.

# Trên máy local
ssh-keygen -t ed25519 -C "admin@laptop"
ssh-copy-id root@your-vps-ip

# Trên VPS, edit /etc/ssh/sshd_config
sudo nano /etc/ssh/sshd_config

Sửa các dòng sau:

PasswordAuthentication no
PermitRootLogin prohibit-password
PubkeyAuthentication yes
ChallengeResponseAuthentication no
UsePAM yes
X11Forwarding no
ClientAliveInterval 300
ClientAliveCountMax 2

Reload SSH:

sudo systemctl reload ssh
⚠️ Lưu ý: MỞ SẴN 1 SSH SESSION KHÁC đang chạy trước khi reload. Lỡ config sai disconnect mới, session cũ vẫn còn để fix. Đừng đóng đến khi confirm login key OK.

Bước 2: Non-root user + sudo, đổi SSH port (optional)

adduser deploy
usermod -aG sudo deploy
# Copy key sang user mới
rsync --archive --chown=deploy:deploy ~/.ssh /home/deploy

Nếu bạn paranoid hơn, đổi SSH port để giảm noise scan:

# /etc/ssh/sshd_config
Port 2222
# /etc/ufw/applications.d/openssh-server (hoặc dùng allow trực tiếp)
sudo ufw allow 2222/tcp
sudo systemctl reload ssh

Đổi port không tăng security thật sự (security through obscurity), nhưng giảm 95% bot scan random port 22, log sạch hơn.

Bước 3: ufw firewall với default deny

Mặc định Ubuntu mở mọi port outbound, inbound chỉ chặn khi có firewall rule. Bật ufw deny mọi inbound trừ whitelist:

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp        # hoặc 2222 nếu đổi port
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw limit ssh           # rate limit SSH brute force
sudo ufw enable
sudo ufw status numbered

ufw limit ssh tự block IP nào thử quá 6 connection trong 30 giây. Combo với fail2ban là 2 layer phòng thủ.

Bước 4: fail2ban với jail tùy chỉnh

sudo apt install fail2ban -y
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local

Tìm và sửa section [DEFAULT]:

bantime = 3600        # ban 1 giờ
findtime = 600        # cửa sổ 10 phút
maxretry = 3          # 3 lần fail = ban
ignoreip = 127.0.0.1/8 ::1 YOUR_HOME_IP

Enable jail SSH (auto enabled), restart:

sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd

Sau vài ngày check lại: sudo fail2ban-client status sshd sẽ thấy danh sách IP bị ban (thường vài chục đến vài trăm tùy mức độ scan).

Bước 5: Swap file (cứu OOM)

VPS gói nhỏ RAM 2-4GB rất dễ OOM khi build code hoặc spike traffic. Swap không thay RAM nhưng cứu được:

sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

# Tune swappiness (default 60, set 10 cho server)
echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

Swappiness 10 = ưu tiên giữ data trong RAM, chỉ dùng swap khi gần đầy. Server thường set vậy, desktop default 60.

Bước 6: Automatic security updates (unattended-upgrades)

VPS không cập nhật security patch là quả bom hẹn giờ. Bật auto-update chỉ cho security patch (không touch package khác để tránh break):

sudo apt install unattended-upgrades apt-listchanges -y
sudo dpkg-reconfigure -plow unattended-upgrades

Chọn Yes. Verify config:

cat /etc/apt/apt.conf.d/50unattended-upgrades | grep -A2 'Allowed-Origins'

Phải có dòng "${distro_id}:${distro_codename}-security";. Test dry-run:

sudo unattended-upgrades --dry-run --debug 2>&1 | tail -20

Nếu app nhạy cảm với restart, edit /etc/apt/apt.conf.d/50unattended-upgrades set:

Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "03:00";

Reboot 3h sáng (timezone server) sau khi kernel update.

Bước 7: Log rotation tránh đầy disk

Mặc định Ubuntu có logrotate, nhưng app/nginx/custom service thường log không rotate, phình ra GB sau vài tháng.

cat /etc/logrotate.d/nginx

Nếu chưa có, tạo:

sudo tee /etc/logrotate.d/nginx <<'EOF'
/var/log/nginx/*.log {
    daily
    missingok
    rotate 14
    compress
    delaycompress
    notifempty
    create 0640 www-data adm
    sharedscripts
    postrotate
        if [ -f /var/run/nginx.pid ]; then
            kill -USR1 `cat /var/run/nginx.pid`
        fi
    endscript
}
EOF

Cleanup journal log (systemd):

sudo journalctl --vacuum-time=14d
# Set giới hạn vĩnh viễn
sudo nano /etc/systemd/journald.conf
# SystemMaxUse=500M
sudo systemctl restart systemd-journald

Bước 8: Monitoring nhẹ (Netdata hoặc Uptime Kuma)

Không có monitoring = không biết server chết tới khi khách hàng report. Hai option nhẹ:

Netdata (metrics real-time)

bash <(curl -Ss https://my-netdata.io/kickstart.sh) --dont-wait

Mở port 19999, vào http://your-ip:19999 xem CPU, RAM, disk, network, processes real-time. Cẩn thận: nên đặt sau Nginx reverse proxy + basic auth, đừng để 19999 mở public.

Uptime Kuma (uptime monitor)

docker run -d --restart=always -p 3001:3001 \
  -v uptime-kuma:/app/data --name uptime-kuma louislam/uptime-kuma:1

Kuma ping/HTTP check endpoint mỗi N giây, ping Telegram/Discord/Email khi down. Self-hosted free, gọn.

Bước 9: Daily backup script + offsite

Snapshot provider tốt nhưng nếu account bị mất, snapshot cũng mất. Backup offsite (S3, B2, Wasabi) là bảo hiểm thật.

sudo apt install restic -y

# Init repo (B2/S3/local)
export RESTIC_REPOSITORY="b2:my-bucket:vps-backup"
export RESTIC_PASSWORD="strong-pass-here"
restic init

# Backup script /usr/local/bin/backup.sh
sudo tee /usr/local/bin/backup.sh <<'EOF'
#!/bin/bash
export RESTIC_REPOSITORY="b2:my-bucket:vps-backup"
export RESTIC_PASSWORD="strong-pass-here"
export B2_ACCOUNT_ID="..."
export B2_ACCOUNT_KEY="..."

# DB dump trước
mysqldump --all-databases | gzip > /tmp/db-$(date +%F).sql.gz

# Backup folder quan trọng
restic backup /etc /var/www /home/deploy /tmp/db-*.sql.gz

# Giữ 7 daily + 4 weekly + 6 monthly
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune

rm /tmp/db-*.sql.gz
EOF
sudo chmod +x /usr/local/bin/backup.sh

# Cron 3h sáng mỗi ngày
echo "0 3 * * * root /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1" | sudo tee /etc/cron.d/backup

Restic dedupe + encrypt mặc định, phục hồi từng file riêng được. B2 storage giá $6/TB/tháng, rẻ hơn S3.

💡 Mẹo: Test restore 1 lần ngay sau setup. restic snapshots liệt kê, restic restore latest --target /tmp/restore-test kéo về kiểm. Backup không test = backup không tồn tại.

Bước 10: Snapshot provider (cứu khi nâng cấp lỗi)

Snapshot là backup full disk ở phía provider, restore 1 click. Khác backup file: snapshot phục hồi nguyên trạng OS + data trong vài phút.

Cách dùng đúng:

  1. Tạo snapshot trước mỗi lần upgrade kernel/major package
  2. Tạo snapshot trước khi đổi config quan trọng (Nginx, MySQL my.cnf)
  3. Tạo snapshot trước khi chạy migration DB
  4. Giữ tối thiểu 1 snapshot gần nhất + 1 snapshot tuần trước

Trên TND Cloud VPS, snapshot tạo từ panel, mất 30 giây đến vài phút tùy size disk.

Bonus: 5 thứ thường quên

1. Set hostname đúng

sudo hostnamectl set-hostname web01.example.com
sudo nano /etc/hosts
# Thêm: 127.0.1.1 web01.example.com web01

Tránh log MTA, mail bounce vì hostname không hợp lệ.

2. NTP sync

sudo timedatectl set-timezone Asia/Ho_Chi_Minh
sudo apt install systemd-timesyncd -y
timedatectl status

Thời gian sai = SSL fail, log lệch, JWT token invalid.

3. Disable IPv6 nếu không dùng

Mở port qua ufw chỉ áp IPv4, IPv6 vẫn dùng default. Nếu app không listen IPv6, disable cho gọn:

echo "net.ipv6.conf.all.disable_ipv6=1" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

Hoặc tốt hơn: configure ufw cho cả IPv6 trong /etc/default/ufw set IPV6=yes.

4. Auditd cho log security

sudo apt install auditd -y
sudo systemctl enable --now auditd
sudo ausearch -ts today -i | tail

5. SSH banner để cảnh báo legal

sudo tee /etc/ssh/banner <<'EOF'
*****************************************************
* AUTHORIZED ACCESS ONLY                            *
* Unauthorized access prohibited and prosecuted.    *
*****************************************************
EOF
# Trong /etc/ssh/sshd_config thêm:
# Banner /etc/ssh/banner

Checklist tổng kết (in ra dán cạnh máy)

#BướcThời gian
1SSH key + disable password5 phút
2Non-root user + (optional) đổi SSH port3 phút
3ufw default deny + allow port2 phút
4fail2ban jail SSH3 phút
5Swap file + swappiness2 phút
6Auto security updates3 phút
7Log rotation + journal limit3 phút
8Netdata / Uptime Kuma5 phút
9Restic backup cron + test restore10 phút
10Snapshot baseline2 phút

Tổng ~40 phút. Sau lần đầu, viết shell script + Ansible playbook chạy 1 lệnh cho VPS sau là 5 phút xong.

Anti-pattern hay gặp

  • Disable ufw "tạm thời" để debug, quên bật lại tuần sau. Luôn dùng ufw allow from 1.2.3.4 to any port 9000 tạm, đừng tắt firewall.
  • Chạy app bằng root: tạo user riêng cho từng service (www-data, nodeapp, postgres). Lỡ RCE thì attacker chỉ có quyền user đó.
  • Backup nhưng không test restore: tới lúc cần phục hồi mới biết file backup corrupted.
  • Mở MySQL/Redis port public: bind 127.0.0.1 hoặc internal network, tuyệt đối không 0.0.0.0 cho DB.
  • Để default port 22 + password root: vài tiếng là bị brute force compromised, đặc biệt với password yếu.

Khi nào checklist này không đủ

10 bước trên đủ cho 95% web app, blog, API thông thường. Nếu chạy fintech, e-commerce có CC, healthcare data thì cần thêm: WAF (ModSecurity/Cloudflare), IDS (OSSEC/Wazuh), SIEM, encrypted disk (LUKS), HSM cho key. Đó là thế giới khác.

Với baseline web/API thông thường trên VPS TND với Ceph SSD + RAM ECC + DC VN, áp dụng 10 bước này là server bạn an toàn hơn 90% VPS production ngoài kia.

Bài viết liên quan

VPS mới, hardening trong 30 phút

TND Cloud VPS từ 199k/tháng: Ubuntu/Debian fresh install, snapshot 1 click, hỗ trợ apply checklist hardening. SSD Ceph Enterprise, RAM ECC, datacenter VN.

Xem bảng giá Cloud VPS
2009
15+ năm vận hành liên tục
10+
tập đoàn lớn tin dùng
100+
doanh nghiệp SMB Việt
30 ngày
đổi key lỗi miễn phí
Phần mềm bản quyền chính hãng chúng tôi cung cấp
Bản quyền chính hãng Hóa đơn VAT đầy đủ Đổi key lỗi 30 ngày Vận hành từ 2009 MST 0200994870 Hotline 0225.999.6666