Error Pages #
Error page yang baik adalah bagian penting dari user experience website yang profesional. Sebuah halaman 404 yang dirancang dengan baik bisa membantu pengunjung menemukan konten yang mereka cari, alih-alih hanya menampilkan pesan error mentah yang membingungkan.
Caddy menyediakan handle_errors — sebuah blok khusus yang dieksekusi ketika terjadi error, memberikan kamu kontrol penuh atas apa yang ditampilkan ketika request gagal.
Cara Kerja handle_errors
#
handle_errors adalah blok yang berjalan ketika handler utama mengembalikan error atau status code tertentu. Ia memiliki akses ke informasi tentang error yang terjadi dan bisa menampilkan response apapun:
Request → Handler utama → Error terjadi (404, 500, dll.)
│
▼
Caddy masuk ke handle_errors
│
▼
Response kustom dikirim ke client
Konfigurasi Dasar #
example.com {
root * /var/www/html
file_server
handle_errors {
# Sajikan file error HTML dari direktori /var/www/errors
root * /var/www/errors
rewrite * /{err.status_code}.html
file_server
}
}
Dengan konfigurasi ini:
- Error 404 → sajikan
/var/www/errors/404.html - Error 500 → sajikan
/var/www/errors/500.html - Error 403 → sajikan
/var/www/errors/403.html
Struktur Direktori Error Pages #
/var/www/errors/
├── 400.html ← Bad Request
├── 401.html ← Unauthorized
├── 403.html ← Forbidden
├── 404.html ← Not Found
├── 408.html ← Request Timeout
├── 429.html ← Too Many Requests
├── 500.html ← Internal Server Error
├── 502.html ← Bad Gateway
├── 503.html ← Service Unavailable
└── 504.html ← Gateway Timeout
Placeholder Error yang Tersedia #
Di dalam handle_errors, kamu bisa menggunakan placeholder khusus yang berisi informasi tentang error:
{err.status_code} → HTTP status code (404, 500, dll.)
{err.status_text} → Text deskripsi status ("Not Found", "Internal Server Error")
{err.message} → Pesan error detail
{err.trace} → Error trace ID (untuk korelasi dengan log)
{err.id} → Error ID unik
Error Page Berbeda per Status Code #
example.com {
root * /var/www/html
file_server
handle_errors {
@404 expression {err.status_code} == 404
@5xx expression {err.status_code} >= 500
# Halaman 404 khusus
handle @404 {
root * /var/www/html
rewrite * /404.html
file_server
}
# Halaman generic untuk 5xx error
handle @5xx {
root * /var/www/html
rewrite * /500.html
file_server
}
# Default untuk error lainnya
handle {
respond "Error {err.status_code}: {err.status_text}" {err.status_code}
}
}
}
Error Page dengan Template Dinamis #
Caddy bisa menggunakan templates directive untuk merender halaman error dengan data dinamis dari placeholder:
example.com {
root * /var/www/html
file_server
handle_errors {
root * /var/www/errors
templates
rewrite * /error.html
file_server
}
}
<!-- /var/www/errors/error.html -->
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Error {{placeholder "err.status_code"}} — Nama Situs</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: system-ui, -apple-system, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.card {
background: white;
border-radius: 16px;
padding: 3rem;
text-align: center;
max-width: 480px;
width: 90%;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
}
.status-code {
font-size: 6rem;
font-weight: 800;
color: #6366f1;
line-height: 1;
margin-bottom: 0.5rem;
}
.status-text {
font-size: 1.5rem;
color: #1f2937;
margin-bottom: 1rem;
}
.message {
color: #6b7280;
margin-bottom: 2rem;
line-height: 1.6;
}
.actions { display: flex; gap: 1rem; justify-content: center; }
.btn {
padding: 0.75rem 1.5rem;
border-radius: 8px;
text-decoration: none;
font-weight: 600;
transition: opacity 0.2s;
}
.btn:hover { opacity: 0.85; }
.btn-primary { background: #6366f1; color: white; }
.btn-secondary { background: #f3f4f6; color: #374151; }
.trace {
margin-top: 2rem;
font-size: 0.75rem;
color: #9ca3af;
font-family: monospace;
}
</style>
</head>
<body>
<div class="card">
<div class="status-code">{{placeholder "err.status_code"}}</div>
<div class="status-text">{{placeholder "err.status_text"}}</div>
<div class="message">
{{if eq (placeholder "err.status_code") "404"}}
Halaman yang kamu cari tidak ditemukan. Mungkin sudah dipindahkan
atau URL yang kamu masukkan salah.
{{else if eq (placeholder "err.status_code") "403"}}
Kamu tidak memiliki izin untuk mengakses halaman ini.
{{else if ge (placeholder "err.status_code") "500"}}
Terjadi kesalahan di sisi server kami. Tim kami sudah diberitahu
dan sedang bekerja untuk memperbaikinya.
{{else}}
Terjadi kesalahan yang tidak terduga. Silakan coba lagi.
{{end}}
</div>
<div class="actions">
<a href="/" class="btn btn-primary">🏠 Ke Beranda</a>
<a href="javascript:history.back()" class="btn btn-secondary">← Kembali</a>
</div>
{{if ge (placeholder "err.status_code") "500"}}
<div class="trace">
Error ID: {{placeholder "err.trace"}}
</div>
{{end}}
</div>
</body>
</html>
Error Pages untuk Reverse Proxy #
Ketika Caddy digunakan sebagai reverse proxy, error bisa terjadi di dua level: Caddy sendiri (misal: backend tidak bisa dijangkau → 502) atau dari backend (misal: aplikasi return 500).
app.example.com {
handle_errors {
# Error 502/503/504: Backend tidak bisa dijangkau
@backendDown expression {err.status_code} in [502, 503, 504]
handle @backendDown {
respond `<!DOCTYPE html>
<html>
<head><title>Maintenance</title></head>
<body>
<h1>🔧 Sedang dalam Pemeliharaan</h1>
<p>Aplikasi kami sedang dalam pemeliharaan singkat. Silakan coba lagi dalam beberapa menit.</p>
</body>
</html>` {err.status_code}
}
# Error 404 dari backend atau Caddy
@notFound expression {err.status_code} == 404
handle @notFound {
root * /var/www/error-pages
rewrite * /404.html
file_server
}
# Semua error lain
handle {
root * /var/www/error-pages
rewrite * /generic-error.html
file_server
}
}
reverse_proxy localhost:3000 {
health_uri /health
health_interval 10s
# Jika semua upstream down, Caddy return 503
}
}
Maintenance Mode #
Pola umum untuk menampilkan halaman maintenance:
example.com {
# Aktifkan maintenance mode: uncomment baris di bawah
# respond "Sedang maintenance" 503
root * /var/www/html
file_server
handle_errors {
@503 expression {err.status_code} == 503
handle @503 {
root * /var/www/maintenance
rewrite * /index.html
file_server
}
}
}
Error Logging dan Alerting #
app.example.com {
log {
output file /var/log/caddy/access.log
format json
}
handle_errors {
# Log error 5xx untuk monitoring
@server_errors expression {err.status_code} >= 500
handle @server_errors {
# Catat ke log terpisah untuk alert
# (Caddy otomatis log semua error ke access.log)
respond `{"error": "{err.status_code}", "trace": "{err.trace}"}` {err.status_code}
}
handle {
root * /var/www/errors
rewrite * /{err.status_code}.html
file_server
}
}
reverse_proxy localhost:3000
}
Contoh Halaman 404 yang Baik #
<!-- /var/www/html/404.html — contoh halaman 404 yang membantu user -->
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<title>404 — Halaman Tidak Ditemukan</title>
<style>
body { font-family: system-ui, sans-serif; max-width: 600px;
margin: 4rem auto; padding: 0 1rem; color: #333; }
h1 { font-size: 3rem; color: #6366f1; }
.suggestions { background: #f8f9ff; border-radius: 8px;
padding: 1.5rem; margin: 2rem 0; }
.suggestions h2 { font-size: 1.1rem; margin-bottom: 1rem; }
.suggestions ul { list-style: none; padding: 0; }
.suggestions li { padding: 0.3rem 0; }
.suggestions a { color: #6366f1; }
.search-form { display: flex; gap: 0.5rem; margin-top: 1.5rem; }
.search-form input { flex: 1; padding: 0.75rem; border: 1px solid #ddd;
border-radius: 6px; font-size: 1rem; }
.search-form button { padding: 0.75rem 1.5rem; background: #6366f1;
color: white; border: none; border-radius: 6px;
cursor: pointer; }
</style>
</head>
<body>
<h1>404</h1>
<p>Halaman yang kamu cari tidak ditemukan.</p>
<p>URL mungkin sudah berubah atau halaman telah dihapus.</p>
<div class="suggestions">
<h2>Coba salah satu ini:</h2>
<ul>
<li><a href="/">🏠 Halaman Utama</a></li>
<li><a href="/blog">📝 Blog</a></li>
<li><a href="">📚 Dokumentasi</a></li>
<li><a href="/kontak">📬 Hubungi Kami</a></li>
</ul>
</div>
<form class="search-form" action="/search" method="get">
<input type="text" name="q" placeholder="Cari di situs ini...">
<button type="submit">Cari</button>
</form>
</body>
</html>
Ringkasan #
handle_errorsadalah blok khusus yang dieksekusi saat terjadi error — letakkan di akhir blok site, setelah semua handler lain.- Gunakan
{err.status_code}dan{err.status_text}untuk membuat response yang spesifik per jenis error.- Untuk error page yang lebih kaya, gunakan
templatesdirective dan file HTML dengan Go template syntax untuk menampilkan data error secara dinamis.- Pisahkan penanganan error 4xx (client errors) dari 5xx (server errors) — keduanya membutuhkan pesan yang berbeda untuk user.
- Error 502/503/504 dari reverse proxy menandakan backend tidak bisa dijangkau — tampilkan halaman maintenance yang informatif.
- Error page yang baik: sertakan link ke halaman lain yang berguna, form search, dan cara menghubungi support — jangan hanya tampilkan kode error saja.
← Sebelumnya: Directory Browse Berikutnya: Konsep Reverse Proxy →