EQVPS

Cách cài MySQL trên VPS: người dùng, truy cập từ xa an toàn, ít RAM hơn và sao lưu

Cài MySQL 8.0 trên VPS Ubuntu 24.04, tạo cơ sở dữ liệu và người dùng cho ứng dụng, truy cập từ máy tính của bạn mà không mở cổng 3306, giảm bộ nhớ lúc rảnh từ 383 MB xuống 165 MB, và sao lưu mỗi đêm bằng một script báo lỗi rõ ràng khi hỏng.

Cài MySQL chỉ mất một lệnh. Phần lớn công việc đến sau: người dùng cho ứng dụng, cách vào cơ sở dữ liệu từ máy tính của bạn mà không đưa cổng 3306 ra internet, thiết lập bộ nhớ cho máy chủ nhỏ, và bản sao lưu tự chạy không cần bạn. Hướng dẫn này đề cập cả bốn trên Ubuntu 24.04. Mọi bước đều chạy trên ảnh Ubuntu 24.04 của chúng tôi, và các con số dưới đây lấy từ chính máy chủ đó.

Chạy các lệnh bằng người dùng có sudo. Hướng dẫn thiết lập Ubuntu tạo sẵn một người.

1. Cài đặt

sudo apt update
sudo apt install -y mysql-server
mysql --version

Trên máy chủ của chúng tôi, việc cài mất 51 giây và cho MySQL 8.0.46. Nó khởi động ngay, khởi động lại mỗi lần boot và chỉ lắng nghe trên chính máy chủ: 127.0.0.1:3306 cho kết nối thường và 127.0.0.1:33060 cho giao thức X mới hơn của MySQL. Lúc này từ bên ngoài chưa vào được gì.

2. Tạo cơ sở dữ liệu và người dùng

Trên Ubuntu, tài khoản root của MySQL dùng xác thực qua socket, nên sudo mysql cho bạn vào mà không cần mật khẩu. Tạo cơ sở dữ liệu và người dùng cho ứng dụng:

sudo mysql
CREATE DATABASE app CHARACTER SET utf8mb4;
CREATE USER 'app'@'localhost' IDENTIFIED BY 'a-long-random-password';
GRANT ALL PRIVILEGES ON app.* TO 'app'@'localhost';
EXIT;

Thử theo đúng cách ứng dụng sẽ kết nối:

mysql -u app -p app -e "SELECT CURRENT_USER();"

Kết quả phải là app@localhost. Sai mật khẩu sẽ nhận ERROR 1045 (28000): Access denied for user 'app'@'localhost'. Người dùng này làm được mọi thứ trong app và không làm được gì bên ngoài.

3. Kết nối từ máy tính của bạn: đường hầm SSH

Để dùng một client máy tính như DBeaver, MySQL Workbench hay dòng lệnh mysql trên máy của bạn, đừng mở cổng nào. Chuyển tiếp một cổng qua SSH:

ssh -L 3307:127.0.0.1:3306 deploy@YOUR.SERVER.IP

Với gói NAT, thêm -p và cổng SSH riêng trong bảng điều khiển. Trong lúc cửa sổ đó còn mở, trỏ client tới host 127.0.0.1, cổng 3307, người dùng app. Phía máy bạn chúng tôi dùng 3307 để không đụng với MySQL có thể đang chạy trên máy tính của bạn. Trong lần thử, máy chủ trả lời ngay qua đường hầm, còn kết nối trực tiếp tới địa chỉ của nó ở cổng 3306 thì bị từ chối.

4. Truy cập trực tiếp cho một máy chủ khác

Đôi khi ứng dụng chạy trên máy chủ thứ hai và phải kết nối trực tiếp. Khi đó cơ sở dữ liệu cần một gói có IPv4 riêng, vì gói NAT chỉ chuyển tiếp cổng SSH của bạn. Có ba thay đổi, tất cả giới hạn trong địa chỉ của máy chủ ứng dụng (ở đây là 203.0.113.20):

sudo sed -i 's/^bind-address.*/bind-address = 0.0.0.0/' /etc/mysql/mysql.conf.d/mysqld.cnf
sudo systemctl restart mysql
sudo ufw allow from 203.0.113.20 to any port 3306 proto tcp
CREATE USER 'app'@'203.0.113.20' IDENTIFIED BY 'a-long-random-password';
GRANT ALL PRIVILEGES ON app.* TO 'app'@'203.0.113.20';

Nếu ufw chưa bật, hãy cho phép SSH trước (sudo ufw allow OpenSSH) rồi chạy sudo ufw enable. Chi tiết có trong hướng dẫn ufw.

Chúng tôi kiểm tra riêng từng ổ khóa. Có quy tắc tường lửa nhưng không có người dùng MySQL tương ứng, máy chủ từ chối với error 1130: Host '…' is not allowed to connect to this MySQL server. Khi đã thêm người dùng, kết nối đi qua. Khi gỡ quy tắc tường lửa, kết nối chỉ đơn giản là hết thời gian chờ. Mọi địa chỉ khác trên internet đều gặp đúng tình trạng hết thời gian chờ đó.

5. Dùng ít RAM hơn

Lúc rảnh và chưa có dữ liệu riêng, MySQL 8.0 dùng 383 MB trên máy chủ của chúng tôi. Phần lớn là của Performance Schema, một tính năng chẩn đoán giữ sẵn bộ nhớ. Trên VPS nhỏ, hãy tắt nó và tự đặt kích thước bộ đệm chính:

sudo tee /etc/mysql/mysql.conf.d/zz-small-vps.cnf >/dev/null <<'EOF'
[mysqld]
performance_schema = OFF
innodb_buffer_pool_size = 256M
max_connections = 50
EOF
sudo systemctl restart mysql
ps -o rss= -C mysqld

Sau khi khởi động lại, nó dùng 165 MB. buffer pool lưu đệm dữ liệu và chỉ mục của bạn, và 256 MB hợp với máy chủ 2 GB còn chạy cả ứng dụng. Tăng nó nếu cơ sở dữ liệu là việc chính của máy chủ và đã lớn hơn bộ đệm. max_connections = 50 là dư dả cho một ứng dụng có connection pool. Bạn mất số liệu thống kê của performance_schema. Bật lại nếu có lúc cần soi kỹ các truy vấn chậm.

6. Sao lưu mỗi đêm

mysqldump --single-transaction lấy một bản sao nhất quán của các bảng InnoDB mà không khóa ứng dụng. Đặt nó vào một script nhỏ:

sudo tee /usr/local/bin/mysql-backup >/dev/null <<'EOF'
#!/bin/bash
set -euo pipefail
umask 077
mkdir -p /var/backups/mysql
mysqldump --single-transaction --routines --triggers app | gzip > /var/backups/mysql/app-$(date +%F).sql.gz
find /var/backups/mysql -name 'app-*.sql.gz' -mtime +14 -delete
EOF
sudo chmod 755 /usr/local/bin/mysql-backup
echo '0 3 * * * root /usr/local/bin/mysql-backup' | sudo tee /etc/cron.d/mysql-backup
sudo /usr/local/bin/mysql-backup && sudo ls -l /var/backups/mysql

Mỗi dòng đều có lý do:

  • umask 077 khiến bản dump chỉ root đọc được (-rw-------), vì nó chứa toàn bộ dữ liệu của bạn.
  • pipefail khiến cả script thất bại nếu mysqldump thất bại. Không có nó, gzip sẽ ghi ra một tệp nén rỗng tí xíu và báo thành công. Chúng tôi cố ý làm hỏng lệnh, và script kết thúc với mã 2 đúng như mong đợi.
  • find … -mtime +14 -delete giữ bản sao của hai tuần.
  • Script chạy bằng root, nên mysqldump đăng nhập qua socket và không có mật khẩu nào nằm trong tệp.

Bản sao lưu bạn chưa từng khôi phục thì chưa tính. Hãy khôi phục vào một cơ sở dữ liệu tạm và xem dữ liệu:

sudo mysql -e "CREATE DATABASE app_restore"
gunzip -c /var/backups/mysql/app-2026-10-25.sql.gz | sudo mysql app_restore
sudo mysql -e "SHOW TABLES FROM app_restore"
sudo mysql -e "DROP DATABASE app_restore"

Trong lần thử, bảng được khôi phục có đúng các hàng như bản gốc. Bản sao trên cùng ổ đĩa không bảo vệ bạn nếu mất máy chủ, nên thỉnh thoảng hãy chuyển chúng sang máy khác bằng scp hoặc restic. Managed Backups còn thêm các điểm khôi phục hằng ngày của toàn bộ máy chủ.

Chọn gói nào

Micro (2 vCPU, 2 GB, 25 GB, $5/tháng) chứa được MySQL chỉnh như trên cùng một ứng dụng web. Nếu bản thân cơ sở dữ liệu vượt quá vài gigabyte, Small với 4 GB và 35 GB giá $8/tháng cho phép bạn cấp cho buffer pool 1 GB hoặc hơn. Cả hai đều là gói NAT, và đường hầm SSH ở bước 3 dùng được trên chúng. Bước 4 cần IPv4 riêng: Micro-IP giá $10/tháng.

Thích PostgreSQL hơn? Hướng dẫn PostgreSQL đi theo cùng kế hoạch. Mọi gói đều có ở VPS, và thanh toán bằng USDT, USDC hoặc ETH trên Base, Ethereum hoặc Polygon.

FAQ

MySQL hay MariaDB?

Cả hai đều có trong kho gói của chính Ubuntu 24.04: mysql-server cài MySQL 8.0 và mariadb-server cài MariaDB 10.11. Hầu hết ứng dụng cần "MySQL" đều chạy được với cả hai. Hãy dùng cái mà tài liệu ứng dụng ghi, còn nếu không quan trọng thì chọn MySQL 8.0. Hướng dẫn này dùng MySQL.

Mật khẩu root của MySQL là gì?

Không có. Trên Ubuntu, tài khoản root của MySQL dùng xác thực qua socket: sudo mysql đưa bạn vào với quyền root, và không ai đăng nhập được bằng root qua mạng. Cứ để vậy và tạo người dùng riêng cho từng ứng dụng.

Có nên mở cổng 3306 ra internet không?

Không. Cổng cơ sở dữ liệu để mở bị quét và bị thử mật khẩu liên tục. Từ máy tính của bạn, hãy kết nối qua đường hầm SSH. Nếu một máy chủ khác phải kết nối trực tiếp, chỉ cho phép IP của máy đó, cả trong MySQL lẫn trong tường lửa.

MySQL cần bao nhiêu RAM?

Ngay sau khi cài, MySQL 8.0 dùng 383 MB trên máy chủ thử nghiệm của chúng tôi dù chưa có cơ sở dữ liệu nào. Tắt Performance Schema và đặt buffer pool vừa phải thì còn 165 MB. Gói Micro 2 GB chạy MySQL cùng một ứng dụng thoải mái; sau khi chỉnh như vậy, 1 GB cũng đủ cho một trang web nhỏ.

Có cần chạy mysql_secure_installation không?

Trên Ubuntu 24.04 phần lớn việc của nó đã được làm sẵn. Bản cài mới của chúng tôi không có người dùng ẩn danh hay cơ sở dữ liệu thử nghiệm, và root chỉ đăng nhập được qua socket cục bộ. Hãy chạy nếu bạn muốn thành phần kiểm tra độ mạnh mật khẩu mà nó cung cấp; nếu không, có thể bỏ qua.

Bình luận

Chưa có bình luận nào. Hãy là người đầu tiên.

Để lại bình luận

Bình luận được kiểm duyệt trước khi hiển thị.