Bayangkan sebuah kantor yang menyimpan uang tunai. Pemiliknya tidak cukup memasang satu kunci di pintu depan. Ada pagar dengan satu gerbang, pintu yang hanya bisa dibuka kartu akses, satpam yang mengusir orang mencurigakan, lalu CCTV dengan alarm kalau ada yang berhasil menyelinap. Jika satu lapis gagal, lapis berikutnya masih menahan. Prinsip yang sama berlaku untuk keamanan server Linux.
Pada tutorial ini kita membangun sistem keamanan server berlapis yang benar-benar kami jalankan di server produksi: firewall UFW, hardening SSH, CrowdSec yang memblokir penyerang secara otomatis, dan skrip penjaga yang mengirim alarm ke Telegram. Selain itu, setiap perintah dan kode di artikel ini sudah kami uji langsung. Namun, kami mengganti IP, domain, dan token dengan nilai contoh, lalu menyensor screenshot asli.

Kenapa Keamanan Server Linux Butuh Banyak Lapis?
Begitu sebuah VPS mendapat IP publik, bot dari seluruh dunia langsung mengetuknya. Sebagai gambaran, salah satu server aplikasi kami mencatat 5.215 percobaan login SSH gagal hanya dalam 24 jam. Selain itu, ada bot lain yang mencari file .env, halaman login WordPress, sampai celah CVE lama di Apache.
Oleh karena itu, satu pengaman saja tidak cukup. Firewall memang menutup port yang tidak dipakai, tetapi port SSH dan web harus tetap terbuka. Begitu juga dengan SSH key-only: cara ini menggagalkan tebakan password, namun bot tetap menghabiskan log dan bandwidth. Karena setiap alat menutup celah yang berbeda, kita memasang semuanya berurutan seperti tabel berikut.
| Lapis | Alat | Yang ditahan | Analogi |
|---|---|---|---|
| 1 | Firewall UFW | Pemindai port, layanan yang tidak sengaja terbuka | Pagar dengan satu gerbang |
| 2 | SSH key-only | Brute force password, login root | Pintu berkartu akses |
| 3 | CrowdSec + bouncer | Bot berulang, probing web, eksploit CVE | Satpam yang mengusir tamu mencurigakan |
| 4 | Skrip penjaga + Telegram | Penyusup yang sudah lolos, backdoor PHP | CCTV dengan alarm |
Persiapan Sebelum Mulai
Tutorial ini memakai Ubuntu 24.04 atau 26.04 dengan user biasa yang punya akses sudo. Pastikan juga Anda tahu cara membuka console darurat di panel penyedia VPS, misalnya VNC. Akses ini menjadi jalan keluar jika suatu saat SSH terkunci.
Pertama, buat key SSH di laptop lalu salin public key-nya ke server. Ganti budi dan IP contoh dengan milik Anda.
# di laptop ssh-keygen -t ed25519 -C "laptop-admin" ssh-copy-id -i ~/.ssh/id_ed25519.pub budi@203.0.113.10 # uji: harus masuk tanpa ditanya password ssh budi@203.0.113.10
Jika perintah terakhir langsung masuk tanpa menanyakan password, key Anda sudah bekerja. Jangan lanjut ke langkah hardening SSH sebelum tahap ini berhasil.
Lapis 1: Tutup Semua Port dengan Firewall UFW
Prinsipnya sederhana: tolak semua koneksi masuk, lalu buka hanya yang benar-benar dipakai. Untuk server web, biasanya cukup port 22 (SSH), 80, dan 443.
sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw allow OpenSSH sudo ufw allow "Nginx Full" sudo ufw enable sudo ufw status verbose
Urutannya penting. Perintah ufw allow OpenSSH harus jalan sebelum ufw enable, karena kalau tidak, sesi SSH Anda sendiri ikut terputus. Profil "Nginx Full" membuka port 80 dan 443 sekaligus. Akibatnya, database seperti MySQL di port 3306 otomatis tertutup dari internet walaupun layanannya tetap berjalan di dalam server.
Lapis 2: Hardening SSH dengan Key-Only
Di lapis ini kita mematikan login dengan password dan login sebagai root. Hasilnya, ribuan tebakan password dari bot menjadi sia-sia, karena server hanya menerima key yang cocok.
Pasang Pengaman Anti-Terkunci Dulu
Kesalahan konfigurasi SSH bisa membuat Anda terkunci dari server sendiri. Oleh karena itu, sebelum mengubah apa pun, kita pasang “tombol batal otomatis” yang akan mengembalikan konfigurasi dalam 5 menit.
sudo systemd-run --unit=ssh-revert --on-active=300 \ /bin/sh -c 'rm -f /etc/ssh/sshd_config.d/00-hardening.conf; systemctl reload ssh'
Perintah ini membuat timer sementara bernama ssh-revert.timer. Jika dalam 5 menit Anda tidak menghentikannya, file hardening dihapus dan SSH kembali seperti semula. Sebaliknya, setelah semua terbukti aman, Anda cukup menghentikan timer tersebut.
Matikan Login Password dan Root
Kita menulis pengaturan ke file terpisah di folder sshd_config.d agar file utama tetap bersih.
printf "PasswordAuthentication no\nKbdInteractiveAuthentication no\nPermitRootLogin no\nPubkeyAuthentication yes\n" \ | sudo tee /etc/ssh/sshd_config.d/00-hardening.conf sudo sshd -t && sudo systemctl reload ssh
Perhatikan awalan 00- pada nama file. Di banyak VPS, cloud-init membuat file 50-cloud-init.conf berisi PasswordAuthentication yes. Karena OpenSSH memakai nilai yang pertama kali dibaca, file kita harus dibaca lebih dulu. Selain itu, sshd -t memeriksa sintaks sebelum reload, sehingga salah ketik tidak mematikan SSH.
Uji, Lalu Hentikan Timer Pengaman
Jangan tutup sesi yang sedang terbuka. Sebaliknya, buka terminal baru dan uji dua hal berikut.
# terminal baru: login dengan key harus tetap bisa ssh budi@203.0.113.10 # memaksa login password harus ditolak ssh -o PubkeyAuthentication=no budi@203.0.113.10 # budi@203.0.113.10: Permission denied (publickey). # semua aman? batalkan pengaman otomatis sudo systemctl stop ssh-revert.timer
Pesan Permission denied (publickey) berarti server tidak lagi menawarkan login password. Setelah itu, periksa konfigurasi yang benar-benar aktif dengan sshd -T, karena perintah ini menampilkan hasil akhir dari semua file konfigurasi.

Contoh Kasus: Ribuan Tebakan yang Sia-Sia
Server aplikasi kami tetap menerima 5.215 percobaan login SSH dalam 24 jam setelah hardening. Bedanya, tidak ada satu pun yang berhasil. Bahkan, log 30 hari terakhir mencatat 0 login dengan password, artinya semua akses sah memakai key. Dengan kata lain, serangannya masih ada, tetapi pintunya sudah tidak bisa ditebak.
Pengecualian untuk Jaringan Tertentu
Kadang ada kebutuhan login dengan password dari satu jaringan saja, misalnya kantor. Untuk itu, tambahkan blok Match di akhir file /etc/ssh/sshd_config.
# baris paling bawah /etc/ssh/sshd_config
Match Address 198.51.100.0/24
PasswordAuthentication yes
Posisi ini penting. Semua baris setelah Match hanya berlaku untuk jaringan tersebut. Jadi, jika blok ini ditaruh di file drop-in yang dibaca di awal, pengaturan berikutnya ikut “terkurung” dan hasilnya kacau. Jalankan sudo sshd -t setiap kali selesai mengubahnya.
Lapis 3: CrowdSec, Satpam yang Membaca Log
CrowdSec membaca log server, mengenali pola serangan, lalu memasukkan IP penyerang ke daftar blokir firewall. Konsepnya mirip Fail2ban. Namun, CrowdSec juga berbagi data dengan komunitas, sehingga IP yang sudah menyerang server lain di dunia ikut diblokir di server Anda.

Instal CrowdSec dan Bouncer Firewall
Kita memakai repository resmi CrowdSec agar mendapat versi terbaru. Untuk Ubuntu 26.04 yang belum didukung repository, tetap gunakan nama rilis noble (24.04) karena paketnya berupa binary yang berdiri sendiri.
curl -fsSL https://packagecloud.io/crowdsec/crowdsec/gpgkey \ | sudo gpg --dearmor -o /etc/apt/keyrings/crowdsec_crowdsec-archive-keyring.gpg echo "deb [signed-by=/etc/apt/keyrings/crowdsec_crowdsec-archive-keyring.gpg] https://packagecloud.io/crowdsec/crowdsec/ubuntu/ noble main" \ | sudo tee /etc/apt/sources.list.d/crowdsec_crowdsec.list sudo apt update sudo apt install -y crowdsec crowdsec-firewall-bouncer-iptables
Paket crowdsec adalah “otak” yang membaca log dan mengambil keputusan. Sementara itu, crowdsec-firewall-bouncer-iptables adalah “tangan” yang benar-benar memblokir IP di firewall memakai ipset. Tanpa bouncer, CrowdSec hanya mendeteksi tanpa memblokir.
Pasang Koleksi Skenario Serangan
Koleksi berisi parser dan skenario untuk layanan tertentu. Pasang sesuai layanan yang ada di server Anda.
sudo cscli collections install crowdsecurity/nginx crowdsecurity/sshd \ crowdsecurity/linux crowdsecurity/base-http-scenarios crowdsecurity/http-cve \ crowdsecurity/whitelist-good-actors sudo systemctl reload crowdsec
Koleksi whitelist-good-actors mencegah mesin pencari dan layanan monitoring populer ikut terblokir. Jika server menjalankan WordPress atau MariaDB, tambahkan juga crowdsecurity/wordpress dan crowdsecurity/mariadb.
Whitelist: Jangan Sampai Memblokir Diri Sendiri
Ini langkah yang paling sering dilupakan. Server monitoring, IP kantor, dan Cloudflare harus dikecualikan. Jika situs Anda memakai proxy Cloudflare, semua pengunjung datang dari IP Cloudflare. Akibatnya, satu ban terhadap IP Cloudflare bisa membuat seluruh situs tidak bisa diakses.
# /etc/crowdsec/parsers/s02-enrich/mss-whitelist.yaml
name: mss/whitelist
description: "IP server sendiri, kantor, dan Cloudflare"
whitelist:
reason: "IP tepercaya"
ip:
- "127.0.0.1"
- "::1"
- "203.0.113.20" # server monitoring
cidr:
- "198.51.100.0/24" # jaringan kantor
- "173.245.48.0/20" # Cloudflare (daftar lengkap: cloudflare.com/ips)
- "103.21.244.0/22"
- "104.16.0.0/13"
Whitelist parser ini membuang event dari IP tepercaya sebelum dianalisis skenario. Namun, ada satu jebakan: whitelist parser tidak berlaku untuk blocklist komunitas. Oleh karena itu, kita perlu lapisan kedua bernama allowlist.
Tambahkan Allowlist
sudo cscli allowlists create mss -d "IP tepercaya" sudo cscli allowlists add mss 203.0.113.20 198.51.100.0/24 173.245.48.0/20 -d "monitoring, kantor, Cloudflare" sudo cscli allowlists inspect mss sudo systemctl restart crowdsec
Hati-hati dengan flag di perintah kedua. Komentar memakai -d, sedangkan -c justru berarti file konfigurasi. Kami sempat salah memakai -c, lalu cscli menolak dengan pesan “open …: no such file or directory”.
Ban 7 Hari untuk Penyerang Berulang
Secara default, CrowdSec mem-ban IP selama 4 jam. Masalahnya, bot yang sama sering kembali setelah ban habis. Untuk itu, tambahkan profil “residivis” di paling atas file /etc/crowdsec/profiles.yaml.
name: mss_recidive filters: - Alert.Remediation == true && Alert.GetScope() == "Ip" && GetDecisionsSinceCount(Alert.GetValue(), "24h") >= 3 decisions: - type: ban duration: 168h on_success: break --- # ... profil bawaan (default_ip_remediation, dst.) tetap di bawahnya
Filter ini memeriksa berapa kali IP yang sama sudah kena keputusan dalam 24 jam terakhir. Jika sudah tiga kali, ban berikutnya berlaku 168 jam (7 hari). Kemudian on_success: break membuat profil bawaan di bawahnya tidak ikut diproses, sehingga durasi 4 jam tidak menimpa ban 7 hari.
| Kondisi | Profil | Durasi ban |
|---|---|---|
| Serangan pertama atau kedua | default_ip_remediation | 4 jam |
| Kena keputusan ≥ 3 kali dalam 24 jam | mss_recidive | 7 hari |
| IP ada di whitelist atau allowlist | – | tidak pernah di-ban |
Uji Apakah Blokir Benar-Benar Jalan
Jangan percaya begitu saja bahwa CrowdSec bekerja. Sebaliknya, buat keputusan palsu untuk IP dokumentasi, lalu cek apakah IP itu masuk daftar firewall.
sudo cscli decisions add --ip 192.0.2.77 --duration 2m --reason "uji blokir" sudo ipset test crowdsec-blacklists-0 192.0.2.77 # Warning: 192.0.2.77 is in set crowdsec-blacklists-0. sudo cscli decisions delete --ip 192.0.2.77
Pesan “is in set” berarti bouncer sudah memasang IP tersebut di firewall, sehingga semua paket darinya langsung dibuang. Setelah itu, hapus keputusan uji agar daftar tetap bersih.

Contoh Kasus: Satu Penyerang, Dua Server
Dari ringkasan harian, kami melihat tiga IP dari penyedia cloud yang sama menyerang SSH di dua server berbeda secara bergantian. Masing-masing kembali setiap kali ban 4 jam berakhir. Setelah profil residivis aktif, ketiganya langsung terkena ban 7 hari. Akibatnya, log SSH di kedua server menjadi jauh lebih bersih.
Lapis 4: Penjaga Login dan File dengan Python
Tiga lapis sebelumnya bertugas mencegah. Namun, bagaimana jika penyerang tetap berhasil masuk, misalnya lewat key yang bocor atau celah plugin? Di sinilah skrip penjaga berperan sebagai CCTV. Setiap 5 menit, skrip memeriksa tiga hal: login SSH dari IP asing, perubahan file sistem penting, dan file PHP baru di folder web.
Skrip berikut hanya memakai pustaka standar Python 3. Kami membaginya menjadi beberapa bagian agar mudah dijelaskan, tetapi semua bagian disimpan berurutan dalam satu file /usr/local/sbin/guard.py.
Konfigurasi dan Bot Telegram
Pertama, buat bot lewat @BotFather di Telegram untuk mendapatkan token. Kemudian kirim satu pesan ke bot itu dan buka https://api.telegram.org/bot<TOKEN>/getUpdates untuk melihat chat_id Anda. Setelah itu, simpan semuanya di file konfigurasi.
{
"host": "web-01",
"tg_token": "123456789:AAH-contoh-token-bot",
"tg_chat": "123456789",
"webroots": ["/var/www"],
"trusted": ["198.51.100.0/24", "203.0.113.20/32"]
}
Simpan sebagai /etc/guard.json lalu jalankan sudo chmod 600 /etc/guard.json, karena file ini berisi token bot. Daftar trusted berisi jaringan yang boleh login SSH tanpa memicu alarm.
Bagian 1: Pengaturan dan Fungsi Bantu
#!/usr/bin/env python3
# guard.py - pantau login SSH, file sensitif, dan file PHP baru.
# Dijalankan cron tiap 5 menit. Hanya memakai pustaka standar Python 3.
import glob, hashlib, ipaddress, json, os, subprocess, time
import urllib.parse, urllib.request
CONF = "/etc/guard.json"
STATE = "/var/lib/guard/state.json"
SENSITIF = [
"/etc/passwd", "/etc/shadow", "/etc/group", "/etc/sudoers",
"/etc/sudoers.d/*", "/etc/ssh/sshd_config", "/etc/ssh/sshd_config.d/*",
"/root/.ssh/authorized_keys", "/home/*/.ssh/authorized_keys",
"/etc/crontab", "/etc/cron.d/*", "/etc/ld.so.preload",
"/etc/systemd/system/*.service",
]
def load(path, default):
try:
with open(path) as f:
return json.load(f)
except (OSError, ValueError):
return default
Daftar SENSITIF berisi file yang biasanya diubah penyusup untuk bertahan di server. Misalnya menambah user baru, menyisipkan public key, atau memasang cron dan service tersembunyi. Sementara itu, fungsi load() membaca file JSON dan mengembalikan nilai default jika file belum ada.
Bagian 2: Kirim Pesan dan Sidik Jari File
def kirim(conf, teks):
teks = "[%s] %s" % (conf.get("host", "server"), teks)
if not conf.get("tg_token"):
print(teks)
return
url = "https://api.telegram.org/bot%s/sendMessage" % conf["tg_token"]
data = urllib.parse.urlencode({"chat_id": conf["tg_chat"], "text": teks}).encode()
urllib.request.urlopen(url, data, timeout=10)
def hash_file(path):
h = hashlib.sha256()
with open(path, "rb") as f:
for blok in iter(lambda: f.read(65536), b""):
h.update(blok)
return h.hexdigest()
Fungsi kirim() menambahkan nama server di depan pesan, lalu mengirimnya ke Telegram. Jika token kosong, pesan hanya dicetak ke layar, sehingga skrip mudah diuji. Kemudian hash_file() menghitung SHA-256 per blok 64 KB agar file besar tidak memenuhi memori.
Bagian 3: Cek File Sensitif
def cek_sensitif(state):
sekarang = {}
for pola in SENSITIF:
for p in glob.glob(pola):
if os.path.isfile(p):
sekarang[p] = hash_file(p)
lama = state.get("hash")
pesan = []
if lama is not None:
for p in sorted(set(lama) | set(sekarang)):
if lama.get(p) != sekarang.get(p):
if p not in lama:
status = "baru"
elif p not in sekarang:
status = "dihapus"
else:
status = "berubah"
pesan.append("File sensitif %s: %s" % (status, p))
state["hash"] = sekarang
return pesan
Fungsi ini membandingkan sidik jari file sekarang dengan hasil pemeriksaan sebelumnya. Dengan demikian, file yang baru muncul, dihapus, maupun isinya berubah akan tercatat. Pada putaran pertama, belum ada data pembanding, jadi fungsi hanya menyimpan sidik jari tanpa membunyikan alarm.
Bagian 4: Cek Login SSH
def cek_ssh(conf, sejak):
tepercaya = [ipaddress.ip_network(n, strict=False) for n in conf.get("trusted", [])]
# OpenSSH 9.8+ mencatat login atas nama sshd-session, versi lama atas nama sshd
perintah = ["journalctl", "SYSLOG_IDENTIFIER=sshd", "SYSLOG_IDENTIFIER=sshd-session",
"--since", "@%d" % int(sejak), "--no-pager", "-o", "cat"]
out = subprocess.run(perintah, capture_output=True, text=True).stdout
pesan = []
for baris in out.splitlines():
if "Accepted " not in baris or " from " not in baris:
continue
# contoh: Accepted publickey for budi from 203.0.113.7 port 51234 ssh2
kata = baris.split()
try:
ip = ipaddress.ip_address(kata[kata.index("from") + 1])
except ValueError:
continue
if not any(ip in jaringan for jaringan in tepercaya):
pesan.append("Login SSH dari IP tidak dikenal: " + baris.strip())
return pesan
Fungsi ini mengambil log SSH sejak pemeriksaan terakhir, mencari baris Accepted, lalu mencocokkan IP-nya dengan daftar tepercaya. Perhatikan dua filter SYSLOG_IDENTIFIER. Jika ditulis dua kali, journalctl menganggapnya sebagai “atau”. Detail kecil ini ternyata sangat penting, seperti yang kami ceritakan di contoh kasus di bawah.
Bagian 5: Cek File PHP Baru dan Fungsi Utama
def cek_php(conf, sejak):
lewati = ("/vendor/", "/node_modules/", "/cache/")
pesan = []
for akar in conf.get("webroots", []):
for folder, _, files in os.walk(akar):
if any(x in folder + "/" for x in lewati):
continue
for nama in files:
if nama.endswith((".php", ".phtml", ".phar")):
p = os.path.join(folder, nama)
if os.stat(p).st_ctime > sejak:
pesan.append("File PHP baru/berubah: " + p)
return pesan[:20]
Webshell hampir selalu berupa file PHP baru di folder upload atau tema. Karena itu, fungsi ini mencari file PHP yang dibuat atau diubah sejak pemeriksaan terakhir. Folder vendor dan cache dilewati agar tidak berisik. Selain itu, pesan dibatasi 20 baris supaya satu kali update aplikasi tidak membanjiri Telegram.
def main():
conf = load(CONF, {})
state = load(STATE, {})
sekarang = time.time()
pertama = "last_run" not in state
sejak = state.get("last_run", sekarang)
pesan = cek_sensitif(state)
if not pertama:
pesan += cek_ssh(conf, sejak) + cek_php(conf, sejak)
state["last_run"] = sekarang
os.makedirs(os.path.dirname(STATE), exist_ok=True)
with open(STATE, "w") as f:
json.dump(state, f)
if pertama:
kirim(conf, "Penjaga aktif: %d file sensitif dipantau." % len(state["hash"]))
elif pesan:
kirim(conf, "\n".join(pesan))
if __name__ == "__main__":
main()
Fungsi main() menyimpan waktu pemeriksaan terakhir ke file state. Dengan begitu, setiap putaran hanya memeriksa kejadian baru. Jika tidak ada temuan, skrip diam sepenuhnya. Inilah yang membuat alarmnya tetap dipercaya.
Pasang dan Jadwalkan dengan Cron
sudo install -m 750 guard.py /usr/local/sbin/guard.py sudo python3 /usr/local/sbin/guard.py # [web-01] Penjaga aktif: 27 file sensitif dipantau. echo "*/5 * * * * root /usr/bin/python3 /usr/local/sbin/guard.py >/dev/null 2>>/var/log/guard.err" \ | sudo tee /etc/cron.d/guard
Putaran pertama dijalankan manual untuk mencatat kondisi awal. Setelah itu, cron menjalankannya setiap 5 menit sebagai root, karena skrip perlu membaca /etc/shadow dan journal SSH. Jika suatu saat skrip error, pesannya tersimpan di /var/log/guard.err.

Contoh Kasus: Alarm yang Diam-Diam Buta
Versi awal skrip kami hanya membaca SYSLOG_IDENTIFIER=sshd. Di server lama semuanya normal. Namun, ketika kami memasangnya di VPS baru dengan Ubuntu 26.04 (OpenSSH 10.2), ternyata log login dicatat atas nama sshd-session. Akibatnya, filter lama menemukan 0 login dalam 3 jam, padahal filter gabungan menemukan 43 login. Skrip tidak error sama sekali, tetapi diam-diam buta. Pelajarannya, uji alarm Anda dengan kejadian nyata, jangan hanya memastikan skrip berjalan tanpa error.
Kasus lain yang juga pernah terjadi adalah alarm “file PHP berubah” di server WordPress. Setelah dicek, ternyata pemilik situs baru saja memperbarui tema. Alarm seperti ini tetap berguna, karena Anda jadi tahu setiap perubahan kode, baik yang sah maupun yang mencurigakan.
Lapis Pemantauan dan Ringkasan Harian
Keamanan juga berarti tahu kapan server bermasalah. Kami memakai Uptime Kuma untuk memantau situs dan masa berlaku SSL, serta Beszel untuk CPU, RAM, dan disk. Panduan lengkapnya ada di artikel dashboard monitoring server dengan Uptime Kuma dan Beszel.
Selain itu, jangan kirim notifikasi untuk setiap IP yang di-ban. Kami pernah menerima sekitar 150 notifikasi sehari, sehingga semuanya mulai diabaikan. Sebagai gantinya, kirim satu ringkasan setiap pagi lewat cron.
#!/bin/sh
# /usr/local/sbin/ringkasan-crowdsec.sh - cron: 0 7 * * *
TOKEN="123456789:AAH-contoh-token-bot"; CHAT="123456789"
R=$(cscli alerts list --since 24h -o raw | tail -n +2 | cut -d, -f4 | grep / | sort | uniq -c | sort -rn | head -10)
curl -s -d chat_id="$CHAT" --data-urlencode "text=[web-01] Ringkasan CrowdSec 24 jam:
${R:-tidak ada serangan}" "https://api.telegram.org/bot$TOKEN/sendMessage" >/dev/null
Perintah cut -d, -f4 mengambil nama skenario dari setiap alert, kemudian uniq -c menghitung jumlahnya. Filter grep / membuang keputusan uji manual yang tidak punya nama skenario. Hasilnya satu pesan ringkas berisi jenis serangan beserta jumlahnya.
Checklist Keamanan Server Linux
| Langkah | Perintah cek | Hasil yang benar |
|---|---|---|
| Firewall aktif | sudo ufw status | Status: active, hanya port yang dipakai |
| Login password mati | sudo sshd -T | grep passwordauth | passwordauthentication no |
| Root tidak bisa login | sudo sshd -T | grep permitroot | permitrootlogin no |
| CrowdSec dan bouncer jalan | sudo cscli bouncers list | bouncer berstatus valid |
| Allowlist terisi | sudo cscli allowlists inspect mss | IP sendiri, kantor, Cloudflare |
| Penjaga berjalan | cat /var/log/guard.err | kosong, tanpa error |
Kesalahan Umum
- Mematikan password SSH sebelum key teruji. Akibatnya Anda terkunci. Selalu uji login key dan pasang timer pengaman lebih dulu.
- Nama file drop-in diawali angka besar. Misalnya
99-hardening.confkalah oleh50-cloud-init.conf, sehingga password tetap aktif. - Lupa memasukkan Cloudflare ke whitelist. Satu ban terhadap IP Cloudflare bisa mematikan semua situs yang memakai proxy-nya.
- Hanya memakai whitelist parser. Blocklist komunitas tetap bisa memblokir IP Anda jika allowlist tidak diisi.
- Salah flag di
cscli allowlists add. Komentar memakai-d, bukan-c. - Menganggap skrip tanpa error berarti berfungsi. Seperti kasus
sshd-session, alarm bisa buta tanpa pesan error apa pun. - Notifikasi untuk setiap kejadian. Terlalu banyak alarm membuat alarm penting ikut terabaikan, jadi gunakan ringkasan harian.
Kesimpulan
Keamanan server Linux yang baik bukan soal satu alat ajaib, melainkan beberapa lapis sederhana yang saling menutup celah. Firewall UFW menutup port yang tidak dipakai, kemudian SSH key-only membuat tebakan password tidak berguna. Selanjutnya, CrowdSec memblokir bot berulang secara otomatis, dan skrip penjaga memberi alarm jika ada yang tetap lolos.
Mulailah dari lapis 1 dan 2 karena paling cepat dan paling berdampak. Setelah itu, tambahkan CrowdSec dan penjaga satu per satu, lalu uji setiap lapis dengan kejadian nyata. Dengan demikian, Anda tidak hanya tahu server sudah “dipasangi keamanan”, tetapi juga yakin semuanya benar-benar bekerja.
Baca Juga
- Cara Membuat Dashboard Monitoring Server (NOC) dengan Uptime Kuma dan Beszel
- Cara Akses Proxmox dari Internet Tanpa IP Publik (WireGuard + VPS)
- Cara Mencegah Serangan XSS dan CSRF di PHP
Referensi: Dokumentasi CrowdSec, OpenSSH sshd_config(5), Ubuntu: UFW, Telegram Bot API sendMessage, Daftar IP Cloudflare.

