Security header adalah header HTTP yang memberi tahu browser bagaimana memperlakukan halaman Anda: sumber script mana yang boleh dijalankan, apakah situs wajib HTTPS, apakah boleh ditampilkan di dalam iframe, dan informasi apa yang boleh dibocorkan lewat referrer. Enam header yang paling penting adalah Content-Security-Policy, Strict-Transport-Security, X-Content-Type-Options, X-Frame-Options/frame-ancestors, Referrer-Policy, dan Permissions-Policy. Lima di antaranya bisa dipasang dalam hitungan menit; CSP butuh uji coba bertahap lewat mode Report-Only.
Artikel ini menjelaskan fungsi tiap header, nilai yang aman untuk dipakai, contoh konfigurasi di Nginx, Apache (.htaccess), dan middleware Laravel, serta cara meluncurkan CSP tanpa merusak website. Header ini bekerja di atas HTTPS, jadi pastikan dulu situs Anda sudah memakai SSL. Jika perlu penyegaran konsep, baca cara kerja HTTP dan HTTPS.
Mengapa security header penting
Banyak serangan web tidak membobol server, melainkan memanfaatkan browser korban. Script berbahaya yang disisipkan lewat celah XSS, halaman yang dibungkus iframe untuk clickjacking, atau koneksi HTTP pertama yang dicegat di Wi-Fi publik. Security header tidak memperbaiki bug di kode Anda, tetapi memberi lapisan pertahanan tambahan yang membatasi dampaknya. Header ini juga sering diperiksa oleh audit keamanan, pentest, dan kuesioner klien.
Sebelum mengubah apa pun, lihat dulu header yang sudah dikirim situs Anda:
curl -sI https://domainanda.com | grep -iE "content-security|strict-transport|x-content-type|x-frame|referrer-policy|permissions-policy"
Penjelasan tiap header
Strict-Transport-Security (HSTS)
HSTS memerintahkan browser untuk selalu memakai HTTPS ke domain Anda selama jangka waktu tertentu, bahkan jika pengguna mengetik http://. Ini menutup celah pada permintaan HTTP pertama yang bisa dicegat.
Strict-Transport-Security: max-age=31536000; includeSubDomains
max-agedalam detik; 31536000 sama dengan satu tahun.includeSubDomainsmemberlakukan aturan ke semua subdomain. Pastikan semua subdomain sudah mendukung HTTPS sebelum menambahkannya.preloaddipakai bila Anda ingin mendaftarkan domain ke daftar preload browser. Keputusan ini sulit dibatalkan, jadi jangan menambahkannya tanpa pertimbangan matang.
Mulailah dengan max-age kecil, misalnya 300, lalu naikkan bertahap setelah yakin tidak ada masalah. Browser mengabaikan HSTS yang dikirim lewat HTTP biasa.
X-Content-Type-Options
X-Content-Type-Options: nosniff
Header ini mencegah browser "menebak" tipe file (MIME sniffing). Tanpa header ini, file teks yang diunggah pengguna bisa saja dieksekusi sebagai script. Nilainya hanya satu, nosniff, dan hampir tidak pernah menimbulkan masalah selama server mengirim Content-Type yang benar.
X-Frame-Options dan frame-ancestors
Keduanya mengatur apakah halaman Anda boleh ditampilkan di dalam iframe situs lain, sebagai pertahanan terhadap clickjacking.
X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self'
Direktif CSP frame-ancestors adalah pengganti modern yang lebih fleksibel, karena bisa menyebutkan beberapa domain. X-Frame-Options hanya mendukung DENY dan SAMEORIGIN. Memasang keduanya masih wajar untuk kompatibilitas dengan browser lama; jika keduanya ada, browser modern mengutamakan frame-ancestors.
Referrer-Policy
Referrer-Policy: strict-origin-when-cross-origin
Header ini menentukan seberapa banyak URL asal dikirim saat pengguna mengklik tautan ke situs lain. Nilai strict-origin-when-cross-origin mengirim URL lengkap untuk navigasi di dalam situs sendiri, hanya domain untuk situs lain, dan tidak mengirim apa pun saat turun dari HTTPS ke HTTP. Nilai ini juga menjadi default di browser modern, tetapi menyatakannya secara eksplisit tetap disarankan. Pilih no-referrer bila URL Anda berisi data sensitif seperti token reset password.
Permissions-Policy
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
Header ini membatasi fitur browser yang boleh dipakai halaman dan iframe di dalamnya. Tanda () berarti fitur dimatikan untuk semua asal; (self) berarti hanya untuk domain Anda. Matikan fitur yang memang tidak dipakai. Jika suatu saat website membutuhkan kamera atau lokasi, ubah nilainya.
Content-Security-Policy (CSP)
CSP adalah header paling kuat sekaligus paling rumit. Ia mendaftar sumber yang diizinkan untuk script, style, gambar, font, koneksi, dan frame. Apa pun di luar daftar diblokir browser. Jika penyerang berhasil menyisipkan <script> lewat XSS, CSP yang ketat bisa mencegah script itu berjalan.
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.jsdelivr.net; style-src 'self' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self'; frame-ancestors 'self'; base-uri 'self'; form-action 'self'; object-src 'none'
Beberapa direktif yang perlu dipahami:
default-src: cadangan untuk tipe sumber yang tidak disebutkan.script-src: sumber JavaScript. Hindari'unsafe-inline'dan'unsafe-eval'karena melemahkan perlindungan XSS.object-src 'none': mematikan plugin lawas.base-uri 'self': mencegah penyerang mengganti tag<base>.form-action 'self': membatasi tujuan pengiriman form.
Untuk script inline yang memang dibutuhkan, gunakan nonce: nilai acak per permintaan yang dicantumkan di header dan di atribut nonce tag script.
Header lama X-XSS-Protection tidak perlu ditambahkan lagi. Fitur filter XSS di browser sudah dihapus, dan perlindungan yang tepat adalah CSP.
Konfigurasi di Nginx
Letakkan di blok server HTTPS:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Content-Security-Policy-Report-Only "default-src 'self'; frame-ancestors 'self'; object-src 'none'; base-uri 'self'" always;
Dua hal penting di Nginx. Pertama, parameter always membuat header ikut dikirim pada respons error seperti 404 dan 500. Kedua, add_header tidak diwariskan bila blok di dalamnya (misalnya location) punya add_header sendiri. Jika Anda menambah header cache di location ~* \.(css|js)$, semua security header dari level server hilang untuk lokasi itu. Solusi praktisnya: simpan header di file terpisah lalu include di setiap blok yang membutuhkan.
sudo nginx -t && sudo systemctl reload nginx
Konfigurasi di Apache (.htaccess)
Pastikan modul mod_headers aktif (di cPanel umumnya sudah). Tambahkan ke .htaccess di folder publik:
<IfModule mod_headers.c>
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" "expr=%{HTTPS} == 'on'"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
Header always set Content-Security-Policy-Report-Only "default-src 'self'; frame-ancestors 'self'; object-src 'none'; base-uri 'self'"
</IfModule>
Kondisi expr=%{HTTPS} == 'on' memastikan HSTS hanya dikirim lewat HTTPS. Untuk Laravel di shared hosting, gunakan .htaccess di folder yang benar-benar menjadi document root, sebagaimana dijelaskan di panduan deploy Laravel ke cPanel.
Berlangganan gratis. Artikel & tutorial coding terbaru langsung ke email kamu. Tanpa spam.
Middleware di Laravel
Jika Anda tidak punya akses ke konfigurasi server, atau ingin header (terutama CSP dengan nonce) diatur dari aplikasi, buat middleware:
php artisan make:middleware SecurityHeaders
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Vite;
use Symfony\Component\HttpFoundation\Response;
class SecurityHeaders
{
public function handle(Request $request, Closure $next): Response
{
Vite::useCspNonce();
$nonce = Vite::cspNonce();
$response = $next($request);
$csp = implode('; ', [
"default-src 'self'",
"script-src 'self' 'nonce-{$nonce}'",
"style-src 'self' 'nonce-{$nonce}' https://fonts.googleapis.com",
"font-src 'self' https://fonts.gstatic.com",
"img-src 'self' data: https:",
"frame-ancestors 'self'",
"object-src 'none'",
"base-uri 'self'",
]);
$headers = [
'X-Content-Type-Options' => 'nosniff',
'X-Frame-Options' => 'SAMEORIGIN',
'Referrer-Policy' => 'strict-origin-when-cross-origin',
'Permissions-Policy' => 'camera=(), microphone=(), geolocation=()',
'Content-Security-Policy-Report-Only' => $csp,
];
if ($request->isSecure()) {
$headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains';
}
foreach ($headers as $key => $value) {
$response->headers->set($key, $value);
}
return $response;
}
}
Daftarkan secara global. Di Laravel 11, lewat bootstrap/app.php:
->withMiddleware(function (Middleware $middleware) {
$middleware->append(\App\Http\Middleware\SecurityHeaders::class);
})
Di Laravel 10, tambahkan kelas tersebut ke array $middleware di app/Http/Kernel.php. Dengan Vite::useCspNonce(), tag script dan style yang dihasilkan @vite otomatis membawa atribut nonce. Untuk script inline di Blade, tulis <script nonce="{{ Vite::cspNonce() }}">.
Jika aplikasi Anda berada di belakang proxy seperti Cloudflare atau load balancer, $request->isSecure() hanya akurat bila trusted proxies sudah dikonfigurasi. Masalah serupa sering muncul di daftar error Laravel yang paling sering terjadi.
Hindari memasang header yang sama di server dan di Laravel. Header CSP ganda akan diterapkan keduanya oleh browser, sehingga kebijakan efektifnya menjadi gabungan yang paling ketat dan sulit di-debug.
Meluncurkan CSP dengan Report-Only
CSP yang langsung diterapkan hampir pasti merusak sesuatu: widget pihak ketiga, script analytics, iklan, atau style inline dari plugin. Itulah gunanya header Content-Security-Policy-Report-Only. Browser mengevaluasi kebijakan dan melaporkan pelanggaran, tetapi tidak memblokir apa pun.
- Pasang kebijakan awal sebagai Report-Only, seperti contoh di atas.
- Buka halaman-halaman utama dan lihat tab Console di DevTools. Setiap pelanggaran muncul sebagai peringatan lengkap dengan direktif dan sumber yang diblokir.
- Agar pelanggaran dari pengunjung nyata juga tercatat, tambahkan
report-uriyang menunjuk ke endpoint penampung laporan, atau gunakan direktifreport-toyang lebih baru bersama headerReporting-Endpoints. - Tambahkan sumber sah ke kebijakan, hapus atau pindahkan script inline ke file terpisah atau beri nonce.
- Setelah beberapa hari tanpa pelanggaran baru yang sah, ganti nama header menjadi
Content-Security-Policy.
Contoh endpoint sederhana untuk menampung laporan di Laravel:
Route::post('/csp-report', function (Illuminate\Http\Request $request) {
logger()->warning('CSP violation', $request->json()->all());
return response()->noContent();
})->withoutMiddleware([\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class]);
Beri rate limit pada endpoint ini karena siapa pun bisa mengirim data ke sana. Laporan yang diterima berbentuk JSON; JSON Viewer membantu membacanya.
Kesalahan umum
- Memasang
includeSubDomainsataupreloadterlalu cepat, padahal masih ada subdomain tanpa HTTPS. - Menambahkan
'unsafe-inline'kescript-srcagar error hilang. CSP jadi hampir tidak berguna terhadap XSS. - Lupa parameter
alwaysdi Nginx, sehingga halaman error tidak membawa header. - Header hilang di lokasi tertentu karena aturan pewarisan
add_headerNginx. - Langsung memberlakukan CSP tanpa Report-Only, lalu iklan, analytics, atau form pembayaran berhenti bekerja.
Checklist
- HSTS dikirim lewat HTTPS, dimulai dengan max-age kecil.
X-Content-Type-Options: nosniffaktif.frame-ancestorsdan/atauX-Frame-Optionsterpasang.- Referrer-Policy dan Permissions-Policy dinyatakan eksplisit.
- CSP berjalan dalam Report-Only sampai tidak ada pelanggaran sah.
- Header diverifikasi dengan
curl -I, termasuk pada halaman 404.
FAQ
Apakah security header memengaruhi SEO?
Tidak secara langsung. Namun HTTPS yang konsisten dan situs yang tidak mudah disusupi script berbahaya membantu menjaga kepercayaan pengguna dan mesin pencari.
Perlukah X-Frame-Options jika sudah ada frame-ancestors?
Browser modern mengutamakan frame-ancestors. Menambahkan X-Frame-Options tetap tidak merugikan dan berguna untuk browser yang sangat lama.
Mengapa iklan atau analytics berhenti setelah CSP aktif?
Domain penyedianya belum masuk ke script-src, img-src, connect-src, atau frame-src. Itulah alasan CSP harus diuji dalam mode Report-Only terlebih dahulu.
Lebih baik header dipasang di server atau di Laravel?
Header statis paling efisien di server. CSP berbasis nonce harus dibuat di aplikasi karena nilainya berubah setiap permintaan. Pilih satu tempat untuk tiap header agar tidak ganda.
Apakah X-XSS-Protection masih perlu?
Tidak. Fitur tersebut sudah dihapus dari browser modern. Gunakan CSP sebagai gantinya.