← Blog

~/.ssh/config: 10 dòng cấu hình tiết kiệm tôi cả triệu lần gõ lệnh

Sau 8 năm SSH vào hàng chục server cho client khác nhau, đây là setup ~/.ssh/config tôi ước gì biết từ năm thứ nhất — alias, ProxyJump, ControlMaster và mấy thứ tăng tốc khác.

Năm 2017, tôi mới ra trường, đi freelance, có 4 client. Mỗi client một VPS riêng, IP riêng, port SSH riêng (vì “security through obscurity”), key SSH riêng. Mỗi sáng mở terminal lên đời tôi trông như này:

ssh -p 2222 -i ~/.ssh/client_a_rsa deploy@45.123.45.67
ssh -p 2200 -i ~/.ssh/client_b_rsa root@srv.client-b.com
ssh -p 22   -i ~/.ssh/personal    nhatdote@my-vps.com

Mỗi lần switch client, tôi mở Notion, copy lệnh, paste vào terminal. Chậm. Đần. Và quan trọng nhất là tôi không nhớ sau 1 tuần không động vào server nào — IP gì, port mấy, key nào. Notion full những file ssh-commands.md.

Đến năm thứ 2, một anh senior nhìn tôi gõ rồi hỏi: “Sao mày không dùng ~/.ssh/config?”. Tôi nói: “Cái đó là gì?”.

Đây là bài tôi viết cho mình-năm-2017 — và cho bất kỳ ai vẫn đang copy-paste lệnh SSH từ một file Notion.

File cấu hình đó nằm ở đâu

~/.ssh/config

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

touch ~/.ssh/config
chmod 600 ~/.ssh/config

chmod 600 quan trọng. SSH client từ chối đọc file config nếu nó “world readable” — security baked in.

Bài học #1: Alias cho mỗi server

Thay vì gõ:

ssh -p 2222 -i ~/.ssh/client_a_rsa deploy@45.123.45.67

Thêm vào ~/.ssh/config:

Host client-a
    HostName 45.123.45.67
    User deploy
    Port 2222
    IdentityFile ~/.ssh/client_a_rsa

Giờ chỉ cần:

ssh client-a

Tab-complete cũng ăn — gõ ssh cli rồi Tab, terminal tự gợi ý.

scprsync cũng đọc config này:

scp file.tar.gz client-a:/tmp/
rsync -avz ./dist/ client-a:/var/www/

Không cần lặp lại port/user/key.

Bài học #2: Wildcard cho group server

Tôi có 5 server cùng một client (web1, web2, db1, db2, redis). Cùng port, cùng key, khác IP:

Host client-a-*
    User deploy
    Port 2222
    IdentityFile ~/.ssh/client_a_rsa

Host client-a-web1
    HostName 45.123.45.67
Host client-a-web2
    HostName 45.123.45.68
Host client-a-db1
    HostName 45.123.45.69

Block đầu set defaults cho cả group. SSH đọc top-down, dòng đầu match được giá trị nào thì dùng, dòng sau không ghi đè. Nên cụ thể (client-a-web1) phải đặt trước wildcard (client-a-*)… khoan, đảo ngược. Để tôi sửa lại:

Host client-a-web1
    HostName 45.123.45.67
Host client-a-web2
    HostName 45.123.45.68

Host client-a-*
    User deploy
    Port 2222
    IdentityFile ~/.ssh/client_a_rsa

Cụ thể trên, wildcard dưới. SSH merge tất cả block match — HostName từ block đầu, User/Port/IdentityFile từ wildcard.

Bài học #3: ProxyJump qua bastion host

Client B của tôi có 1 bastion host public, 5 server private không có public IP. Cách cũ:

ssh bastion
# trong bastion:
ssh internal-web1

Hai bước, copy file thì khỏi nói (scp 2 bước cực vô lý).

Cách mới — ProxyJump trong config:

Host bastion-b
    HostName bastion.client-b.com
    User admin
    IdentityFile ~/.ssh/client_b_rsa

Host internal-*
    User deploy
    IdentityFile ~/.ssh/client_b_rsa
    ProxyJump bastion-b

Host internal-web1
    HostName 10.0.1.10
Host internal-db1
    HostName 10.0.1.20

Giờ ssh internal-web1 tự nhảy qua bastion và đến đích. scp file.sql internal-db1:/tmp/ cũng tự jump.

Bài học #4: ControlMaster — tăng tốc nhiều connection

Mỗi lần SSH connect, có cú handshake TCP + SSH ~200–500ms. Khi bạn git pull rồi git push rồi scp rồi ssh đến cùng server trong vòng 1 phút, đó là 4 lần handshake.

ControlMaster reuse connection đầu cho các lần sau:

Host *
    ControlMaster auto
    ControlPath ~/.ssh/cm-%r@%h:%p
    ControlPersist 10m

Đặt vào cuối file (Host * áp dụng cho mọi host). Sau lần SSH đầu tiên, một socket được tạo ở ~/.ssh/cm-.... Lần SSH/scp/rsync tiếp theo trong 10 phút dùng lại socket cũ — không handshake. Tốc độ nhảy từ 500ms xuống ~10ms.

Cảnh báo: nếu MFA/2FA bật, ControlMaster có thể giữ session sau khi bạn nghĩ là đã logout. Tắt với ControlPersist no cho server nhạy cảm.

Bài học #5: Keep alive — dừng disconnect giữa chừng

Mỗi developer đều biết cảm giác này: SSH vào server, mở vim, đi pha cafe, quay lại thấy “Write failed: Broken pipe”. Network NAT giữa bạn và server idle-timeout sau ~5 phút.

Fix:

Host *
    ServerAliveInterval 60
    ServerAliveCountMax 3

Mỗi 60 giây, SSH client gửi packet “are you there?”. Server reply → connection coi như alive, NAT giữ slot. Sau 3 lần không reply (3 phút) mới chấp nhận là chết.

Bài học #6: AddKeysToAgent

Trên macOS, mỗi lần SSH bằng key có passphrase, gõ lại passphrase — bực. Thêm:

Host *
    AddKeysToAgent yes
    UseKeychain yes
    IdentitiesOnly yes

AddKeysToAgent yes: lần đầu nhập passphrase, key load vào ssh-agent → các lần sau khỏi nhập. UseKeychain yes (chỉ macOS): lưu passphrase vào Keychain → reboot vẫn nhớ. IdentitiesOnly yes: SSH chỉ thử đúng cái IdentityFile được khai trong block. Không có dòng này, SSH agent thử mọi key có trong agent — server có policy strict (như GitHub) sẽ ban IP sau 5 lần fail.

File config hoàn chỉnh tôi đang dùng

# Defaults cho mọi host
Host *
    AddKeysToAgent yes
    UseKeychain yes
    IdentitiesOnly yes
    ServerAliveInterval 60
    ServerAliveCountMax 3
    ControlMaster auto
    ControlPath ~/.ssh/cm-%r@%h:%p
    ControlPersist 10m

# Personal
Host my-vps
    HostName my-vps.com
    User nhatdote
    IdentityFile ~/.ssh/personal

# Client A
Host client-a-*
    User deploy
    Port 2222
    IdentityFile ~/.ssh/client_a_rsa
Host client-a-web1
    HostName 45.123.45.67
Host client-a-web2
    HostName 45.123.45.68

# Client B (qua bastion)
Host bastion-b
    HostName bastion.client-b.com
    User admin
    IdentityFile ~/.ssh/client_b_rsa
Host internal-*
    User deploy
    IdentityFile ~/.ssh/client_b_rsa
    ProxyJump bastion-b
Host internal-web1
    HostName 10.0.1.10

Khoảng 30 dòng. Thay thế 4 file Notion + 1 năm trí nhớ.

Lời cuối

~/.ssh/config là một trong những file mà công sức bỏ ra (15 phút setup) so với lợi ích nhận về (cả đời gõ lệnh) lệch một cách đáng xấu hổ. Tôi cứ tiếc là đã không dùng từ ngày đầu.

Một lưu ý cuối: file này là documentation cho future-you. Comment đầy đủ vào — 2 năm sau quay lại bạn sẽ cảm ơn mình. Tôi để mỗi block có comment “client-x — purpose — date added”.

Mỗi server đáng có một dòng tử tế trong file này. Notion folder ssh-commands thì xoá đi.