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?
| Feature | Free | Pro ($20/mo) | Business ($200/mo) |
|---|---|---|---|
| DDoS Protection | Unlimited | Unlimited | Unlimited |
| WAF Managed Rules | Basic | Full OWASP | Full + Custom |
| Custom Firewall Rules | 5 | 20 | 100 |
| Rate Limiting | Basic | Advanced | Advanced |
| Bot Management | Basic | Advanced | Advanced |
| Uptime SLA | None | None | 100% |
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:
- Cek cache untuk menghitung berapa requests dari user/IP dalam periode tertentu
- Kalau masih di bawah limit, increment counter dan lanjutkan request
- Kalau exceed limit, return 429 Too Many Requests
- 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)
| Task | Priority | Notes |
|---|---|---|
| Setup Cloudflare account | High | Gunakan email client jika mereka yang manage |
| Add domain ke Cloudflare | High | Pastikan semua DNS records correct |
| Update nameservers | High | Propagation bisa sampai 24 jam |
| Configure SSL/TLS Full (strict) | High | Butuh SSL di origin server |
| Enable Always Use HTTPS | High | Redirect semua HTTP ke HTTPS |
| Review DDoS protection settings | High | Default Medium biasanya cukup |
| Setup firewall rules dasar | Medium | 5 rules di free tier |
| Configure Nginx rate limiting | High | Test sebelum deploy |
| Setup limit zones untuk general, api, login | High | Sesuaikan rate dengan traffic pattern |
| Create Laravel rate limiters | High | Di AppServiceProvider |
| Add security headers middleware | Medium | Global middleware |
| Set APP_DEBUG=false | Critical | Jangan lupa |
| Set APP_ENV=production | Critical | Harus production |
| Verify .env not accessible | Critical | Test dengan curl |
| Configure TrustProxies untuk Cloudflare | High | Agar $request->ip() correct |
| Test rate limiting | High | Gunakan ab atau curl |
| Review error pages (429, 403, 500) | Medium | User-friendly messages |
Post-Deployment (Setelah Go Live)
| Task | Frequency | Notes |
|---|---|---|
| Monitor Cloudflare Analytics | Weekly | Check threats blocked |
| Review server logs | Weekly | Look for unusual patterns |
| Check error rates | Daily (first week) | Pastikan no false positives |
| Update Laravel & packages | Monthly | Security patches |
| Review firewall rules effectiveness | Monthly | Adjust if needed |
| Test Under Attack Mode | Quarterly | Know how to enable quickly |
| Backup configuration | Monthly | Nginx, Laravel configs |
| Review security log | Weekly | Custom 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.
- Cek dari browser kamu (beda network dari client)
- Gunakan isitdownrightnow.com atau downforeveryoneorjustme.com
- Cek status.cloudflare.com
Kadang masalah ada di ISP atau device client, bukan server.
Step 2: Quick Diagnosis (2-5 menit)
Kalau memang down atau slow:
- Login Cloudflare → Analytics → Overview
- Apakah ada spike traffic unusual?
- Apakah ada peningkatan threats?
- SSH ke server
- Check resource:
htopatautop - CPU tinggi? Memory penuh?
Step 3: Immediate Action (jika under attack)
Kalau terlihat ada serangan:
- Enable Cloudflare Under Attack Mode
- Dashboard → Overview → Under Attack Mode → ON
- Informasikan client: "Sedang handle issue, website akan normal dalam beberapa menit."
- 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:
- Review attack patterns di Cloudflare Analytics
- Identify source IPs atau countries
- Add firewall rules untuk block repeat offenders
- Review Nginx access logs
- 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:
- Mengaktifkan mode proteksi ekstra di Cloudflare
- Memblokir sumber-sumber serangan yang teridentifikasi
- 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
- Email: [email yang digunakan untuk daftar]
- Plan: Free / Pro
- Dashboard: dash.cloudflare.com
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:
- Contact [nama] via [channel] - response dalam [X jam]
- Jika tidak bisa dihubungi, enable Under Attack Mode di Cloudflare
- Dashboard: dash.cloudflare.com → [domain] → Under Attack Mode
Pricing Security sebagai Add-On
Sebagai freelancer, kamu bisa package security sebagai value-add service:
| Package | Included | Suggested Price |
|---|---|---|
| Basic (Default) | Cloudflare free setup, Laravel throttle, security headers | Included 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 |
| Retainer | Monthly monitoring + updates + priority support | Rp 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