Keamanan Server Linux: Tutorial Hardening SSH, Firewall UFW, CrowdSec, dan Alarm Telegram

Keamanan server Linux: hardening SSH, firewall UFW, CrowdSec, dan alarm Telegram

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.

Diagram 4 lapis keamanan server Linux: firewall UFW, SSH key-only, CrowdSec, dan penjaga login serta file
Empat lapis pertahanan. Setiap jenis serangan berhenti di lapisan yang berbeda.

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.

LapisAlatYang ditahanAnalogi
1Firewall UFWPemindai port, layanan yang tidak sengaja terbukaPagar dengan satu gerbang
2SSH key-onlyBrute force password, login rootPintu berkartu akses
3CrowdSec + bouncerBot berulang, probing web, eksploit CVESatpam yang mengusir tamu mencurigakan
4Skrip penjaga + TelegramPenyusup yang sudah lolos, backdoor PHPCCTV 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.

Output sshd -T dan ufw status verbose setelah hardening SSH di server Linux
Tangkapan asli dari salah satu VPS kami (nama host disensor): password dan root ditolak, firewall hanya membuka 3 port.

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.

Alur CrowdSec memblokir penyerang dari log, parser, skenario, keputusan, sampai bouncer firewall
Alur kerja CrowdSec dari baris log sampai paket penyerang di-drop.

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.

KondisiProfilDurasi ban
Serangan pertama atau keduadefault_ip_remediation4 jam
Kena keputusan ≥ 3 kali dalam 24 jammss_recidive7 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.

Output cscli decisions list CrowdSec berisi IP penyerang yang di-ban dan rekap skenario serangan 24 jam
Tangkapan asli dari server aplikasi kami, IP penyerang disensor. Yang berwarna merah adalah ban 7 hari dari profil residivis.

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.

Hasil uji skrip penjaga keamanan server Linux mendeteksi file sensitif berubah, login SSH asing, dan file PHP baru
Tangkapan asli uji tiga putaran (IP disensor, path uji dan username diganti): mencatat kondisi awal, mendeteksi tiga jenis kejadian, lalu diam saat aman.

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

LangkahPerintah cekHasil yang benar
Firewall aktifsudo ufw statusStatus: active, hanya port yang dipakai
Login password matisudo sshd -T | grep passwordauthpasswordauthentication no
Root tidak bisa loginsudo sshd -T | grep permitrootpermitrootlogin no
CrowdSec dan bouncer jalansudo cscli bouncers listbouncer berstatus valid
Allowlist terisisudo cscli allowlists inspect mssIP sendiri, kantor, Cloudflare
Penjaga berjalancat /var/log/guard.errkosong, 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.conf kalah oleh 50-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

Referensi: Dokumentasi CrowdSec, OpenSSH sshd_config(5), Ubuntu: UFW, Telegram Bot API sendMessage, Daftar IP Cloudflare.

Ali Akbar

Software Developer yang fokus mengembangkan aplikasi berbasis Web dan Desktop. Senang mempelajari teknologi baru terutama di bidang web design dan web development.

View all posts by Ali Akbar →

Tinggalkan Balasan

Alamat email Anda tidak akan dipublikasikan. Ruas yang wajib ditandai *