Claude cho sysadmin: 6 việc server dùng hằng ngày
Xem Claude hỗ trợ 6 việc server: đọc log unit lỗi, viết systemd, review nginx và Compose, cùng 4 nhóm dữ liệu bạn tuyệt đối không được dán.
Claude cho sysadmin: tư vấn trước, thực thi sau
Claude hoạt động tốt nhất như một reviewer cho sysadmin. Bạn dán một đoạn log, file cấu hình, command không nhận ra hoặc chuỗi lỗi, rồi nhận lại phần giải thích để kiểm tra trước khi thay đổi bất kỳ thứ gì trên server. Câu trả lời sai không gây thiệt hại cho đến khi bạn chạy nó. Vì vậy, giữ model ở phía tư vấn là nguyên tắc an toàn cốt lõi.
Mỗi tuần, một Linux VPS thuê (virtual private server) thường phát sinh 6 việc. Với mỗi việc dưới đây có một mẫu prompt phù hợp, command dùng để xác minh câu trả lời và failure mode bạn nên dự kiến. Không việc nào yêu cầu model truy cập server của bạn. Bạn có thể dán nội dung từ một tab trình duyệt hoặc từ một cửa sổ trên desktop của mình, vì Claude chạy native trên Linux dưới dạng desktop app và CLI.
Thứ tự này quan trọng trên production box: đọc phần giải thích, tự chạy bước kiểm tra, rồi mới quyết định. Autonomy phù hợp với một scratch VM. Với box đang phục vụ khách hàng, review luôn được ưu tiên vì model không thể nhìn thấy trạng thái mà nó đang phỏng đoán.
Những thứ tuyệt đối không được dán
Mọi nội dung trong prompt đều rời khỏi server của bạn. Có 4 nhóm phải giữ nguyên trên máy:
- Private key:
~/.ssh/id_ed25519,/etc/ssh/ssh_host_*_keyvà mọi TLS (transport layer security) key nằm dưới/etc/letsencrypt/live/. - File credential:
.env,~/.aws/credentials,/root/.docker/config.jsonvà password database trong bất kỳ file hoặc dòng log nào. - Dữ liệu account:
/etc/shadowvà/etc/gshadow. Không có câu hỏi sysadmin nào cần password hash để trả lời. - Bất kỳ dữ liệu nào thuộc về user của bạn: địa chỉ email, các dòng order, request log chứa session cookie hoặc PII (personally identifiable information).
Public key an toàn để dán. Private key thì không. Hai loại file này nhìn khá giống nhau, vì vậy hãy đọc dòng đầu tiên trước khi copy: file có dòng đầu tiên chứa BEGIN OPENSSH PRIVATE KEY tuyệt đối không được đưa vào prompt. Giữ đúng các file SSH key là việc đáng dành riêng 10 phút.
Hãy redact trước khi dán, thay vì tin rằng bạn sẽ phát hiện được một token trong 200 dòng:
sudo journalctl -u myapp -n 200 --no-pager | sed -E 's/(token|secret|password|api[_-]?key)[=:][^[:space:]]+/\1=REDACTED/gI'Docker có một bẫy riêng. docker compose config nội suy các giá trị .env vào output mà nó in ra, nên output đó là secret dù file trên disk không phải secret. Hãy dùng docker compose config -q. Lệnh này kiểm tra nhưng không in gì. Để xem chính sách rộng hơn về những gì agent được phép nhìn thấy, giữ secret ngoài AI agent trình bày phần cấu hình môi trường.
Job 1: tại sao service này bị lỗi?
Bắt đầu bằng 2 lệnh chứa câu trả lời:
systemctl status myapp.service --no-pager
sudo journalctl -u myapp.service -b -n 100 --no-pager -o short-isoDán cả 2 lệnh, kèm theo ngữ cảnh mà model không thể tự đoán: bản distribution và version, thay đổi gần đây nhất, service đã từng chạy được hay chưa, và sự cố bắt đầu từ bao lâu. Trước tiên, hãy hỏi về cơ chế gây lỗi.
Ubuntu 24.04.myapp.servicevẫn hoạt động bình thường cho đến khi tôi chỉnh sửa unit cách đây 1 giờ. Đây làsystemctl statusvà 100 dòng journal cuối cùng. Dòng nào là lỗi thực sự đầu tiên, và nó có nghĩa là gì? Chưa cần đưa ra cách khắc phục.
Cụm “chưa cần đưa ra cách khắc phục” có tác dụng thực tế trong prompt này. Log thường che khuất lỗi đầu tiên bằng các lần retry do lỗi đó gây ra, nên nếu được yêu cầu khắc phục, model sẽ giải thích dòng cuối cùng mà nó thấy. Dòng quan trọng thường nằm cách phần log nhiễu khoảng 20 dòng.
Kết quả có thể là một dòng như Main PID: 1841 (code=exited, status=203/EXEC). Exit status 203/EXEC nghĩa là kernel không thể thực thi file được chỉ định trong ExecStart: hoặc path không tồn tại, hoặc file tồn tại nhưng không có quyền thực thi. Dòng #! chỉ đến một interpreter chưa được cài cũng tạo ra status này. Có thể kiểm tra tất cả các khả năng đó bằng ls -l và head -1.
Dạng lỗi: nguyên nhân bị bịa. Nếu dán quá ít nội dung, model sẽ lấp chỗ trống bằng một nguyên nhân chung chung, chẳng hạn “cổng đã được sử dụng”. Hãy hỏi ngược lại một câu: “Dòng nào trong nội dung tôi đã cung cấp chứng minh điều đó?” Nếu không ai chỉ ra được nguyên nhân trong phần văn bản, thì đó chỉ là phỏng đoán.
Tác vụ 2: soạn systemd unit hoặc cron entry
Cung cấp cho unit các thông tin cần thiết: command chính xác, user chạy command, working directory, unit có phải chờ network hay không và cần làm gì khi process exit với mã khác 0. Sau đó kiểm tra kết quả trả về trước khi enable bất kỳ thứ gì.
sudo systemd-analyze verify /etc/systemd/system/myapp.service
sudo systemctl daemon-reload
sudo systemctl start myapp.service
systemctl status myapp.service --no-pagersystemd-analyze verify phân tích file theo đúng cách systemd phân tích, nên phát hiện được những lỗi mà mắt người dễ bỏ qua. Directive viết sai sẽ in ra /etc/systemd/system/myapp.service:7: Unknown key name 'Enviroment' in section 'Service', ignoring.. Binary bị thiếu sẽ in ra Command /usr/local/bin/myapp is not executable: No such file or directory. Cả hai đều không báo lỗi trong daemon-reload. Vì vậy unit có thể load bình thường nhưng vẫn fail ngay khi chạy.
Có 2 lỗi soạn thảo thường lặp lại. Lỗi đầu tiên là After=network.target. Nó chỉ có nghĩa network stack đã được cấu hình, không có nghĩa interface đã có address. Service bind vào một IP cụ thể sẽ fail khi boot với bind: Cannot assign requested address. Cách sửa là dùng Wants=network-online.target cùng với After=network-online.target. Lỗi thứ hai là dùng Type=simple cho chương trình tự daemonise: systemd coi process đầu tiên là service, process cha exit ngay, rồi đánh dấu unit là đã chết trong khi process thật vẫn chạy mà không được quản lý. Đây là lỗi model rất dễ đưa ra, vì model không thể biết binary có fork hay không chỉ từ command của bạn. Vì vậy, trước khi chấp nhận bản soạn thảo, hãy xem mỗi giá trị Type= cam kết điều gì với systemd.
Với schedule, hãy kiểm tra thay vì chỉ đọc:
systemd-analyze calendar 'Mon *-*-* 04:00:00'Lệnh này in ra dạng đã chuẩn hóa và thời điểm tiếp theo expression sẽ chạy. Nhờ đó, bạn có thể xác định chính xác expression có nghĩa gì. Nếu đang chọn giữa timer và crontab, systemd service và timer trên VPS trình bày các điểm đánh đổi.
Cron có một bẫy mà model sẽ không cảnh báo nếu bạn không hỏi. Cron chạy job với environment tối thiểu, nên PATH gần tương đương với /usr/bin:/bin và shell profile của bạn không bao giờ được đọc. Job chạy được khi bạn paste vào terminal nhưng fail dưới cron với /bin/sh: 1: docker: not found, vì binary đó nằm trong /usr/local/bin. Hãy dùng absolute path trong crontab. Nếu việc unit file bắt buộc ghi rõ user, environment và dependency có vẻ rườm rà so với một dòng crontab, những vấn đề systemd được xây dựng để giải quyết sẽ giải thích nguồn gốc của mức độ chi tiết đó.
Job 3: review một file nginx hoặc Compose trước khi đưa vào vận hành
Job này mang lại hiệu quả cao nhất. Dán file, nêu rõ file cần làm gì, rồi yêu cầu giải thích từng dòng về hành vi thực tế của nó.
Vhost này phải phục vụexample.comqua HTTPS và proxy/apiđến một service local trên port 8080. Hãy đọc lại cho tôi và nêu mọi điểm không khớp với mô tả đó.
Sau đó chạy tool hiểu grammar:
sudo nginx -t
docker compose config -qnginx -t in ra nginx: configuration file /etc/nginx/nginx.conf test is successful hoặc chỉ rõ tên file và dòng, như trong nginx: [emerg] unknown directive "proxy_pas" in /etc/nginx/conf.d/app.conf:12. docker compose config -q không in gì khi file parse thành công, và sẽ in lỗi thẳng như yaml: line 7: did not find expected key khi bạn thụt lề sai.
Cả hai tool đều không kiểm tra intent. Một config vượt qua nginx -t vẫn có thể proxy đến sai port hoặc listen trên 0.0.0.0 trong khi bạn muốn 127.0.0.1. Khoảng cách này là nơi model phát huy tác dụng, nhưng cũng là nơi nó dễ sai: khi được yêu cầu sửa một directive, model thường trả về toàn bộ file đã viết lại và âm thầm làm mất 2 directive của bạn. Hãy yêu cầu chỉ trả về các dòng đã thay đổi và lý do của từng thay đổi, rồi tự sửa bằng tay.
Xác nhận những gì bạn thực sự đã expose:
sudo ss -tulpnKhông có sudo, bạn chỉ thấy các listening socket mà không thấy process nào đang sở hữu chúng. Nếu output này khiến bạn bất ngờ, port là gì và Linux bind port như thế nào là phần đọc ngắn hơn.
Job 4: giải thích một command chưa quen thuộc trước khi chạy
Dán command và đặt 4 câu hỏi về nó: mỗi flag có tác dụng gì, nó ghi gì, nó xóa gì và điều gì xảy ra nếu tôi chạy nó 2 lần. Câu hỏi cuối thường phát hiện nhiều thiệt hại hơn các câu hỏi còn lại.
Xét find /var/log -name '*.gz' -mtime +7 -delete. Một câu trả lời tốt sẽ cho biết -mtime +7 đếm các khoảng thời gian trọn vẹn 24 giờ và bỏ phần lẻ, nên nó khớp với các file đã tồn tại ít nhất 8 ngày thay vì 7 ngày. Câu trả lời cũng phải cho biết find đánh giá expression từ trái sang phải, vì vậy nếu đưa -delete lên trước -name thì command sẽ xóa mọi thứ bên dưới path bắt đầu. Điểm thứ hai được cảnh báo trong man page của find và đã khiến nhiều người mất /var/log.
Hoặc xét rsync -a --delete /srv/app/ /backup/app/. Dấu slash ở cuối source có nghĩa là “nội dung của directory này”. Bỏ dấu slash đó sẽ tạo ra /backup/app/app/. Thêm --delete sẽ xóa mọi thứ ở destination không có trong source. Đây là hành vi đúng khi đồng bộ mirror và sẽ gây thảm họa nếu source path bị sai.
Xác minh bằng tool, không dùng model:
rsync -a --delete --dry-run /srv/app/ /backup/app/ | head -20
find /var/log -name '*.gz' -mtime +7Chạy find mà không có -delete sẽ cho ra danh sách thay vì gây mất dữ liệu.
Dạng lỗi: bịa flag. Model thường đáng tin cậy với các tool có 30 năm tài liệu, nhưng kém tin cậy hơn nhiều với vendor CLI (command line interface) và các subcommand mới. Trong những trường hợp đó, model có thể tạo ra một flag trông hoàn toàn hợp lý nhưng không tồn tại. --help sẽ xác nhận việc này trong 1 giây. Quoting là điểm yếu khác. Vì vậy, khi command bao bọc một expression $(...), hãy đọc cách command substitution được mở rộng trước khi command chạy thay vì tin ngay phần giải thích.
Job 5: Biến shell history thành runbook
Bạn vừa mất hai giờ để làm cho một thứ hoạt động. Kiến thức đó chỉ nằm trong scrollback và sẽ biến mất vào tháng sau.
history 200 > /tmp/session.txtĐọc file đó và xóa mọi dòng chứa password, token hoặc mã định danh khách hàng trước khi đưa file đi đâu đó. Shell history là một trong những nơi đáng tin cậy nhất để tìm secret trên Linux, vì ai cũng có lúc nhập secret trực tiếp trên command line. Đặt HISTCONTROL=ignorespace trong ~/.bashrc của bạn để command được nhập với một khoảng trắng ở đầu không bao giờ được ghi vào history.
Prompt tạo ra một runbook dùng được phải yêu cầu cả bước kiểm tra, không chỉ các bước thực hiện:
Đây là một shell session đã đưa một máy Debian 13 mới cài đến trạng thái Postgres hoạt động. Hãy viết lại thành runbook được đánh số. Mỗi bước chỉ có một command. Sau mỗi bước, đưa ra command chứng minh bước đó đã thành công và mô tả output bình thường sẽ trông như thế nào. Đánh dấu mọi bước phụ thuộc vào host cụ thể của tôi.
Failure mode: một câu chuyện gọn gàng. Session của bạn có một bước đã làm sai hai lần trước khi sửa, và đó là bước model sẽ làm cho trơn tru, vì transcript trông sạch hơn nếu bỏ qua nó. So sánh runbook với history rồi đưa phần sửa lỗi trở lại. Model cũng có thể tự bịa các command kiểm tra nghe có vẻ hợp lý, vì vậy hãy chạy mọi check mà nó viết ra trước khi lưu file. Nếu runbook bao quát lần boot đầu tiên, hãy đối chiếu nó với 10 phút đầu tiên trên một VPS mới để không ghi lại một phiên bản tệ hơn của vấn đề đã được giải quyết.
Công việc 6: biến thông báo lỗi thành cách khắc phục
Dán nguyên văn chuỗi lỗi, lệnh đã tạo ra lỗi đó và thay đổi duy nhất bạn đã thực hiện trước khi lỗi xuất hiện. Yêu cầu liệt kê các nguyên nhân theo mức độ có thể xảy ra, kèm một lệnh phân biệt cho từng nguyên nhân. Cách này buộc câu trả lời phải có thể kiểm tra được.
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use). Hãy xếp hạng các nguyên nhân có thể xảy ra và đưa cho tôi một lệnh cho mỗi nguyên nhân để xác nhận hoặc loại trừ nguyên nhân đó.
Với lỗi này, cơ chế không mơ hồ: một tiến trình khác đã chiếm cổng 80 và sudo ss -tulpn | grep ':80 ' cho biết tiến trình đó là gì. Thường đó là một nginx master thứ hai còn sót lại sau khi reload thất bại, hoặc Apache được kéo vào dưới dạng dependency rồi được package riêng tự khởi động.
Dạng lỗi: cách khắc phục chỉ che giấu nguyên nhân. chmod 777, --privileged, tắt SELinux và chạy service dưới quyền root đều có thể làm lỗi biến mất. Không chấp nhận cách khắc phục làm mở rộng quyền cho đến khi mô hình đã giải thích được vì sao quyền hạn hẹp hơn lại thất bại. Phần giải thích đó mới là câu trả lời thực sự. Workaround chỉ khiến lỗi không còn hiển thị.
Những điểm nó thường sai
- Nó không thể nhìn thấy hệ thống của bạn. Mọi câu trả lời đều dựa trên phần bạn đã dán vào, và nó sẽ không nói rằng đoạn trích quá ngắn.
- Nó thường nhầm lẫn giữa các version. Tên package và flag mặc định thay đổi giữa các distribution và bản release, còn model thì tổng hợp thông tin từ tất cả những phiên bản đó.
- Nó vẫn diễn đạt trôi chảy khi sai. Một cơ chế được bịa ra vẫn đọc giống hệt cơ chế đúng. Vì vậy, mỗi nguyên nhân ở trên đều đi kèm một command để kiểm tra.
- Nó mất mạch khi session kéo dài. Các thông tin ở đầu một cuộc hội thoại kéo dài 2 giờ dần không còn ảnh hưởng đến câu trả lời ở cuối.
Điểm cuối cùng chủ yếu là vấn đề khi làm việc, không hẳn là vấn đề của model. Quản lý context trong một session Claude Code dài là cách xử lý thực tế: session ngắn hơn, mỗi session chỉ làm một task.
Đặt agent ngay trên server
Toàn bộ phần trên đều chỉ là copy và paste, nên model không bao giờ chạm vào máy của bạn. Khi nó chạy ngay trên server, đọc file và thực thi command, mức độ rủi ro sẽ thay đổi: một command sai có thể làm hỏng một service. Hãy cấp cho nó một user không có quyền đặc biệt thay vì root, không chạy nó trên server production trong thời gian đầu để theo dõi cách hoạt động, và tạo snapshot trước. Chạy Claude Code an toàn trên VPS trình bày về sandbox và mô hình phân quyền. Điều khiển Claude Code bên trong tmux giải quyết phần còn lại, vì một phiên SSH (secure shell) bị ngắt sẽ dừng agent foreground khi nó đang thực hiện công việc. Hãy tạo account theo cách bạn vẫn tạo một service account; user ít quyền nhất trên VPS trình bày chi tiết cách này.
FAQ
Claude có thể đọc trực tiếp log của server không?
Không. Giao diện chat chỉ thấy phần văn bản bạn dán vào. Claude Code chạy trên server dưới dạng công cụ dòng lệnh có thể đọc file và chạy command với quyền của user khởi chạy nó. Đây là quyết định có mức độ tin cậy cao hơn. Với một câu hỏi hỗ trợ thông thường, dán một đoạn trích 100 dòng đã ẩn thông tin nhạy cảm nhanh và an toàn hơn việc cấp shell access cho agent.
Tôi tuyệt đối không nên dán gì từ server?
Private key, file .env và các kho credential khác, /etc/shadow, cùng mọi dữ liệu thuộc về user của bạn. Hãy ẩn token khỏi các đoạn log trước khi đưa chúng vào prompt. Một trường hợp dễ bị bỏ sót: output của docker compose config đã nội suy các giá trị .env vào đó. Vì vậy, hãy dùng docker compose config -q để kiểm tra file mà không in ra gì.
Cho Claude chạy command trên VPS production có an toàn không?
Hãy coi Claude như một admin mới chưa biết bối cảnh: cho đọc thì được, còn ghi thì cần review. Trên production, hãy yêu cầu giải thích rồi tự chạy command. Nếu bạn thực sự muốn agent thực thi, hãy cấp cho agent một account unprivileged riêng, không có sudo toàn quyền. Hãy bắt đầu trên một máy staging, nơi một lỗi chỉ khiến bạn phải rebuild thay vì gây outage.
Vì sao Claude đề xuất một flag không tồn tại?
Vì Claude dự đoán văn bản có vẻ hợp lý, mà một flag có vẻ hợp lý trông giống hệt flag thật. Lỗi này xảy ra nhiều nhất với CLI của vendor và các subcommand mới hơn, nơi tài liệu dùng để huấn luyện model còn thiếu hoặc đã thay đổi. --help và man là nguồn xác minh cuối cùng. Mọi command có thể xóa hoặc ghi đè đều nên được chạy dry run trước.
Làm thế nào để kiểm tra systemd unit trước khi enable?
Chạy sudo systemd-analyze verify /etc/systemd/system/myapp.service. Command này phân tích file bằng chính parser của systemd, báo các directive không xác định kèm số dòng, đồng thời báo lỗi nếu binary ExecStart bị thiếu hoặc không có quyền execute. Sau đó chạy daemon-reload, start và đọc systemctl status trước khi enable unit, vì một unit load thành công vẫn có thể fail ngay lần chạy đầu tiên.