Tips Jitu Anti DDoS Projek Laravel 12 Untuk Freelancer

Kamu baru deliver project Laravel untuk client. E-commerce kecil, fitur lengkap, design bagus. Client happy, payment masuk, case closed. Dua minggu kemudian, WhatsApp berbunyi jam 11 malam.

"Mas, website kok tidak bisa diakses? Dari tadi customer complain."

Kamu cek. Memang down. Server tidak respond. Panik mulai muncul.

Setelah investigasi, ternyata website kena serangan DDoS. Ribuan requests membanjiri server dalam waktu singkat. Server kewalahan, website collapse.

Client tidak paham teknis. Yang dia tahu: "Website saya dihack. Padahal baru 2 minggu."

Meskipun technically bukan "hack" dalam artian data dicuri, reputasi kamu sebagai freelancer sudah tercoreng. Client questioning kemampuan kamu. Project selanjutnya? Mungkin dia cari freelancer lain.

Ini skenario yang tidak mau dialami siapapun.


Apa Itu DDoS Attack?

DDoS adalah singkatan dari Distributed Denial of Service.

Cara paling sederhana untuk memahaminya: bayangkan kamu punya toko kecil yang bisa melayani 10 customer sekaligus. Tiba-tiba, 1000 orang masuk bersamaan. Bukan untuk beli, tapi hanya untuk memenuhi toko. Customer asli yang mau beli tidak bisa masuk. Toko lumpuh.

Itulah DDoS dalam dunia website.

Attacker menggunakan ribuan bahkan jutaan komputer yang sudah terinfeksi malware (disebut botnet) untuk mengirim requests ke target website secara bersamaan. Server tidak bisa membedakan mana request asli dari pengunjung, mana request palsu dari botnet. Akhirnya server overwhelmed dan tidak bisa melayani siapapun.

Yang perlu dipahami: DDoS bukan tentang mencuri data. Ini tentang membuat website tidak bisa diakses. Tujuannya bisa macam-macam. Ada yang iseng, ada yang kompetitor jahat, ada yang minta tebusan.

Faktanya, serangan DDoS semakin mudah dan murah untuk dilakukan. Ada "DDoS-for-hire" services yang bisa disewa dengan harga murah. Target tidak harus perusahaan besar. Small-medium business justru sering jadi korban karena pertahanannya lemah.


Jenis-Jenis DDoS Attack

Tidak semua DDoS attack sama. Secara umum, ada dua kategori besar.

Layer 3/4 Attack (Network Layer)

Serangan ini menyerang infrastruktur network. Contohnya UDP floods, SYN floods, atau ICMP floods. Target utamanya adalah membanjiri bandwidth atau menghabiskan resource network equipment.

Karakteristik serangan ini: volume besar, relatif "bodoh" (mudah dideteksi karena pattern jelas), dan biasanya di-handle di level hosting atau CDN. Sebagai developer Laravel, kamu tidak banyak bisa berbuat di level ini. Yang bisa protect adalah infrastructure provider.

Layer 7 Attack (Application Layer)

Ini yang lebih tricky. Serangan Layer 7 menyerang aplikasi langsung, biasanya lewat HTTP requests yang terlihat legitimate.

Contoh: attacker mengirim ribuan requests ke endpoint yang resource-intensive, seperti search dengan query kompleks atau page yang butuh banyak database queries. Dari luar, requests ini terlihat seperti pengunjung biasa. Tapi volumenya tidak normal.

Serangan Layer 7 lebih sulit dideteksi karena mirip traffic normal. Kabar baiknya, protection untuk serangan ini bisa di-implement di level aplikasi. Dan ini dimana Laravel developer bisa berperan.


Kenapa Ini Relevan untuk Freelancer Laravel?

"Tapi client saya cuma UMKM kecil. Siapa yang mau nyerang?"

Justru itu masalahnya.

Small-medium business sering menjadi target empuk karena asumsi defense-nya lemah. Dan asumsi itu seringkali benar. Banyak freelancer yang fokus ke fitur dan design, tapi mengabaikan security dasar.

Beberapa alasan kenapa kamu sebagai freelancer perlu peduli:

Client expect "aman" meskipun tidak explicitly bilang. Ketika client hire kamu untuk build website, ada unspoken expectation bahwa website itu akan works dan aman. Mereka tidak akan bilang "tolong tambahin anti DDoS ya." Tapi ketika website down karena serangan, mereka akan tanya "kenapa bisa kena?"

Reputasi adalah segalanya untuk freelancer. Satu incident bisa merusak relationship dengan client. Dan di dunia freelance, word of mouth sangat powerful. Client yang kecewa bisa cerita ke network mereka. Sebaliknya, client yang puas karena website-nya reliable akan recommend kamu ke orang lain.

Project Laravel biasanya web app dengan traffic. Berbeda dengan landing page statis, project Laravel biasanya aplikasi yang digunakan banyak orang. E-commerce, SaaS, portal member, sistem internal. Semua ini punya traffic dan user yang bergantung pada availability.

Recovery cost lebih mahal dari prevention. Ketika website sudah down, damage sudah terjadi. Revenue hilang, customer frustration, dan waktu kamu habis untuk firefighting. Prevention jauh lebih murah dibanding recovery.


Dampak DDoS untuk Client

Mari bicara konkret tentang apa yang terjadi ketika website client kena DDoS.

Revenue loss langsung. Untuk e-commerce, website down berarti tidak ada transaksi. Setiap jam downtime adalah rupiah yang hilang. Kalau serangan terjadi saat peak season atau promo besar, kerugiannya bisa signifikan.

Reputasi bisnis rusak. Customer yang tidak bisa akses website akan kecewa. Mereka mungkin pindah ke kompetitor. Trust yang sudah dibangun lama bisa hancur dalam hitungan jam.

Customer data concern. Meskipun DDoS tidak langsung mencuri data, client dan customer-nya tidak tahu itu. Mereka hanya tahu "website dihack." Ini bisa memunculkan kekhawatiran tentang keamanan data mereka.

Recovery time dan cost. Setelah serangan reda, masih ada pekerjaan. Investigasi apa yang terjadi, cleanup, strengthen defense, dan yang paling makan waktu: menjelaskan ke client dan customer.


Mindset: Defense in Depth

Sebelum masuk ke technical implementation, ada satu mindset yang perlu dipahami: tidak ada single solution yang 100% protect dari DDoS.

Approach yang benar adalah "defense in depth" atau pertahanan berlapis.

Bayangkan seperti keamanan rumah. Kamu tidak hanya mengandalkan kunci pintu. Ada pagar, ada CCTV, ada alarm, mungkin ada satpam komplek. Setiap layer menambah tingkat kesulitan bagi pencuri.

Untuk DDoS protection, kita bicara tentang tiga layer utama:

Layer 1: CDN/DNS Level Ini adalah garis pertahanan pertama. Traffic disaring sebelum sampai ke server kamu. Cloudflare adalah contoh paling populer. Mereka punya network capacity besar yang bisa absorb serangan volumetric.

Layer 2: Server Level Kalau ada serangan yang lolos dari layer pertama, server harus punya mekanisme sendiri untuk handle. Nginx dengan rate limiting adalah contohnya. Ini membatasi berapa banyak requests yang bisa di-process dari satu IP dalam waktu tertentu.

Layer 3: Application Level Di level Laravel, kamu bisa implement throttling dan validation. Ini melindungi dari serangan yang lebih targeted ke endpoint spesifik.

Dengan kombinasi ketiga layer ini, website client kamu punya pertahanan yang solid. Kalau satu layer breach, layer lain masih bisa menahan.


Kabar Baiknya

Sebelum kamu overthink dan merasa ini terlalu kompleks, ada kabar baik.

Protection DDoS dasar bisa di-setup dengan budget minimal, bahkan gratis. Cloudflare free tier sudah sangat powerful untuk kebanyakan use case. Laravel punya built-in tools untuk rate limiting. Nginx configuration-nya straightforward.

Kamu tidak perlu jadi security expert untuk implement basics ini. Yang perlu adalah memahami konsepnya dan tahu dimana harus configure apa.

Di bagian selanjutnya, kita akan mulai dari Layer 1: setup Cloudflare untuk project Laravel. Step by step, dengan config yang bisa langsung di-copy.

Protection yang kamu implement hari ini akan menghindarkan banyak sakit kepala di kemudian hari.


Lanjut ke Bagian 2: Setup Cloudflare untuk Laravel (Free Tier)

Bagian 2: Layer 1 - Setup Cloudflare untuk Laravel (Free Tier)

Cloudflare adalah garis pertahanan pertama dan paling penting untuk project Laravel kamu.

Kenapa Cloudflare? Karena mereka punya network capacity 449 Tbps yang tersebar di 330+ data centers di seluruh dunia. Ketika ada serangan DDoS, traffic disaring di edge network mereka sebelum sampai ke server kamu. Serangan yang bisa melumpuhkan server kecil adalah "Tuesday morning" buat Cloudflare.

Yang lebih menarik: free tier mereka sudah include DDoS protection tanpa limit. Tidak ada bandwidth cap untuk mitigasi. Serangan 10 Gbps atau 100 Gbps, Cloudflare handle tanpa extra charge.

Mari setup step by step.


Step 1: Daftar Akun Cloudflare

Buka cloudflare.com dan klik Sign Up. Gunakan email yang proper, bukan email temporary. Akun ini akan manage security untuk project client kamu.

Setelah verify email, kamu akan masuk ke dashboard.

Step 2: Add Site

Klik "Add a Site" dan masukkan domain client. Misalnya: tokoclient.com

Cloudflare akan scan existing DNS records dari domain tersebut. Proses ini biasanya memakan waktu beberapa detik sampai satu menit.

Step 3: Pilih Plan

Untuk kebanyakan freelance project, pilih Free plan. Jangan minder, free plan Cloudflare sudah sangat capable:

  • DDoS protection unlimited
  • Global CDN
  • SSL certificate gratis
  • Basic firewall rules (5 rules)
  • Analytics

Klik Continue dengan Free plan.

Step 4: Review DNS Records

Cloudflare akan menampilkan DNS records yang terdeteksi. Perhatikan beberapa hal:

A Record yang mengarah ke server harus "Proxied" (icon awan orange). Ini yang membuat traffic melewati Cloudflare.

MX Record untuk email harus "DNS only" (icon awan abu-abu). Email tidak boleh melewati proxy Cloudflare.

CNAME untuk www atau subdomain lain yang perlu protection juga harus "Proxied."

Pastikan semua record sudah benar sebelum lanjut.

Step 5: Update Nameservers

Cloudflare akan memberikan dua nameserver, misalnya:

Login ke domain registrar client (tempat domain dibeli, seperti Niagahoster, Namecheap, GoDaddy) dan ganti nameserver ke yang diberikan Cloudflare.

Propagation biasanya memakan waktu beberapa jam sampai 24 jam. Cloudflare akan notify via email ketika sudah active.


Konfigurasi SSL/TLS

Setelah domain active di Cloudflare, langkah pertama adalah setup SSL dengan benar.

Masuk ke SSL/TLS di sidebar.

Encryption Mode

Pilih "Full (strict)" jika server sudah punya SSL certificate (dari Let's Encrypt misalnya). Ini mode paling secure karena Cloudflare akan verify certificate di origin server.

Pilih "Full" jika server punya self-signed certificate.

Pilih "Flexible" hanya jika server tidak support HTTPS sama sekali. Tapi ini tidak recommended untuk production.

Edge Certificates

Di tab Edge Certificates, enable:

  • Always Use HTTPS: ON
  • Automatic HTTPS Rewrites: ON
  • Minimum TLS Version: TLS 1.2

Setting ini memastikan semua traffic terenkripsi.


Konfigurasi DDoS Protection

Kabar baiknya, DDoS protection di Cloudflare sudah ON by default. Tapi ada beberapa setting yang bisa di-optimize.

Masuk ke Security → DDoS.

HTTP DDoS attack protection

Sensitivity level default adalah Medium. Untuk kebanyakan website, ini sudah cukup. Kalau website client pernah mengalami serangan atau ada concern khusus, bisa dinaikkan ke High.

Action default adalah Block. Biarkan seperti ini.

Jangan disable protection. Beberapa orang disable karena takut false positive. Approach yang lebih baik adalah monitor dulu, dan kalau ada legitimate traffic yang ke-block, buat exception rule.


Firewall Rules Dasar

Cloudflare free tier memberikan 5 custom firewall rules. Gunakan dengan bijak.

Masuk ke Security → WAF → Custom rules.

Rule 1: Challenge Suspicious Requests ke Login/Admin

Endpoint login dan admin adalah target utama brute force dan targeted attacks.

Expression:
(http.request.uri.path contains "/login") or
(http.request.uri.path contains "/admin") or
(http.request.uri.path contains "/register")

Action: Managed Challenge

Dengan rule ini, setiap akses ke halaman sensitif akan melewati challenge CAPTCHA. Bot otomatis akan gagal, tapi human user bisa lewat dengan mudah.

Rule 2: Block Known Bad Bots

Expression:
(cf.client.bot) and not (cf.verified_bot_category in {"Search Engine Crawler" "Search Engine Optimization"})

Action: Block

Rule ini memblokir bot yang terdeteksi malicious, tapi tetap mengizinkan bot legitimate seperti Googlebot dan Bingbot.

Rule 3: Rate Limit High-Traffic Endpoints

Untuk API atau endpoint yang sering di-hit:

Expression:
(http.request.uri.path contains "/api/")

Action: Managed Challenge (dengan Rate Limiting)

Catatan: Rate limiting advanced adalah fitur berbayar di Cloudflare. Tapi dengan Managed Challenge, bot yang mencoba flood API akan tertahan.

Rule 4: Geo-blocking (Optional)

Kalau client punya target market spesifik, misalnya hanya Indonesia:

Expression:
(ip.geoip.country ne "ID")

Action: Managed Challenge

Visitor dari luar Indonesia akan dapat challenge. Ini mengurangi attack surface dari negara yang bukan target market.

Rule 5: Protect Sensitive Files

Expression:
(http.request.uri.path contains ".env") or
(http.request.uri.path contains ".git") or
(http.request.uri.path contains "wp-config")

Action: Block

Meskipun seharusnya sudah dihandle di server, tidak ada salahnya double protection.


Under Attack Mode

Ini fitur emergency yang sangat powerful.

Ketika website sedang under active attack dan kamu perlu immediate protection, enable Under Attack Mode.

Lokasi: Overview → Quick Actions → Under Attack Mode → ON

Apa yang terjadi:

  • Semua visitor akan melihat interstitial page selama 5 detik
  • JavaScript challenge dijalankan untuk verify bahwa visitor adalah human
  • Bot dan automated tools akan gagal melewati challenge
  • Legitimate users tetap bisa akses, hanya delay 5 detik

Gunakan fitur ini sementara, matikan setelah attack reda. Interstitial page bisa mengganggu user experience untuk traffic normal.


Konfigurasi Laravel untuk Cloudflare

Setelah Cloudflare active, ada beberapa adjustment yang perlu dilakukan di Laravel.

Trust Cloudflare Proxies

Ketika request melewati Cloudflare, IP address yang dilihat Laravel adalah IP Cloudflare, bukan IP visitor asli. Ini bisa menyebabkan masalah untuk rate limiting dan logging.

Laravel perlu dikonfigurasi untuk trust Cloudflare sebagai proxy.

Buka file app/Http/Middleware/TrustProxies.php:

<?php

namespace App\\Http\\Middleware;

use Illuminate\\Http\\Middleware\\TrustProxies as Middleware;
use Illuminate\\Http\\Request;

class TrustProxies extends Middleware
{
    /**
     * The trusted proxies for this application.
     *
     * @var array<int, string>|string|null
     */
    protected $proxies = '*';

    /**
     * The headers that should be used to detect proxies.
     *
     * @var int
     */
    protected $headers =
        Request::HEADER_X_FORWARDED_FOR |
        Request::HEADER_X_FORWARDED_HOST |
        Request::HEADER_X_FORWARDED_PORT |
        Request::HEADER_X_FORWARDED_PROTO |
        Request::HEADER_X_FORWARDED_AWS_ELB;
}

Setting $proxies = '*' berarti trust semua proxies. Untuk production yang lebih strict, kamu bisa list IP ranges Cloudflare secara explicit. Tapi untuk kebanyakan case, wildcard sudah cukup.

Verify Real IP

Setelah trust proxy dikonfigurasi, $request->ip() akan return IP visitor yang sebenarnya, bukan IP Cloudflare.

Kamu bisa verify dengan membuat simple route:

Route::get('/debug-ip', function (Request $request) {
    return response()->json([
        'ip' => $request->ip(),
        'cf_ip' => $request->header('CF-Connecting-IP'),
    ]);
});

Jangan lupa hapus route ini setelah testing.


Protect Origin Server

Satu hal penting yang sering terlewat: pastikan origin server tidak bisa diakses langsung.

Attacker yang tahu IP asli server kamu bisa bypass Cloudflare dan menyerang langsung. Cara mencegahnya:

Di Server Firewall (iptables/ufw)

Hanya allow traffic dari Cloudflare IPs:

# Contoh dengan ufw
# Allow Cloudflare IPv4
ufw allow from 173.245.48.0/20 to any port 80,443 proto tcp
ufw allow from 103.21.244.0/22 to any port 80,443 proto tcp
# ... (tambahkan semua Cloudflare IP ranges)

# Deny semua traffic lain ke port 80,443
ufw deny 80
ufw deny 443

List lengkap Cloudflare IPs bisa dilihat di: cloudflare.com/ips

Di Nginx

# Allow Cloudflare IPs only
allow 173.245.48.0/20;
allow 103.21.244.0/22;
allow 103.22.200.0/22;
allow 103.31.4.0/22;
allow 141.101.64.0/18;
allow 108.162.192.0/18;
allow 190.93.240.0/20;
allow 188.114.96.0/20;
allow 197.234.240.0/22;
allow 198.41.128.0/17;
allow 162.158.0.0/15;
allow 104.16.0.0/13;
allow 104.24.0.0/14;
allow 172.64.0.0/13;
allow 131.0.72.0/22;
deny all;

Dengan konfigurasi ini, direct access ke IP server akan ditolak.


Cloudflare Plans: Kapan Perlu Upgrade?

FeatureFreePro ($20/mo)Business ($200/mo)
DDoS ProtectionUnlimitedUnlimitedUnlimited
WAF Managed RulesBasicFull OWASPFull + Custom
Custom Firewall Rules520100
Rate LimitingBasicAdvancedAdvanced
Bot ManagementBasicAdvancedAdvanced
Uptime SLANoneNone100%

Kapan Free cukup:

  • Website dengan traffic moderate
  • Client dengan budget terbatas
  • Startup atau UMKM

Kapan recommend Pro:

  • E-commerce dengan transaksi harian
  • Website yang pernah kena attack
  • Client yang prioritize security

Kapan recommend Business:

  • Enterprise client
  • Website mission-critical
  • Butuh SLA dan dedicated support

Untuk kebanyakan freelance project, Free plan sudah sangat memadai. Upgrade ke Pro ketika client punya budget dan concern security yang lebih tinggi.


Dengan Cloudflare terkonfigurasi, kamu sudah punya layer pertama yang solid. Serangan volumetric akan di-absorb sebelum sampai ke server.

Tapi defense in depth berarti kita tidak berhenti di sini. Di bagian selanjutnya, kita akan setup Layer 2: Nginx rate limiting sebagai pertahanan kedua.


Lanjut ke Bagian 3: Nginx Rate Limiting Configuration

Bagian 3: Layer 2 - Nginx Rate Limiting Configuration

Cloudflare adalah pertahanan pertama yang powerful. Tapi bukan satu-satunya.

Attacker yang determinan bisa menemukan IP asli server dan bypass Cloudflare. Atau ada serangan yang lolos dari filter Cloudflare karena terlihat terlalu mirip traffic normal. Di sinilah Nginx berperan sebagai layer kedua.

Nginx sangat efficient dalam handling high traffic. Dengan rate limiting yang proper, Nginx bisa menolak excessive requests sebelum sampai ke PHP dan Laravel. Ini menghemat resource server dan menjaga aplikasi tetap responsive untuk legitimate users.


Konsep: Leaky Bucket Algorithm

Sebelum masuk ke konfigurasi, penting untuk memahami bagaimana rate limiting di Nginx bekerja.

Nginx menggunakan "leaky bucket algorithm." Bayangkan seperti ini:

Ada ember dengan lubang di bagian bawah. Air (requests) dituang dari atas. Air keluar lewat lubang dengan rate yang konstan. Kalau air yang masuk lebih cepat dari yang keluar, ember akan penuh. Ketika penuh, air yang masuk selanjutnya akan overflow dan ditolak.

Dalam context Nginx:

  • Ember adalah queue untuk requests
  • Rate keluarnya air adalah rate limit yang kita set
  • Overflow berarti request ditolak dengan 429 Too Many Requests

Algorithm ini smooth dalam handling bursts. Tidak langsung block ketika ada spike, tapi juga tidak membiarkan flood melewati.


Dua Directive Utama

Rate limiting di Nginx dikontrol dengan dua directive:

limit_req_zone - Mendefinisikan zone dan rate limit_req - Mengapply zone ke location tertentu

Mari breakdown satu per satu.


Konfigurasi limit_req_zone

Directive ini didefinisikan di http block, biasanya di /etc/nginx/nginx.conf.

http {
    # Zone untuk general requests
    limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;

    # Zone untuk API (lebih strict)
    limit_req_zone $binary_remote_addr zone=api:10m rate=5r/s;

    # Zone untuk login (sangat strict)
    limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;

    # Zone untuk search (resource intensive)
    limit_req_zone $binary_remote_addr zone=search:10m rate=2r/s;

    # Include site configs
    include /etc/nginx/sites-enabled/*;
}

Mari breakdown setiap parameter:

$binary_remote_addr Ini adalah key yang digunakan untuk tracking. Binary format dari IP address visitor. Lebih hemat memory dibanding $remote_addr.

zone=general:10m Nama zone adalah "general" dan dialokasikan 10 megabyte shared memory. Memory ini digunakan untuk menyimpan state setiap IP address. 10MB bisa menyimpan sekitar 160,000 unique IP addresses.

rate=10r/s Maksimum 10 requests per second diizinkan dari satu IP address.

Kenapa buat multiple zones? Karena different endpoints butuh different limits. Login page perlu lebih strict dari homepage. API mungkin perlu limit berbeda dari web pages.


Mengapply Rate Limiting ke Location

Setelah zones didefinisikan, apply ke server block:

server {
    listen 80;
    server_name example.com;
    root /var/www/laravel/public;
    index index.php;

    # General rate limit untuk semua routes
    location / {
        limit_req zone=general burst=20 nodelay;
        try_files $uri $uri/ /index.php?$query_string;
    }

    # Strict rate limit untuk API
    location /api/ {
        limit_req zone=api burst=10 nodelay;
        try_files $uri $uri/ /index.php?$query_string;
    }

    # Sangat strict untuk login dan register
    location ~ ^/(login|register|password) {
        limit_req zone=login burst=5;
        try_files $uri $uri/ /index.php?$query_string;
    }

    # Strict untuk search
    location /search {
        limit_req zone=search burst=5 nodelay;
        try_files $uri $uri/ /index.php?$query_string;
    }

    # PHP handling
    location ~ \\.php$ {
        fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }

    # Deny hidden files
    location ~ /\\.(?!well-known).* {
        deny all;
    }
}

Parameter burst

burst=20 berarti Nginx akan queue sampai 20 requests yang exceed rate limit, instead of langsung reject. Requests dalam queue akan di-process sesuai rate.

Contoh: rate=10r/s dengan burst=20. User mengirim 30 requests sekaligus. 10 requests pertama langsung di-process. 20 requests berikutnya masuk queue. Requests ke-31 dan seterusnya ditolak.

Parameter nodelay

Tanpa nodelay, requests dalam queue akan di-delay untuk match rate limit. Dengan nodelay, semua requests dalam burst di-process immediately.

Kapan pakai nodelay? Ketika kamu ingin experience tetap responsive untuk legitimate burst traffic, tapi tetap protect dari sustained attack.


Connection Limiting

Selain request rate, Nginx bisa membatasi concurrent connections dari satu IP.

http {
    # Define connection zone
    limit_conn_zone $binary_remote_addr zone=conn_limit:10m;

    # Request zones
    limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;

    # ...
}

server {
    # Limit concurrent connections per IP
    limit_conn conn_limit 15;

    # Custom status code untuk limit violations
    limit_conn_status 429;
    limit_req_status 429;

    # ...
}

limit_conn conn_limit 15 berarti satu IP hanya bisa punya maksimum 15 koneksi aktif bersamaan. Browser normal biasanya hanya membuka 6-8 koneksi per domain. 15 memberikan headroom untuk legitimate users tapi membatasi abusers.


Custom Error Response

Default Nginx akan return plain 429 status. Untuk user experience yang lebih baik, buat custom error page.

Untuk Web Pages:

server {
    # Custom error page
    error_page 429 /errors/429.html;

    location = /errors/429.html {
        root /var/www/laravel/public;
        internal;
    }

    # ...
}

Buat file /var/www/laravel/public/errors/429.html:

<!DOCTYPE html>
<html>
<head>
    <title>Too Many Requests</title>
    <style>
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
            background: #f5f5f5;
        }
        .container {
            text-align: center;
            padding: 40px;
        }
        h1 { color: #e53e3e; }
        p { color: #666; }
    </style>
</head>
<body>
    <div class="container">
        <h1>429</h1>
        <h2>Too Many Requests</h2>
        <p>Anda mengirim terlalu banyak request. Silakan tunggu beberapa saat dan coba lagi.</p>
    </div>
</body>
</html>

Untuk API Endpoints:

location /api/ {
    limit_req zone=api burst=10 nodelay;
    error_page 429 = @too_many_requests;
    try_files $uri $uri/ /index.php?$query_string;
}

location @too_many_requests {
    default_type application/json;
    return 429 '{"error": "Too many requests", "message": "Rate limit exceeded. Please wait and try again.", "retry_after": 60}';
}

Response JSON yang proper memudahkan client-side handling.


Blocking Bad Actors

Selain rate limiting, Nginx bisa block IP addresses dan user agents yang diketahui malicious.

Block Specific IPs:

server {
    # Block known bad IPs
    deny 192.168.1.100;
    deny 10.0.0.0/8;

    # Allow everything else
    allow all;

    # ...
}

Block Bad User Agents:

server {
    # Block suspicious user agents
    if ($http_user_agent ~* (wget|curl|nikto|sqlmap|nmap|masscan)) {
        return 403;
    }

    # Block empty user agents
    if ($http_user_agent = "") {
        return 403;
    }

    # ...
}

Catatan: Block berdasarkan user agent bisa di-bypass karena user agent mudah di-spoof. Ini hanya layer tambahan, bukan primary defense.

Whitelist Admin Area:

location /admin {
    # Only allow specific IPs
    allow 203.0.113.50;  # IP kantor client
    allow 103.xxx.xxx.xxx;  # IP kamu
    deny all;

    try_files $uri $uri/ /index.php?$query_string;
}


Complete Nginx Configuration

Berikut complete config yang menggabungkan semua konsep:

# /etc/nginx/nginx.conf

user www-data;
worker_processes auto;
pid /run/nginx.pid;

events {
    worker_connections 1024;
    multi_accept on;
}

http {
    # Basic settings
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    server_tokens off;  # Hide Nginx version

    # MIME types
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    # Logging
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    # Rate limiting zones
    limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
    limit_req_zone $binary_remote_addr zone=api:10m rate=5r/s;
    limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;

    # Connection limiting
    limit_conn_zone $binary_remote_addr zone=conn_limit:10m;

    # Status codes
    limit_req_status 429;
    limit_conn_status 429;

    # Gzip
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;

    # Include site configs
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

# /etc/nginx/sites-available/laravel

server {
    listen 80;
    server_name example.com www.example.com;
    root /var/www/laravel/public;
    index index.php;

    # Connection limit
    limit_conn conn_limit 15;

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    # Block bad user agents
    if ($http_user_agent ~* (wget|nikto|sqlmap|nmap|masscan)) {
        return 403;
    }

    # Custom error pages
    error_page 429 /errors/429.html;
    error_page 403 /errors/403.html;
    error_page 500 502 503 504 /errors/50x.html;

    location = /errors/429.html {
        root /var/www/laravel/public;
        internal;
    }

    # General routes
    location / {
        limit_req zone=general burst=20 nodelay;
        try_files $uri $uri/ /index.php?$query_string;
    }

    # API routes
    location /api/ {
        limit_req zone=api burst=10 nodelay;
        error_page 429 = @api_rate_limit;
        try_files $uri $uri/ /index.php?$query_string;
    }

    location @api_rate_limit {
        default_type application/json;
        return 429 '{"error": "rate_limit_exceeded", "retry_after": 60}';
    }

    # Auth routes (strict)
    location ~ ^/(login|register|password|forgot-password) {
        limit_req zone=login burst=5;
        try_files $uri $uri/ /index.php?$query_string;
    }

    # PHP handling
    location ~ \\.php$ {
        fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_hide_header X-Powered-By;
    }

    # Static files
    location ~* \\.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
        expires 30d;
        access_log off;
        add_header Cache-Control "public, immutable";
    }

    # Deny sensitive files
    location ~ /\\.(?!well-known).* {
        deny all;
    }

    location ~ /\\.env {
        deny all;
    }
}


Testing Rate Limiting

Sebelum deploy ke production, test konfigurasi menggunakan Apache Benchmark (ab).

# Install ab (biasanya sudah ada di Ubuntu)
sudo apt install apache2-utils

# Test normal load - should pass
ab -n 100 -c 10 <http://example.com/>

# Test high load - should trigger rate limit
ab -n 500 -c 50 <http://example.com/>

# Check results
# Look for "Non-2xx responses" - ini yang ke-reject

Kamu juga bisa monitor di log:

# Watch access log untuk 429 responses
tail -f /var/log/nginx/access.log | grep " 429 "

# Count requests per IP
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20


Apply Changes

Setelah edit konfigurasi:

# Test config syntax
sudo nginx -t

# Kalau OK, reload
sudo systemctl reload nginx

Selalu test dengan nginx -t sebelum reload. Typo kecil bisa membuat Nginx gagal start.


Dengan Nginx rate limiting, kamu punya layer kedua yang solid. Bahkan kalau ada traffic yang lolos dari Cloudflare, Nginx akan membatasi sebelum sampai ke PHP.

Tapi defense in depth belum selesai. Di bagian selanjutnya, kita akan setup Layer 3: Laravel rate limiting dan security headers untuk protection di level aplikasi.


Lanjut ke Bagian 4: Laravel Rate Limiting & Security Headers

Bagian 4: Layer 3 - Laravel Rate Limiting & Security Headers

Kita sudah punya Cloudflare di level CDN dan Nginx di level server. Sekarang layer terakhir: aplikasi Laravel itu sendiri.

Kenapa perlu protection di level aplikasi kalau sudah ada dua layer sebelumnya?

Pertama, ada serangan yang sangat targeted ke endpoint spesifik. Misalnya, attacker mengirim requests ke endpoint yang melakukan heavy computation atau complex database queries. Rate limit general mungkin tidak catch ini karena total requests masih di bawah threshold.

Kedua, Laravel bisa memberikan response yang lebih contextual. Kamu bisa return pesan error yang helpful, log detail untuk debugging, atau handle differently berdasarkan user type.

Ketiga, defense in depth berarti setiap layer menambah complexity untuk attacker. Semakin banyak layer yang harus di-bypass, semakin kecil kemungkinan serangan berhasil.


Laravel Built-in Throttle Middleware

Laravel 12 sudah punya rate limiting built-in yang powerful. Ini tersedia lewat throttle middleware.

Default behavior untuk API routes adalah 60 requests per menit per user atau IP. Konfigurasi ada di routes/api.php:

Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
    return $request->user();
});

Di belakang layar, Laravel API routes sudah di-wrap dengan throttle middleware. Kamu bisa lihat di bootstrap/app.php:

->withMiddleware(function (Middleware $middleware) {
    $middleware->api(prepend: [
        \\Laravel\\Sanctum\\Http\\Middleware\\EnsureFrontendRequestsAreStateful::class,
    ]);

    $middleware->alias([
        'throttle' => \\Illuminate\\Routing\\Middleware\\ThrottleRequests::class,
    ]);
})

Cara kerja throttle middleware:

  1. Cek cache untuk menghitung berapa requests dari user/IP dalam periode tertentu
  2. Kalau masih di bawah limit, increment counter dan lanjutkan request
  3. Kalau exceed limit, return 429 Too Many Requests
  4. Counter reset setelah periode berlalu

Custom Rate Limiters

Default 60 requests per menit mungkin terlalu general. Laravel memungkinkan kamu define custom rate limiters dengan logic yang lebih sophisticated.

Buka app/Providers/AppServiceProvider.php:

<?php

namespace App\\Providers;

use Illuminate\\Cache\\RateLimiting\\Limit;
use Illuminate\\Http\\Request;
use Illuminate\\Support\\Facades\\RateLimiter;
use Illuminate\\Support\\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        //
    }

    public function boot(): void
    {
        // Default API rate limiter
        RateLimiter::for('api', function (Request $request) {
            return Limit::perMinute(60)->by(
                $request->user()?->id ?: $request->ip()
            );
        });

        // Strict limiter untuk login
        RateLimiter::for('login', function (Request $request) {
            return Limit::perMinute(5)
                ->by($request->ip())
                ->response(function (Request $request, array $headers) {
                    return response()->json([
                        'message' => 'Terlalu banyak percobaan login. Silakan tunggu dan coba lagi.',
                        'retry_after' => $headers['Retry-After'] ?? 60,
                    ], 429);
                });
        });

        // Limiter untuk contact form
        RateLimiter::for('contact', function (Request $request) {
            return Limit::perHour(5)->by($request->ip());
        });

        // Limiter untuk uploads
        RateLimiter::for('uploads', function (Request $request) {
            return Limit::perMinute(10)->by(
                $request->user()?->id ?: $request->ip()
            );
        });

        // Dynamic limiter berdasarkan user subscription
        RateLimiter::for('premium', function (Request $request) {
            $user = $request->user();

            if (!$user) {
                return Limit::perMinute(20)->by($request->ip());
            }

            return $user->isPremium()
                ? Limit::perMinute(120)->by($user->id)
                : Limit::perMinute(30)->by($user->id);
        });

        // Limiter untuk search (resource intensive)
        RateLimiter::for('search', function (Request $request) {
            return Limit::perMinute(15)
                ->by($request->user()?->id ?: $request->ip())
                ->response(function () {
                    return response()->json([
                        'message' => 'Terlalu banyak pencarian. Silakan tunggu sebentar.',
                    ], 429);
                });
        });

        // Limiter untuk export/download
        RateLimiter::for('export', function (Request $request) {
            return Limit::perHour(10)->by(
                $request->user()?->id ?: $request->ip()
            );
        });
    }
}

Beberapa pattern yang berguna:

perMinute(n) - Limit per menit perHour(n) - Limit per jam perDay(n) - Limit per hari by($key) - Grouping berdasarkan key (user ID, IP, atau custom) response($callback) - Custom response ketika limit exceeded


Mengapply Rate Limiters ke Routes

Setelah define rate limiters, apply ke routes:

// routes/web.php

use App\\Http\\Controllers\\AuthController;
use App\\Http\\Controllers\\ContactController;
use App\\Http\\Controllers\\SearchController;

// Login dengan strict rate limiting
Route::post('/login', [AuthController::class, 'login'])
    ->middleware('throttle:login');

Route::post('/register', [AuthController::class, 'register'])
    ->middleware('throttle:login');

Route::post('/forgot-password', [AuthController::class, 'forgotPassword'])
    ->middleware('throttle:login');

// Contact form
Route::post('/contact', [ContactController::class, 'store'])
    ->middleware('throttle:contact');

// Search
Route::get('/search', [SearchController::class, 'index'])
    ->middleware('throttle:search');

// routes/api.php

use App\\Http\\Controllers\\Api\\PostController;
use App\\Http\\Controllers\\Api\\UploadController;
use App\\Http\\Controllers\\Api\\ExportController;

// Default API routes
Route::middleware(['auth:sanctum', 'throttle:api'])->group(function () {
    Route::apiResource('posts', PostController::class);
    Route::apiResource('comments', CommentController::class);
});

// Upload dengan limit khusus
Route::middleware(['auth:sanctum', 'throttle:uploads'])->group(function () {
    Route::post('/upload', [UploadController::class, 'store']);
});

// Export dengan limit ketat
Route::middleware(['auth:sanctum', 'throttle:export'])->group(function () {
    Route::get('/export/users', [ExportController::class, 'users']);
    Route::get('/export/transactions', [ExportController::class, 'transactions']);
});

// Premium endpoints
Route::middleware(['auth:sanctum', 'throttle:premium'])->group(function () {
    Route::get('/premium/analytics', [AnalyticsController::class, 'index']);
});


Security Headers Middleware

Rate limiting melindungi dari floods. Security headers melindungi dari berbagai jenis attacks lain seperti XSS, clickjacking, dan content sniffing.

Buat middleware baru:

php artisan make:middleware SecurityHeaders

Edit app/Http/Middleware/SecurityHeaders.php:

<?php

namespace App\\Http\\Middleware;

use Closure;
use Illuminate\\Http\\Request;
use Symfony\\Component\\HttpFoundation\\Response;

class SecurityHeaders
{
    /**
     * Headers yang tidak ingin kita expose
     */
    private array $unwantedHeaders = [
        'X-Powered-By',
        'Server',
    ];

    public function handle(Request $request, Closure $next): Response
    {
        $response = $next($request);

        // Hapus headers yang tidak perlu
        foreach ($this->unwantedHeaders as $header) {
            header_remove($header);
        }

        // Prevent clickjacking
        $response->headers->set('X-Frame-Options', 'SAMEORIGIN');

        // Prevent MIME type sniffing
        $response->headers->set('X-Content-Type-Options', 'nosniff');

        // XSS Protection (legacy browsers)
        $response->headers->set('X-XSS-Protection', '1; mode=block');

        // Referrer Policy
        $response->headers->set(
            'Referrer-Policy',
            'strict-origin-when-cross-origin'
        );

        // Permissions Policy
        $response->headers->set(
            'Permissions-Policy',
            'geolocation=(), microphone=(), camera=(), payment=()'
        );

        // HSTS - hanya untuk HTTPS
        if ($request->secure()) {
            $response->headers->set(
                'Strict-Transport-Security',
                'max-age=31536000; includeSubDomains; preload'
            );
        }

        // Content Security Policy
        $response->headers->set(
            'Content-Security-Policy',
            $this->buildCsp()
        );

        return $response;
    }

    /**
     * Build Content Security Policy header
     */
    private function buildCsp(): string
    {
        $directives = [
            "default-src 'self'",
            "script-src 'self' 'unsafe-inline' <https://cdn.jsdelivr.net> <https://cdnjs.cloudflare.com>",
            "style-src 'self' 'unsafe-inline' <https://fonts.googleapis.com> <https://cdn.jsdelivr.net>",
            "font-src 'self' <https://fonts.gstatic.com>",
            "img-src 'self' data: https:",
            "connect-src 'self'",
            "frame-ancestors 'self'",
            "form-action 'self'",
            "base-uri 'self'",
            "object-src 'none'",
        ];

        return implode('; ', $directives);
    }
}

Register middleware di bootstrap/app.php:

->withMiddleware(function (Middleware $middleware) {
    $middleware->append(\\App\\Http\\Middleware\\SecurityHeaders::class);
})

Penjelasan setiap header:

X-Frame-Options: SAMEORIGIN Mencegah website di-embed dalam iframe oleh domain lain. Melindungi dari clickjacking.

X-Content-Type-Options: nosniff Browser tidak boleh menebak MIME type. Mencegah MIME confusion attacks.

X-XSS-Protection: 1; mode=block Mengaktifkan built-in XSS filter di browser lama.

Referrer-Policy: strict-origin-when-cross-origin Mengontrol informasi yang dikirim di Referer header.

Permissions-Policy Menonaktifkan akses ke features seperti geolocation, camera, microphone.

Strict-Transport-Security Memaksa browser selalu menggunakan HTTPS.

Content-Security-Policy Mengontrol dari mana resources boleh di-load. Ini defense paling powerful melawan XSS.


Custom Middleware untuk IP Blocking

Untuk protection tambahan, buat middleware yang track dan auto-block suspicious IPs:

<?php

namespace App\\Http\\Middleware;

use Closure;
use Illuminate\\Http\\Request;
use Illuminate\\Support\\Facades\\Cache;
use Illuminate\\Support\\Facades\\Log;
use Symfony\\Component\\HttpFoundation\\Response;

class BlockSuspiciousTraffic
{
    /**
     * Threshold requests dalam 5 menit sebelum auto-block
     */
    private int $threshold = 300;

    /**
     * Durasi block dalam menit
     */
    private int $blockDuration = 60;

    public function handle(Request $request, Closure $next): Response
    {
        $ip = $request->ip();

        // Cek apakah IP di blacklist
        if ($this->isBlacklisted($ip)) {
            Log::channel('security')->info('Blocked request from blacklisted IP', [
                'ip' => $ip,
                'uri' => $request->getRequestUri(),
            ]);

            abort(403, 'Access denied');
        }

        // Track request count
        $this->trackRequest($ip);

        // Cek apakah perlu auto-block
        if ($this->shouldBlock($ip)) {
            $this->addToBlacklist($ip);

            Log::channel('security')->warning('Auto-blocked IP due to high request rate', [
                'ip' => $ip,
                'request_count' => $this->getRequestCount($ip),
            ]);

            abort(403, 'Access denied - Too many requests');
        }

        return $next($request);
    }

    private function isBlacklisted(string $ip): bool
    {
        return Cache::has("blacklist:{$ip}");
    }

    private function addToBlacklist(string $ip): void
    {
        Cache::put("blacklist:{$ip}", true, now()->addMinutes($this->blockDuration));
    }

    private function trackRequest(string $ip): void
    {
        $key = "request_count:{$ip}";
        $count = Cache::get($key, 0);

        if ($count === 0) {
            Cache::put($key, 1, now()->addMinutes(5));
        } else {
            Cache::increment($key);
        }
    }

    private function getRequestCount(string $ip): int
    {
        return Cache::get("request_count:{$ip}", 0);
    }

    private function shouldBlock(string $ip): bool
    {
        return $this->getRequestCount($ip) > $this->threshold;
    }
}

Register di routes yang perlu extra protection:

Route::middleware(['block-suspicious'])->group(function () {
    // Routes yang rentan
});

Atau apply globally untuk semua routes:

->withMiddleware(function (Middleware $middleware) {
    $middleware->append(\\App\\Http\\Middleware\\BlockSuspiciousTraffic::class);
})


Logging untuk Security Monitoring

Buat dedicated log channel untuk security events:

Tambahkan di config/logging.php:

'channels' => [
    // ... existing channels

    'security' => [
        'driver' => 'daily',
        'path' => storage_path('logs/security.log'),
        'level' => 'debug',
        'days' => 30,
    ],
],

Gunakan di middleware dan controllers:

use Illuminate\\Support\\Facades\\Log;

// Log suspicious activity
Log::channel('security')->warning('Multiple failed login attempts', [
    'ip' => $request->ip(),
    'email' => $request->input('email'),
    'user_agent' => $request->userAgent(),
]);

// Log blocked requests
Log::channel('security')->info('Request blocked by rate limit', [
    'ip' => $request->ip(),
    'uri' => $request->getRequestUri(),
]);


Additional Security Measures

1. Disable Debug Mode di Production

File .env:

APP_DEBUG=false
APP_ENV=production

Debug mode yang aktif di production bisa expose sensitive information.

2. Validate dan Sanitize Input

Selalu validate input sebelum processing:

public function store(Request $request)
{
    $validated = $request->validate([
        'name' => 'required|string|max:100',
        'email' => 'required|email|max:255',
        'message' => 'required|string|max:1000',
    ]);

    // Gunakan validated data, bukan raw input
    Contact::create($validated);
}

3. Use Eloquent, Avoid Raw Queries

Eloquent otomatis protect dari SQL injection:

// Good - protected
User::where('email', $email)->first();

// Bad - vulnerable
DB::select("SELECT * FROM users WHERE email = '$email'");

4. CSRF Protection

Laravel sudah handle ini by default. Pastikan semua form punya @csrf:

<form method="POST" action="/submit">
    @csrf
    <!-- form fields -->
</form>

5. Protect .env File

Pastikan .env tidak accessible via web. Sudah di-handle di Nginx config sebelumnya, tapi double check dengan:

curl <https://yourdomain.com/.env>
# Should return 403 or 404


Packages yang Membantu

spatie/laravel-csp

Package untuk manage Content Security Policy dengan lebih mudah:

composer require spatie/laravel-csp

Publish config dan sesuaikan dengan kebutuhan.

Laravel Telescope (untuk development)

composer require laravel/telescope --dev

Berguna untuk debug dan monitor requests, tapi jangan enable di production.

Laravel Pulse

composer require laravel/pulse

Real-time monitoring untuk production. Bisa monitor slow queries, exceptions, dan request rates.


Dengan rate limiting dan security headers di level Laravel, defense in depth kamu sudah lengkap. Tiga layer protection: Cloudflare, Nginx, dan Laravel.

Di bagian terakhir, kita akan wrap up dengan implementation checklist, monitoring basics, dan tips untuk communicate dengan client.


Lanjut ke Bagian 5: Implementation Checklist & Tips Freelancer

Bagian 5: Implementation Checklist & Tips Freelancer

Kita sudah cover tiga layer protection: Cloudflare, Nginx, dan Laravel. Sekarang saatnya menyatukan semuanya menjadi checklist yang actionable dan membahas aspek non-teknis yang sama pentingnya.

Sebagai freelancer, kamu tidak hanya perlu implement security. Kamu juga perlu communicate dengan client, prepare untuk incidents, dan document semuanya dengan proper.


Implementation Checklist

Gunakan checklist ini untuk setiap project Laravel yang kamu deliver:

Pre-Deployment (Sebelum Go Live)

TaskPriorityNotes
Setup Cloudflare accountHighGunakan email client jika mereka yang manage
Add domain ke CloudflareHighPastikan semua DNS records correct
Update nameserversHighPropagation bisa sampai 24 jam
Configure SSL/TLS Full (strict)HighButuh SSL di origin server
Enable Always Use HTTPSHighRedirect semua HTTP ke HTTPS
Review DDoS protection settingsHighDefault Medium biasanya cukup
Setup firewall rules dasarMedium5 rules di free tier
Configure Nginx rate limitingHighTest sebelum deploy
Setup limit zones untuk general, api, loginHighSesuaikan rate dengan traffic pattern
Create Laravel rate limitersHighDi AppServiceProvider
Add security headers middlewareMediumGlobal middleware
Set APP_DEBUG=falseCriticalJangan lupa
Set APP_ENV=productionCriticalHarus production
Verify .env not accessibleCriticalTest dengan curl
Configure TrustProxies untuk CloudflareHighAgar $request->ip() correct
Test rate limitingHighGunakan ab atau curl
Review error pages (429, 403, 500)MediumUser-friendly messages

Post-Deployment (Setelah Go Live)

TaskFrequencyNotes
Monitor Cloudflare AnalyticsWeeklyCheck threats blocked
Review server logsWeeklyLook for unusual patterns
Check error ratesDaily (first week)Pastikan no false positives
Update Laravel & packagesMonthlySecurity patches
Review firewall rules effectivenessMonthlyAdjust if needed
Test Under Attack ModeQuarterlyKnow how to enable quickly
Backup configurationMonthlyNginx, Laravel configs
Review security logWeeklyCustom security.log

Monitoring Basics untuk Freelancer

Kamu tidak perlu enterprise monitoring tools. Berikut cara sederhana untuk keep track:

Cloudflare Analytics

Login ke Cloudflare dashboard, pilih domain, klik Analytics.

Yang perlu di-check:

  • Traffic overview: apakah ada spike unusual?
  • Threats: berapa yang di-block?
  • Top countries: apakah sesuai dengan target market?

Kalau melihat spike threats dari negara yang bukan target market, consider add geo-blocking rule.

Server Logs Quick Commands

# Watch live access log
tail -f /var/log/nginx/access.log

# Filter hanya 429 responses
tail -f /var/log/nginx/access.log | grep " 429 "

# Count requests per IP (top 20)
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20

# Find IPs with most 429 errors
grep " 429 " /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -10

# Check error log
tail -f /var/log/nginx/error.log

Laravel Log

# Watch Laravel log
tail -f storage/logs/laravel.log

# Watch security log (jika sudah setup)
tail -f storage/logs/security.log

Simple Health Check Endpoint

Tambahkan di routes untuk quick check:

Route::get('/health', function () {
    return response()->json([
        'status' => 'ok',
        'timestamp' => now()->toIso8601String(),
        'laravel' => app()->version(),
    ]);
})->name('health');

Bisa di-ping dari uptime monitoring service gratis seperti UptimeRobot.


Emergency Response Plan

Ketika client report "website down" atau "lambat banget," ini step-by-step response:

Step 1: Verify (1 menit)

Jangan panik. Verifikasi dulu apakah memang down.

Kadang masalah ada di ISP atau device client, bukan server.

Step 2: Quick Diagnosis (2-5 menit)

Kalau memang down atau slow:

  1. Login Cloudflare → Analytics → Overview
  2. Apakah ada spike traffic unusual?
  3. Apakah ada peningkatan threats?
  4. SSH ke server
  5. Check resource: htop atau top
  6. CPU tinggi? Memory penuh?

Step 3: Immediate Action (jika under attack)

Kalau terlihat ada serangan:

  1. Enable Cloudflare Under Attack Mode
    • Dashboard → Overview → Under Attack Mode → ON
  2. Informasikan client: "Sedang handle issue, website akan normal dalam beberapa menit."
  3. Monitor 15-30 menit

Under Attack Mode akan memasang JavaScript challenge. Legitimate users masih bisa akses dengan delay 5 detik. Bots dan automated attacks akan gagal.

Step 4: Investigation (setelah stable)

Setelah kondisi stable:

  1. Review attack patterns di Cloudflare Analytics
  2. Identify source IPs atau countries
  3. Add firewall rules untuk block repeat offenders
  4. Review Nginx access logs
  5. Document semua yang terjadi

Step 5: Communication

Update client dengan clear, non-technical explanation. Template ada di bawah.


Client Communication

Setting Expectation di Awal Project

Sertakan di proposal atau kickoff meeting:

"Website akan dilengkapi dengan proteksi keamanan dasar menggunakan Cloudflare dan rate limiting. Ini melindungi dari sebagian besar serangan DDoS umum. Untuk proteksi enterprise-grade atau dedicated security monitoring, tersedia sebagai layanan tambahan."

Ini set expectation bahwa security sudah di-handle, tapi juga membuka pintu untuk upsell kalau client butuh lebih.

Include di Contract/Agreement

  • Scope of security measures included
  • What's NOT included (24/7 monitoring, incident response SLA, etc.)
  • Maintenance terms (updates, monitoring frequency)
  • Emergency contact procedure

Response Template untuk Incident

Ketika incident terjadi dan client tanya apa yang sedang terjadi:


Subject: Update Status Website [Nama Project]

Halo [Nama Client],

Website mengalami gangguan selama [durasi] pada [tanggal/jam].

Apa yang terjadi: Website menerima lonjakan traffic yang tidak normal (serangan DDoS). Ini adalah jenis serangan yang mencoba membuat website tidak bisa diakses dengan membanjiri server dengan requests palsu.

Yang perlu diketahui:

  • Ini bukan "hacking" dalam artian data dicuri
  • Tidak ada data user yang terekspos
  • Serangan seperti ini bisa terjadi pada website manapun

Tindakan yang sudah diambil:

  1. Mengaktifkan mode proteksi ekstra di Cloudflare
  2. Memblokir sumber-sumber serangan yang teridentifikasi
  3. Memonitor hingga kondisi fully stable

Status saat ini: Website sudah normal dan bisa diakses seperti biasa.

Rekomendasi kedepan: [Sesuaikan dengan situasi - bisa upgrade Cloudflare, tambah monitoring, dll]

Kalau ada pertanyaan, feel free untuk hubungi saya.

Best regards, [Nama Kamu]


Response untuk Pertanyaan Teknis dari Client

Client mungkin tanya: "Gimana caranya supaya tidak kena lagi?"

Template jawaban:

"Website sudah dilengkapi dengan tiga layer proteksi: Cloudflare sebagai pertahanan pertama, rate limiting di server, dan throttling di aplikasi. Tidak ada sistem yang 100% immune dari serangan, tapi kombinasi ini sudah sangat solid untuk sebagian besar kasus.

Untuk proteksi yang lebih advanced, opsi yang bisa dipertimbangkan:

  • Upgrade ke Cloudflare Pro untuk WAF yang lebih lengkap
  • Monitoring 24/7 dengan alert system
  • Dedicated security audit

Mau saya buatkan proposal untuk opsi ini?"


Documentation untuk Client Handoff

Siapkan dokumen sederhana ketika serah terima project:


Security Configuration Documentation

Project: [Nama Project] Domain: [domain.com] Date: [Tanggal] Prepared by: [Nama Kamu]

1. Cloudflare Account

2. Security Measures Applied

Layer 1 - Cloudflare:

  • DDoS Protection: Enabled
  • SSL/TLS: Full (strict)
  • Firewall Rules: [list rules yang aktif]

Layer 2 - Server (Nginx):

  • Rate limiting: 10 req/s general, 5 req/s API, 1 req/s login
  • Connection limit: 15 per IP

Layer 3 - Application (Laravel):

  • Throttle middleware: Active
  • Security headers: Active
  • CSRF protection: Active

3. What's Protected

  • Volumetric DDoS attacks
  • Application layer floods
  • Brute force login attempts
  • Bot attacks
  • Basic web vulnerabilities (XSS, clickjacking)

4. What's NOT Included

  • 24/7 monitoring
  • Guaranteed incident response time
  • Advanced threat intelligence
  • Penetration testing
  • Compliance audits

5. Maintenance Requirements

  • Keep Cloudflare account active
  • Monthly review of analytics (recommended)
  • Quarterly Laravel/package updates
  • Contact [nama/email] untuk issues

6. Emergency Procedure

Jika website down atau under attack:

  1. Contact [nama] via [channel] - response dalam [X jam]
  2. Jika tidak bisa dihubungi, enable Under Attack Mode di Cloudflare
  3. Dashboard: dash.cloudflare.com → [domain] → Under Attack Mode

Pricing Security sebagai Add-On

Sebagai freelancer, kamu bisa package security sebagai value-add service:

PackageIncludedSuggested Price
Basic (Default)Cloudflare free setup, Laravel throttle, security headersIncluded in project
Standard+ Nginx rate limiting, custom firewall rules, documentation+Rp 500rb - 1jt
Premium+ Cloudflare Pro, advanced monitoring setup, incident response+Rp 1.5jt - 3jt
RetainerMonthly monitoring + updates + priority supportRp 500rb - 1jt/bulan

Ini bukan upselling yang manipulative. Ini memberikan opsi untuk client yang value security lebih tinggi, sekaligus memberikan kamu additional revenue.


Resources untuk Melanjutkan

Untuk kamu yang ingin memperdalam skill Laravel security dan menjadi freelancer yang lebih valuable, BuildWithAngga menyediakan resources yang bisa membantu.

Materi Laravel Development dalam Bahasa Indonesia. Belajar Laravel dari fundamental sampai advanced dengan bahasa yang mudah dipahami. Termasuk best practices untuk security, deployment, dan production optimization.

Project-based Learning. Praktek langsung build real projects dengan implementasi security yang proper. Portfolio yang kuat adalah pembeda utama dalam winning freelance projects.

Mentor yang Bisa Ditanya. Ketemu configuration yang tricky? Server error yang tidak ketemu solusinya? Mentor siap membantu dengan guidance langsung.

Komunitas Developers. Connect dengan developers lain, share pengalaman, dan belajar dari case studies project nyata.

Akses Selamanya. Materi selalu bisa diakses kapanpun untuk reference ketika mengerjakan project.


Penutup

DDoS protection bukan luxury. Ini adalah baseline expectation untuk website profesional.

Sebagai freelancer Laravel, kamu tidak hanya deliver fitur. Kamu deliver product yang reliable dan secure. Client mungkin tidak explicitly bilang "tolong tambahin anti DDoS," tapi mereka pasti expect website tidak tumbang karena serangan.

Yang sudah kita cover:

Layer 1 - Cloudflare: First line of defense dengan 449 Tbps capacity. Free tier sudah sangat powerful.

Layer 2 - Nginx: Rate limiting dan connection limiting untuk protect server dari floods.

Layer 3 - Laravel: Throttle middleware dan security headers untuk application-level protection.

Non-Technical: Client communication, documentation, dan emergency response plan.

Implementasi lengkap memakan waktu sekitar 2-3 jam untuk setup awal. Maintenance ongoing minimal, mostly monitoring dan occasional updates.

Ini investment waktu yang sangat worth it.

Satu incident bisa merusak reputasi yang dibangun bertahun-tahun. Satu client yang website-nya reliable akan recommend kamu ke orang lain. Prevention selalu lebih murah dari recovery.

Yang perlu diingat:

Defense in depth. Tidak ada single solution yang sempurna. Kombinasi multiple layers adalah approach yang benar.

Free tools are powerful. Cloudflare free tier, Nginx (open source), Laravel (open source). Kamu tidak perlu budget besar untuk protection solid.

Documentation matters. Client handoff yang proper menunjukkan profesionalisme dan melindungi kamu dari misunderstanding.

Prepare for incidents. Bukan "kalau" tapi "kapan." Punya emergency response plan membuat kamu tenang ketika incident terjadi.

Communicate proactively. Set expectation di awal. Update secara transparan ketika ada issue. Treat client sebagai partner, bukan obstacle.

Sekarang, buka project Laravel terakhir kamu. Cek apakah sudah ada Cloudflare? Apakah Nginx sudah di-configure rate limiting? Apakah throttle middleware sudah applied ke routes sensitif?

Kalau belum, mulai implement hari ini. Layer by layer.

Protect your client's business. Protect your reputation.


**Angga Risky Setiawan** Founder, BuildWithAngga