Đổi cổng SSH trên Rocky Linux với SELinux, firewalld
Đổi cổng SSH trên Rocky Linux, AlmaLinux đúng thứ tự: mở firewalld, gán SELinux port label rồi sửa sshd_config để không mất session hiện tại.
Vì sao đổi cổng SSH ở đây cần 3 bước
Để đổi cổng SSH trên Rocky Linux, AlmaLinux, CentOS Stream hoặc Fedora, chỉ sửa một chỗ là chưa đủ. Có 3 hệ thống riêng biệt cùng quyết định liệu kết nối đến cổng mới có hoạt động hay không. firewalld quyết định packet có đến được máy chủ hay không. SELinux quyết định sshd có được phép bind vào số cổng đó hay không. sshd_config quyết định daemon yêu cầu sử dụng cổng nào. Bỏ qua bước SELinux thì daemon sẽ không khởi động. Bỏ qua bước firewalld thì daemon vẫn khởi động và listen, nhưng không ai kết nối được.
Trên Ubuntu, công việc tương tự chỉ cần sửa một chỗ và restart, vì Ubuntu dùng AppArmor thay cho SELinux và không đi kèm profile giới hạn các cổng mà sshd được phép bind. Nếu ufw đang chạy, bạn thêm một rule. Toàn bộ khác biệt là như vậy. Họ RHEL cài sẵn firewalld đang chạy và SELinux ở chế độ enforcing, đồng thời cả hai đều kiểm soát số cổng.
Thực hiện theo thứ tự này để session hiện tại vẫn hoạt động trong suốt quá trình:
- Mở cổng mới trong firewalld, tạm thời vẫn để cổng 22 mở.
- Thêm SELinux label cho cổng mới bằng
semanage. - Đặt cổng trong cấu hình sshd.
- Restart
sshd, sau đó đăng nhập bằng cổng mới từ terminal thứ hai trước khi đóng terminal đầu tiên.
Tìm web console của nhà cung cấp (VNC hoặc serial) trước khi bắt đầu và kiểm tra rằng bạn có thể đăng nhập qua đó. Console này là cách để bạn truy cập lại máy chủ nếu thay đổi xảy ra lỗi. Đổi cổng là một trong những lý do phổ biến nhất khiến tenant tự khóa quyền truy cập vào máy chủ mà họ vừa thuê.
Đầu tiên, cài semanage
semanage là công cụ dùng để chỉnh sửa các thiết lập policy của SELinux. Bản cài đặt Rocky Linux hoặc AlmaLinux tối thiểu không có công cụ này. Nó nằm trong policycoreutils-python-utils.
sudo dnf install -y policycoreutils-python-utilsChạy lệnh trước khi cài package đó sẽ cho sudo: semanage: command not found. Đây là lúc nhiều người kết luận rằng SELinux chưa được cài và bỏ qua bước này. SELinux đã được cài. Chỉ thiếu công cụ quản lý. Nếu cú pháp dnf còn mới với bạn, các lệnh tương đương trong dnf và apt sẽ giúp liên hệ với những gì bạn đã biết.
Chọn một port và kiểm tra xem đã có tiến trình nào sử dụng chưa
Bạn có thể dùng bất kỳ TCP port nào còn trống trong khoảng 1024 đến 65535. Hãy thực hiện 2 kiểm tra trước khi chọn port:
sudo ss -tlnp | grep -w 2222
sudo semanage port -l | grep -w 2222Lệnh đầu tiên cho biết đã có tiến trình listening trên port đó hay chưa. Lệnh thứ hai cho biết policy của SELinux đã gán port đó cho một service type khác hay chưa. Port còn trống sẽ không trả về kết quả từ cả hai lệnh. Nếu policy đã sử dụng port đó, semanage port -a trong bước 2 sẽ fail với ValueError: Port tcp/2222 already defined. Khi đó, hãy chọn một port khác.
Trong toàn bộ hướng dẫn này, 2222 được dùng làm ví dụ. Đây cũng là port đầu tiên scanner thử sau port 22, vì vậy trên server thực tế, hãy chọn một port khó đoán hơn.
Bước 1: mở cổng trong firewalld
sudo firewall-cmd --permanent --add-port=2222/tcp
sudo firewall-cmd --reload
sudo firewall-cmd --list-ports--permanent ghi rule vào file zone trên ổ đĩa và không tác động đến firewall đang chạy. --reload nạp cấu hình trên ổ đĩa vào firewall đang chạy. Nếu bỏ qua thao tác reload, rule vẫn tồn tại nhưng không có tác dụng cho đến khi firewalld khởi động lại. Đây là một trong những nguyên nhân phổ biến nhất khiến toàn bộ quy trình này có vẻ thất bại mà không rõ lý do.
Tạm thời không thay đổi entry của service ssh. Entry này giữ cho cổng 22 mở và là phương án dự phòng trong lúc bạn kiểm tra.
Bạn cũng nên kiểm tra control panel của nhà cung cấp. Nhiều nhà cung cấp chạy network firewall phía trước VPS, bên ngoài hệ điều hành. Vì vậy, cổng đã mở trong firewalld vẫn có thể bị chặn ở upstream. Hướng dẫn cơ bản về firewalld cho VPS giải thích về zone và sự khác nhau giữa cấu hình runtime với cấu hình permanent nếu mô hình này còn mới với bạn.
Bước 2: gắn nhãn cho cổng trong SELinux
sudo semanage port -a -t ssh_port_t -p tcp 2222
sudo semanage port -l | grep ssh_port_t-a thêm một ánh xạ cổng mới. -t ssh_port_t là type mà các cổng SSH sử dụng. Lệnh thứ hai liệt kê mọi thứ hiện được ssh_port_t quản lý, để bạn xác nhận số cổng đã được thêm trước khi chỉnh sửa daemon.
Tại sao SELinux chặn cổng
SELinux (security-enhanced Linux) gán một label cho mọi object trên hệ thống, và số cổng TCP cũng là object như các object khác. SSH daemon chạy trong một domain bị giới hạn có tên là sshd_t. Policy cho phép sshd_t bind các cổng TCP được gán label ssh_port_t, và mặc định chỉ cổng 22 có label đó. Nếu yêu cầu daemon bind vào 2222, kernel sẽ kiểm tra label, thấy type generic mà policy đã gán cho số cổng đó, rồi từ chối quyền name_bind trên socket.
Vì vậy lỗi này không giống lỗi firewall. Kernel từ chối trước khi listening socket được tạo, nên sshd báo lỗi rồi thoát. Với lỗi firewall thì ngược lại: daemon vẫn chạy bình thường, còn packet bị loại bỏ trên đường đi vào.
getenforce cho biết máy đang ở mode nào. Ở Permissive, denial được ghi lại nhưng không bị áp dụng, nên thay đổi cổng có vẻ hoạt động rồi hỏng vào ngày ai đó chạy setenforce 1 hoặc máy reboot vào enforcing mode. Dù ở mode nào, hãy gán label cho cổng. Hướng dẫn cơ bản về SELinux cho server giải thích đầy đủ về mode, context và boolean. Cổng không phải object duy nhất chịu ảnh hưởng này. Cùng policy đó cũng ngăn container đọc một thư mục host được mount cho đến khi path đó được gán lại label. Vì vậy, hướng dẫn cài Docker trên Rocky Linux hoặc AlmaLinux có bước cấu hình SELinux mà các hướng dẫn Ubuntu không đề cập.
Bước 3: đặt port trong cấu hình sshd
Trên Rocky Linux 9 và 10, AlmaLinux 9 và 10, cùng các bản Fedora hiện tại, /etc/ssh/sshd_config bắt đầu bằng một dòng include, nên vị trí gọn nhất để thay đổi là một file drop-in. Các bản cập nhật package sẽ không ghi đè thay đổi của bạn.
grep -n '^Include' /etc/ssh/sshd_config
echo 'Port 2222' | sudo tee /etc/ssh/sshd_config.d/10-port.conf
sudo sshd -tNếu grep không tìm thấy dòng Include, như trên Rocky Linux 8 và các image cũ khác, hãy đặt Port 2222 trực tiếp vào /etc/ssh/sshd_config. sshd -t phân tích toàn bộ cấu hình, bao gồm cả các file drop-in, rồi báo lỗi cú pháp. Hãy sửa mọi lỗi được báo trước khi restart, vì cấu hình không phân tích được đồng nghĩa với việc daemon sẽ không khởi động lại.
Port có thể xuất hiện nhiều lần và sshd sẽ lắng nghe trên mọi port được liệt kê. Giữ Port 22 cùng với Port 2222 trong ngày đầu tiên là một biện pháp an toàn đơn giản, miễn là bạn nhớ xóa nó.
sshd của bạn có được khởi động bằng socket unit không?
Một số image khởi động SSH bằng systemd socket activation thay vì chạy dưới dạng service liên tục. Khi được cấu hình theo cách này, systemd quản lý listening socket và chuyển các kết nối cho sshd, nên dòng Port trong sshd_config hoàn toàn bị bỏ qua. Hãy kiểm tra trước khi restart bất kỳ thứ gì:
systemctl is-enabled sshd.socketKết quả enabled nghĩa là cổng được cấu hình trong socket unit, không phải trong sshd_config:
sudo systemctl edit sshd.socket[Socket]
ListenStream=
ListenStream=2222ListenStream= không có giá trị là bắt buộc. Các giá trị được cộng dồn qua những drop-in, nên nếu không dùng một phép gán rỗng để xóa danh sách trước, socket vẫn tiếp tục listen trên cả 22 và 2222. Áp dụng thay đổi bằng sudo systemctl daemon-reload rồi chạy sudo systemctl restart sshd.socket. Nếu unit bị disable hoặc không tồn tại trên server của bạn, phần này không áp dụng.
Bước 4: khởi động lại, sau đó kiểm tra từ terminal thứ hai
sudo systemctl restart sshd
systemctl status sshd
sudo ss -tlnp | grep sshdGiữ terminal này mở. Không đăng xuất khỏi terminal này. Mở terminal thứ hai trên máy của bạn và kết nối bằng cổng mới:
ssh -p 2222 youruser@203.0.113.10Chỉ đóng session đầu tiên sau khi đăng nhập từ terminal thứ hai đã thành công. Nếu không thành công, bạn vẫn còn một shell để hoàn tác mọi thay đổi. Thói quen này là khác biệt giữa một thay đổi mất 5 phút và việc phải dùng console của nhà cung cấp suốt cả buổi chiều.
Firewall drop hay SELinux denial? Cách phân biệt
Từ laptop, hai lỗi này gần như giống hệt nhau. Trên server, chúng hoàn toàn khác nhau.
- Nếu
systemctl status sshdcho biết unit đã failed, daemon chưa bao giờ bind được socket. Đây là lỗi cấu hình hoặc SELinux denial. - Nếu unit đang active và
ss -tlnpcho thấy sshd đã bind vào port mới, daemon không có lỗi. Vấn đề nằm trên đường truyền mạng: firewalld, firewall riêng của nhà cung cấp, hoặc địa chỉ và port bạn đã dùng để kết nối.
Với trường hợp SELinux, hãy đọc audit record thay vì đoán:
sudo ausearch -m AVC -ts recent
sudo journalctl -u sshd -n 50 --no-pagerMột denial name_bind trên class tcp_socket cho biết process trong comm="sshd", số port trong src= và label mà port thực sự mang trong tcontext=. Trường cuối cùng đó là câu trả lời. Giá trị khác ssh_port_t nghĩa là bước 2 chưa áp dụng cho port bạn đang dùng, thường do nhập sai số hoặc chọn sai protocol. Cài setroubleshoot-server nếu bạn muốn sealert chuyển record này thành một câu mô tả.
Thông báo mà chính sshd ghi lại khi kernel từ chối bind có dạng như sau:
error: Bind to port 2222 on 0.0.0.0 failed: Permission denied.Permission denied trên port lớn hơn 1024, nơi không cần quyền root để bind, là dấu hiệu của SELinux. Address already in use trong cùng dòng đó là một lỗi khác: process khác đang giữ port. Từ phía client, sự khác nhau giữa connection refused và connection timed out giúp phân biệt hai trường hợp trên mạng, vì refusal nghĩa là packet đã đến host nhưng không có process nào listening, còn timeout nghĩa là không có phản hồi nào.
Đóng cổng 22 và cập nhật client
Sau khi đã đăng nhập thành công vài lần qua cổng mới, hãy gỡ cổng 22:
sudo firewall-cmd --permanent --remove-service=ssh
sudo firewall-cmd --reload
sudo firewall-cmd --list-allKhông thay đổi SELinux label trên cổng 22. Label này đến từ base policy và không còn cấp quyền gì khi firewall không cho packet đi vào.
Sau đó cập nhật các client, vì mọi công cụ mặc định dùng cổng này giờ cần được cấu hình lại. Chỉ cần thêm cấu hình vào ~/.ssh/config trên máy của bạn một lần, thay vì phải nhập -p mãi:
Host myvps
HostName 203.0.113.10
Port 2222
User youruserscp, sftp, rsync và Ansible đều đọc file đó. Các job backup, lần kiểm tra monitoring và script cron hardcode cổng 22 thì không đọc file này, nên hãy rà soát và cập nhật chúng ngay khi thay đổi vẫn còn mới trong đầu.
Việc đổi cổng giúp được gì và không giúp được gì
Việc này làm giảm log rác. Các trình quét tự động liên tục quét cổng 22. Chuyển SSH khỏi cổng này sẽ loại bỏ phần lớn các dòng đó khỏi journal, giúp bạn dễ thấy các sự kiện thực tế hơn. Đây không phải là biện pháp bảo mật. Bất kỳ trình quét nào quét toàn bộ dải cổng cũng sẽ tìm thấy daemon của bạn và đọc banner phiên bản của nó. Hãy xem việc đổi cổng là công việc bảo trì. Biện pháp bảo vệ thực sự là chỉ xác thực bằng key và tắt đăng nhập bằng password. Hướng dẫn hardening SSH cho VPS trình bày từng bước việc này.
Toàn bộ nội dung trên hoạt động giống hệt trên cả hai bản rebuild chính của RHEL vì chúng được build từ cùng một source. Nếu vẫn đang chọn giữa hai bản này, hãy xem So sánh Rocky Linux và AlmaLinux. Có hai bản rebuild gần như giống hệt nhau để lựa chọn vì CentOS không còn là một bản rebuild vào năm 2020. Diễn biến này được trình bày đầy đủ trong câu chuyện từ Red Hat đến CentOS, rồi đến Rocky và AlmaLinux. Trước khi làm theo bất kỳ hướng dẫn cũ nào, hãy kiểm tra chính xác release bạn được cấp bằng cat /etc/os-release. Các hướng dẫn viết cho Rocky Linux 8 vẫn thường được tìm thấy ở vị trí cao, và các bước semanage và firewall-cmd trong đó vẫn đúng. Tuy nhiên, Rocky 8 không có dòng include sshd_config.d và không có socket unit cần kiểm tra, nên phần sshd trong các hướng dẫn đó không khớp với một máy hiện tại.
fail2ban phải được cấu hình để biết cổng mới
fail2ban không có trong các repository cơ sở. Gói này đến từ EPEL (extra packages for enterprise Linux):
sudo dnf install -y epel-release
sudo dnf install -y fail2ban fail2ban-firewalldSubpackage fail2ban-firewalld khiến fail2ban ghi các lệnh ban thông qua firewalld. Đây là lựa chọn cần dùng trên máy chủ do firewalld quản lý ruleset.
Jail sshd mặc định đặt port = ssh. Tên này được phân giải qua /etc/services thành 22. Sau khi thay đổi, jail sẽ theo dõi một cổng mà không ai tấn công. Vì vậy nó không ban ai, trong khi các lần đăng nhập thất bại liên tục tăng trên 2222. Đặt cổng bằng số trong /etc/fail2ban/jail.local:
[sshd]
enabled = true
port = 2222
backend = systemd
maxretry = 5
bantime = 3600backend = systemd đọc các lần thất bại từ journal thay vì /var/log/secure. Đây là lựa chọn an toàn hơn trên bản cài đặt tối giản, nơi rsyslog có thể không tồn tại. Khởi động bằng sudo systemctl enable --now fail2ban và kiểm tra jail bằng sudo fail2ban-client status sshd. Cú pháp jail giống với cú pháp dùng trong thiết lập fail2ban cho SSH trên Ubuntu 24.04. Chỉ nguồn package và ban action là khác nhau.
Bản vá quan trọng hơn cổng
Một server đã đổi cổng SSH nhưng chưa áp dụng bản cập nhật bảo mật nào trong 4 tháng sẽ ở tình trạng tệ hơn server vẫn dùng cổng 22 nhưng tự động cài bản vá mỗi đêm. Hãy bật unattended updates ngay trong cùng phiên làm việc, khi bạn đã đăng nhập bằng root: cấu hình cập nhật dnf tự động trên Rocky Linux và AlmaLinux hướng dẫn về timer và lựa chọn giữa việc tải bản cập nhật xuống với việc áp dụng chúng. Bản cập nhật đã cài không khởi động lại các daemon vẫn đang chạy code cũ, vì vậy kiểm tra những gì vẫn cần restart hoặc reboot là việc đáng dành 1 phút mỗi khi openssh-server hoặc một library mà nó liên kết được cập nhật trong batch.
FAQ
Tại sao sshd không khởi động sau khi tôi đổi port trên Rocky Linux?
Gần như luôn là do thiếu SELinux port label. sshd chạy trong domain sshd_t bị giới hạn, và policy chỉ cho phép nó bind vào các port được gắn nhãn ssh_port_t; mặc định chỉ có port 22. Kernel từ chối thao tác bind, nên daemon thoát thay vì listen, và journalctl -u sshd có một dòng dạng error: Bind to port 2222 on 0.0.0.0 failed: Permission denied.. Chạy sudo semanage port -a -t ssh_port_t -p tcp 2222 với số port của bạn, sau đó restart service. Nếu không tìm thấy semanage, hãy cài policycoreutils-python-utils trước.
Tôi có còn cần semanage khi SELinux ở chế độ permissive không?
Có. Ở chế độ permissive, denial được ghi lại nhưng thao tác bind vẫn được cho phép, nên thay đổi có vẻ đã hoạt động. Port label vẫn bị thiếu. Ngay khi ai đó chạy setenforce 1, hoặc máy khởi động với SELINUX=enforcing trong /etc/selinux/config, sshd sẽ không khởi động được trên port đó. Thêm label chỉ cần một command và loại bỏ một lỗi có thể chỉ xuất hiện sau nhiều tuần mà không có nguyên nhân rõ ràng.
Port đã được gắn nhãn và sshd đang chạy, vậy tại sao kết nối của tôi lại timeout?
Daemon đang chạy nghĩa là SELinux đã cho phép, nên packet đang bị drop trên đường đi vào. Kiểm tra sudo firewall-cmd --list-ports cho port của bạn và xác nhận bạn đã chạy firewall-cmd --reload sau rule --permanent, vì chỉ có permanent rule thì không bao giờ được áp dụng vào firewall đang chạy. Sau đó kiểm tra control panel của nhà cung cấp để tìm network firewall riêng đặt trước VPS. Đây là vị trí thứ hai thường chặn kết nối, và không thành phần nào bên trong operating system hiển thị được việc đó.
Tôi nên dùng port nào thay cho 22?
Bất kỳ TCP port nào còn trống trong khoảng 1024 đến 65535. Tránh dùng 2222 và 22222 trên server thật, vì scanner sẽ thử các port này ngay sau port 22. Xác nhận số port còn trống bằng sudo ss -tlnp, xác nhận SELinux policy chưa claim port đó bằng sudo semanage port -l, và bỏ qua mọi port đã được gán cho service mà bạn có thể cài sau này. Một số port cao, khó nhớ cũng không sao, vì bạn chỉ cần ghi nó vào ~/.ssh/config một lần và sẽ không phải nhập lại.