Lúc 2 giờ sáng, database từ chối kết nối
Câu chuyện bắt đầu từ một buổi deploy khá quen thuộc: mình vừa cài xong MariaDB trên Fedora Server, chạy systemctl start mariadb, thử kết nối từ app server — Connection refused. Kiểm tra firewall, kiểm tra port, kiểm tra config — tất cả đều ổn. Mất gần 40 phút mới tìm ra thủ phạm: SELinux đang chặn ngầm, không log rõ ràng, không báo lỗi cụ thể.
Fedora cập nhật package rất nhanh — mình dùng làm máy development chính suốt 2 năm và khá thích điều đó. Nhưng đổi lại, SELinux và firewalld ở đây được bật và siết chặt hơn hẳn so với Ubuntu hay CentOS. Bảo mật tốt hơn, đương nhiên. Nguồn gốc của không ít đêm trắng, cũng đương nhiên.
Bài này ghi lại đúng quy trình mình dùng để đưa MariaDB vào production trên Fedora Server — bao gồm cả phần SELinux context và firewalld mà hầu hết hướng dẫn khác đều lướt qua.
Tại sao Fedora lại khác?
Ubuntu hay CentOS 7 thì cài MariaDB xong, chạy mysql_secure_installation là gần xong việc. Fedora Server với SELinux ở chế độ enforcing (mặc định) có thêm ba lớp cần hiểu trước:
- SELinux context: Mỗi file, process, port đều mang một context riêng. MariaDB cần đúng context mới được đọc datadir, mở socket, và lắng nghe port.
- firewalld zones: Zone
publicmặc định chặn hoàn toàn port 3306 — không có ngoại lệ nào cho đến khi bạn tự mở. - systemd hardening: Unit file của MariaDB trên Fedora có các directive như
PrivateTmp,ProtectSystem— ảnh hưởng đến socket path và custom datadir.
Nắm ba điểm này trước sẽ tiết kiệm cho bạn ít nhất 1-2 tiếng debug thừa.
Thực hành: Cài đặt và cấu hình từng bước
Bước 1: Cài MariaDB
Fedora repo đã có MariaDB sẵn, version khá mới. Kiểm tra trước khi cài:
dnf info mariadb-server
Cài đặt:
sudo dnf install -y mariadb-server mariadb
Bật và khởi động luôn:
sudo systemctl enable --now mariadb
systemctl status mariadb
Status hiện active (running) là ổn. Nếu thấy failed, chạy journalctl -xe -u mariadb để đọc log — thường thì lỗi sẽ nằm ngay trong vài dòng đầu.
Bước 2: Bảo mật cài đặt ban đầu
sudo mysql_secure_installation
Script hỏi lần lượt — trả lời như sau:
- Set root password → Y, đặt password dài ít nhất 16 ký tự, có chữ hoa + ký tự đặc biệt
- Remove anonymous users → Y
- Disallow root login remotely → Y (bắt buộc với production)
- Remove test database → Y
- Reload privilege tables → Y
Kiểm tra đăng nhập sau khi xong:
sudo mysql -u root -p
# Chạy với sudo thì dùng unix socket, không cần nhập password:
sudo mysql
Bước 3: Tạo database và user cho ứng dụng
App không được kết nối bằng root — nguyên tắc này không có ngoại lệ. Tạo user riêng với quyền tối thiểu cần thiết:
-- Đăng nhập MariaDB trước
CREATE DATABASE myapp_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'myapp_user'@'localhost' IDENTIFIED BY 'StrongPassword123!';
GRANT ALL PRIVILEGES ON myapp_db.* TO 'myapp_user'@'localhost';
-- Nếu app ở server khác (thay bằng IP thực của app server):
CREATE USER 'myapp_user'@'192.168.1.50' IDENTIFIED BY 'StrongPassword123!';
GRANT SELECT, INSERT, UPDATE, DELETE ON myapp_db.* TO 'myapp_user'@'192.168.1.50';
FLUSH PRIVILEGES;
Lưu ý GRANT ALL ở dòng đầu chỉ dùng khi app và DB cùng server, low-risk. Với remote connection thì giới hạn đúng quyền cần thiết như ví dụ phía dưới.
Bước 4: Mở firewall đúng cách
Port 3306 bị chặn hoàn toàn theo mặc định — bước này không thể bỏ qua nếu app và DB khác server.
# Xem zone đang active
firewall-cmd --get-active-zones
# Khuyến nghị: chỉ cho phép kết nối từ IP cụ thể của app server
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.1.50" port port="3306" protocol="tcp" accept'
# Hoặc mở cho cả dải mạng nội bộ (ít an toàn hơn)
firewall-cmd --permanent --add-service=mysql
# Áp dụng thay đổi
firewall-cmd --reload
# Kiểm tra lại
firewall-cmd --list-all
Mình dùng rich rule thay vì --add-service=mysql trên production vì nó cho phép giới hạn theo source IP. Chỉ app server mới kết nối được vào port 3306 — các máy khác trong cùng mạng cũng không.
Bước 5: SELinux — thủ phạm thầm lặng
Phần này là lý do chính bài viết tồn tại. SELinux trên Fedora ở chế độ enforcing và nó sẽ chặn mà không giải thích gì cho bạn biết.
Kiểm tra trạng thái:
getenforce
# Kết quả mong đợi: Enforcing
Dùng datadir mặc định (/var/lib/mysql) thì không cần làm gì thêm — path này đã có context mysqld_db_t sẵn, MariaDB hoạt động bình thường.
Custom datadir mới là chỗ hay vỡ. Ví dụ bạn đổi datadir sang /data/mysql:
# Xem context hiện tại — thường sẽ là unlabeled_t hoặc default_t
ls -laZ /data/mysql
# Gán đúng SELinux context cho MariaDB
sudo semanage fcontext -a -t mysqld_db_t "/data/mysql(/.*)?"
sudo restorecon -Rv /data/mysql
# Kiểm tra lại — phải thấy mysqld_db_t
ls -laZ /data/mysql
# system_u:object_r:mysqld_db_t:s0
Nếu chưa có semanage:
sudo dnf install -y policycoreutils-python-utils
Debug khi SELinux chặn kết nối:
# Xem AVC deny gần nhất
sudo ausearch -m avc -ts recent
# Đọc dễ hơn với audit2why
sudo ausearch -m avc -ts recent | audit2why
# Công cụ mạnh nhất: sealert — tự phân tích và gợi ý fix
sudo dnf install -y setroubleshoot-server
sudo sealert -a /var/log/audit/audit.log
Đêm hôm đó audit2why cho thấy MariaDB đang cố bind vào socket path tùy chỉnh mà SELinux không cho phép. Fix trong 5 phút bằng cách restore lại socket path mặc định trong /etc/my.cnf — 40 phút trước đó là thời gian mò mẫm không biết nhìn vào đâu.
Bước 6: Cấu hình cho production
Tạo file riêng thay vì sửa thẳng vào /etc/my.cnf — dễ quản lý và không bị ghi đè khi update package:
sudo vim /etc/my.cnf.d/production.cnf
[mysqld]
# Charset mặc định — utf8mb4 hỗ trợ emoji và full Unicode
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
# Bỏ comment nếu app cùng server, giới hạn chỉ nghe localhost
# bind-address = 127.0.0.1
# Performance — innodb_buffer_pool_size đặt ~50-70% RAM khả dụng
# Server 1GB RAM thì đặt 512M, server 4GB thì 2-3G
innodb_buffer_pool_size = 256M
max_connections = 100
query_cache_type = 0 # Query cache đã deprecated từ MariaDB 10.1
# Error log
log_error = /var/log/mariadb/mariadb.log
# Slow query — giúp phát hiện query chậm sau khi deploy
slow_query_log = 1
slow_query_log_file = /var/log/mariadb/slow.log
long_query_time = 2
[client]
default-character-set = utf8mb4
Restart sau khi thay đổi config:
sudo systemctl restart mariadb
systemctl status mariadb
Bước 7: Kiểm tra kết nối từ xa
Từ app server hoặc máy khác trong mạng, test kết nối:
# Thay 192.168.1.100 bằng IP thực của Fedora Server
mysql -h 192.168.1.100 -u myapp_user -p myapp_db
# Không có mysql client thì dùng netcat test port trước
nc -zv 192.168.1.100 3306
Vẫn bị từ chối dù đã mở firewall? Kiểm tra MariaDB có đang bind đúng interface không:
sudo ss -tlnp | grep 3306
# Phải thấy 0.0.0.0:3306, không được là 127.0.0.1:3306
Nếu thấy 127.0.0.1:3306 thì MariaDB đang chỉ nghe localhost — kiểm tra lại bind-address trong config.
Checklist production
- MariaDB chạy và autostart:
systemctl is-enabled mariadbtrả vềenabled - Root login từ xa bị chặn (đã làm ở bước
mysql_secure_installation) - App dùng user riêng với quyền tối thiểu, không dùng root
- firewalld chỉ mở port 3306 cho IP app server cụ thể, dùng rich rule
- SELinux ở chế độ enforcing, datadir có đúng context
mysqld_db_t - Slow query log đã bật để phát hiện vấn đề hiệu năng sau deploy
- Có kế hoạch backup:
mysqldumpcho DB nhỏ,mariabackupcho production lớn
Tổng kết
Cài MariaDB trên Fedora không phức tạp hơn các distro khác — chỉ là có thêm vài bước mà hầu hết hướng dẫn bỏ qua vì họ viết cho Ubuntu. SELinux và firewalld không phải kẻ thù. Chúng là lý do server Fedora ít bị compromise hơn trong thực tế.
Đừng tắt SELinux chỉ vì lười debug. Mình đã làm vậy một lần trên staging server và cái giá phải trả là ngồi giải thích với team tại sao staging pass mà production fail. Không hay chút nào.
Gặp lỗi sau khi làm theo: journalctl -xe -u mariadb cho MariaDB, ausearch -m avc -ts recent cho SELinux — hai lệnh này giải quyết được 90% vấn đề phổ biến. Chúc deploy suôn sẻ.
