Pesan 500 Internal Server Error sering menjadi titik awal kepanikan: pengguna tidak tahu apa yang terjadi, sementara developer juga belum tentu langsung tahu bagian mana yang gagal. Masalahnya bukan karena error 500 itu salah, melainkan karena ia hanya menjelaskan gejala paling umum dari kegagalan di sisi server.
Aplikasi web yang sehat tidak harus bebas dari error. Yang lebih realistis adalah membuat error mudah dilacak oleh tim internal, tetapi tetap aman dan tidak membingungkan bagi pengguna. Kuncinya ada pada pemisahan antara pesan untuk pengguna dan informasi untuk developer.
Jangan tampilkan detail teknis ke semua orang
Saat PHP mengalami exception atau kesalahan fatal, informasi seperti nama file, query SQL, path folder, versi library, bahkan kredensial yang tidak sengaja ikut tercetak bisa muncul di halaman. Dalam mode pengembangan, detail ini membantu. Dalam production, detail yang sama dapat menjadi petunjuk bagi pihak yang ingin mencari celah.
Karena itu, aplikasi sebaiknya memiliki dua perilaku berbeda:
- Development: detail error boleh ditampilkan atau dicatat secara lengkap agar proses debugging cepat.
- Production: pengguna hanya menerima pesan umum, sedangkan detail disimpan di log yang terlindungi.
Pengaturan seperti display_errors perlu ditinjau kembali sebelum aplikasi dipublikasikan. Menyembunyikan error dari layar bukan berarti mengabaikannya. Error tetap harus dicatat melalui mekanisme logging yang bisa diperiksa oleh tim.
Gunakan satu pintu untuk menangani exception
Kesalahan sering menjadi sulit ditangani ketika setiap controller atau file PHP memiliki cara sendiri untuk menampilkan pesan. Sebagian memakai try-catch, sebagian mencetak error langsung, dan sebagian lagi hanya mengembalikan halaman kosong.
Lebih baik siapkan satu lapisan penanganan exception. Dalam aplikasi modern, lapisan ini bisa berada di middleware, front controller, atau bootstrap aplikasi. Tugasnya adalah menangkap exception yang tidak tertangani, mencatat detailnya, lalu mengirimkan respons yang sesuai.
try {
$result = $service->processOrder($request);
return responseJson($result, 200);
} catch (Throwable $exception) {
$errorId = bin2hex(random_bytes(8));
error_log(sprintf(
'[%s] %s in %s:%d',
$errorId,
$exception->getMessage(),
$exception->getFile(),
$exception->getLine()
));
return responseJson([
'message' => 'Terjadi kesalahan pada server.',
'error_id' => $errorId
], 500);
}Contoh tersebut memakai Throwable agar dapat menangani exception maupun sebagian error yang dapat ditangkap. Implementasi nyata tentu perlu menyesuaikan framework dan struktur aplikasi yang digunakan.
Error ID membuat laporan pengguna lebih berguna
Kalimat “Website-nya error” terlalu umum untuk ditindaklanjuti. Sebaliknya, jika halaman menampilkan kode seperti ERR-7F3A91C2, pengguna dapat menyebutkan kode tersebut kepada admin atau customer service.
Error ID tidak harus berisi informasi tentang penyebab error. Justru lebih baik jika ID dibuat acak dan tidak mudah ditebak. Di sisi server, ID itu dikaitkan dengan detail exception, waktu kejadian, endpoint, dan informasi teknis lain yang relevan.
Dengan begitu, percakapan antara pengguna dan tim support menjadi lebih singkat:
“Saat menyimpan pesanan muncul kode ERR-7F3A91C2 pada pukul 14.05.”
Tim tidak perlu menebak-nebak halaman yang dimaksud. Mereka bisa mencari ID tersebut di log dan melihat kejadian yang sesuai.
Bedakan error yang bisa dipulihkan dan yang tidak
Tidak semua masalah harus berakhir sebagai error 500. Salah satu kesalahan umum adalah menganggap setiap kegagalan sebagai masalah server, padahal penyebabnya mungkin berasal dari input pengguna atau kondisi bisnis yang memang tidak valid.
- 400 Bad Request: format permintaan tidak benar.
- 401 Unauthorized: pengguna belum terautentikasi.
- 403 Forbidden: pengguna sudah dikenal, tetapi tidak memiliki izin.
- 404 Not Found: data atau halaman yang diminta tidak ditemukan.
- 409 Conflict: permintaan bertabrakan dengan kondisi data saat ini.
- 422 Unprocessable Content: input terbaca, tetapi tidak memenuhi aturan validasi.
- 500 Internal Server Error: kegagalan tidak terduga di sisi server.
Pembedaan ini penting untuk frontend, API client, dan tim support. Jika semua respons memakai status 500, aplikasi akan sulit membedakan antara input yang perlu diperbaiki dan bug yang harus segera diselidiki.
Jangan mencatat sembarang data
Logging memang penting, tetapi log juga bisa menjadi sumber kebocoran data. Password, token akses, nomor kartu, isi cookie, dan data pribadi tidak seharusnya dicatat secara utuh.
Sebelum menulis sesuatu ke log, tanyakan: apakah data ini benar-benar dibutuhkan untuk mendiagnosis masalah? Jika jawabannya tidak, jangan catat. Jika dibutuhkan, pertimbangkan masking atau penyamaran sebagian nilai.
Contohnya, email dapat ditampilkan sebagai na***@contoh.com, sedangkan token cukup dicatat beberapa karakter terakhirnya—atau tidak dicatat sama sekali. Log juga perlu memiliki batas akses, masa penyimpanan, dan kebijakan penghapusan yang jelas.
Respons API harus konsisten
Untuk API, format error yang konsisten jauh lebih berguna daripada pesan yang berubah-ubah di setiap endpoint. Client dapat memeriksa struktur yang sama tanpa menulis logika khusus untuk setiap jenis kegagalan.
{
"error": {
"code": "ORDER_PROCESSING_FAILED",
"message": "Pesanan belum dapat diproses.",
"error_id": "ERR-7F3A91C2"
}
}Field code sebaiknya stabil dan dapat dipakai program, sementara message ditujukan untuk manusia. Jangan menjadikan teks pesan sebagai satu-satunya dasar logika frontend karena teks dapat berubah untuk kebutuhan bahasa atau pengalaman pengguna.
Apa yang bisa dilakukan sekarang?
- Periksa apakah detail error masih tampil di production.
- Buat satu mekanisme global untuk menangkap exception yang tidak tertangani.
- Tambahkan error ID pada respons halaman dan API.
- Gunakan status HTTP sesuai jenis masalahnya.
- Pastikan log tidak menyimpan password, token, atau data sensitif.
- Uji halaman error secara sengaja, bukan hanya menunggu bug muncul.
Halaman error yang baik tidak perlu terlihat canggih. Yang penting, pengguna mendapat penjelasan yang tenang dan langkah berikutnya yang masuk akal, sementara developer memperoleh cukup konteks untuk menemukan akar masalah.
Dengan pendekatan ini, error 500 tidak lagi sekadar pesan buntu. Ia menjadi titik temu antara keamanan, pengalaman pengguna, dan proses debugging yang lebih teratur.
– Rio Yotto @rioyotto
