File Server #
Direktif file_server adalah engine di balik kemampuan Caddy untuk menyajikan file statis. Meski terlihat sederhana dari luar, file_server memiliki banyak opsi yang memungkinkannya digunakan untuk berbagai use case — dari serving website sederhana hingga file download server dengan directory listing yang rapi.
Artikel ini membahas semua opsi file_server secara mendalam, termasuk mekanisme internal yang penting dipahami untuk debugging masalah yang umum terjadi.
Cara Kerja file_server #
Saat request masuk dan diteruskan ke file_server, Caddy melakukan serangkaian langkah:
Request: GET /products/gadgets/phone.html
1. Gabungkan root path + request URI:
root = /var/www/html
uri = /products/gadgets/phone.html
→ Cari: /var/www/html/products/gadgets/phone.html
2. Jika file ditemukan:
→ Baca file
→ Set MIME type berdasarkan ekstensi
→ Cek If-None-Match (ETag) atau If-Modified-Since
→ Jika tidak berubah: return 304 Not Modified
→ Jika berubah: return 200 dengan isi file
3. Jika file tidak ditemukan:
→ Coba path/index.html (jika path adalah direktori)
→ Jika tidak ada index: return 404 (atau directory listing jika browse aktif)
Semua Opsi file_server #
example.com {
root * /var/www/html
file_server {
# ── Index Files ──────────────────────────────────────────
# File yang dicari ketika request ke direktori (tanpa path file spesifik)
# Caddy mencoba dalam urutan ini
# Default: index.html
index index.html index.htm index.php default.html
# ── Directory Browsing ───────────────────────────────────
# Aktifkan directory listing ketika tidak ada index file
# Nonaktif secara default (alasan keamanan)
# browse
# ── Hidden Files ─────────────────────────────────────────
# Sembunyikan file/direktori dari listing dan akses
# Mendukung glob pattern
# Path relatif terhadap root
hide .git .svn .env .htaccess *.secret node_modules
# ── Canonical URIs ───────────────────────────────────────
# Default: Caddy redirect /about ke /about/ (trailing slash)
# untuk direktori, dan /about/ ke /about untuk file
# Nonaktifkan jika trailing slash menyebabkan masalah
# disable_canonical_uris
# ── Precompressed Files ──────────────────────────────────
# Sajikan versi pre-compressed jika ada dan client mendukung
# Caddy cek file.gz dan file.br sebelum melayani file asli
precompressed gzip br zstd
# ── Status Code Override ─────────────────────────────────
# Paksa status code tertentu untuk semua response dari file_server
# Jarang digunakan, berguna untuk specific error pages
# status 403
}
}
try_files — Fallback File #
try_files adalah directive yang bekerja sebelum file_server — ia mencoba beberapa path secara berurutan dan menggunakan yang pertama ditemukan:
example.com {
root * /var/www/html
# Coba dalam urutan:
# 1. File persis seperti diminta ({path})
# 2. File dengan .html ditambahkan ({path}.html)
# 3. Fallback ke /index.html
try_files {path} {path}.html /index.html
file_server
}
Use Case: “Clean URLs” (Tanpa Ekstensi .html) #
example.com {
root * /var/www/html
# Visitor ke /about → Caddy coba /about.html
# Visitor ke /blog/post-1 → Caddy coba /blog/post-1.html
try_files {path} {path}.html /404.html
file_server
}
Use Case: SPA dengan Fallback ke index.html #
app.example.com {
root * /var/www/app/dist
# Cara 1: try_files
try_files {path} /index.html
file_server
}
# Cara 2: rewrite (lebih eksplisit)
app.example.com {
root * /var/www/app/dist
@notFile not file
rewrite @notFile /index.html
file_server
}
Canonical URI Handling #
file_server secara default melakukan redirect untuk memastikan URI canonical:
Direktori tanpa trailing slash → Redirect ke dengan trailing slash
Request ke: → 301 redirect ke /
File dengan trailing slash → Redirect ke tanpa trailing slash
Request ke: /style.css/ → 301 redirect ke /style.css
Ini berguna untuk SEO (menghindari duplicate content) tapi bisa menyebabkan masalah dalam kondisi tertentu:
example.com {
root * /var/www/html
# Nonaktifkan canonical redirect jika menyebabkan masalah
file_server {
disable_canonical_uris
}
}
Serving dari Multiple Root #
example.com {
# Root berbeda untuk path berbeda
root /images/* /storage/images
root /videos/* /storage/videos
root /* /var/www
root * /var/www/html # Default root
file_server
}
Contoh: CDN-like Asset Server #
assets.example.com {
# Serve assets dari berbagai lokasi storage
root /v1/* /storage/v1/assets
root /v2/* /storage/v2/assets
root /user-uploads/* /storage/user-content
root * /storage/public
encode {
gzip
zstd
minimum_length 1024
}
header {
# CORS untuk semua origin (CDN-like behavior)
Access-Control-Allow-Origin "*"
Access-Control-Allow-Methods "GET, HEAD, OPTIONS"
# Cache agresif untuk aset (assumsi aset tidak berubah)
Cache-Control "public, max-age=86400"
-Server
}
file_server {
hide .git .env
}
}
File Download Server #
downloads.example.com {
root * /var/www/downloads
# Basic auth untuk melindungi akses
basicauth {
user1 $2a$14$hash1
user2 $2a$14$hash2
}
# Force download untuk semua file (Content-Disposition: attachment)
@downloadable {
path *.zip *.tar.gz *.pdf *.dmg *.exe *.deb *.rpm
}
header @downloadable Content-Disposition "attachment"
# MIME type untuk file yang mungkin tidak terdeteksi
@torrent path *.torrent
header @torrent Content-Type "application/x-bittorrent"
# Directory listing agar user bisa browse
file_server {
browse
hide .htaccess .git
}
}
Range Requests untuk Large Files #
Caddy secara otomatis mendukung HTTP Range Requests — ini penting untuk streaming video, resume download, dan akses parsial file besar:
Client: GET /video.mp4
Range: bytes=1048576-2097151
Caddy: 206 Partial Content
Content-Range: bytes 1048576-2097151/104857600
Content-Length: 1048576
(hanya bagian yang diminta dikirim)
Tidak perlu konfigurasi khusus — Caddy menangani ini otomatis. Tapi ada hal yang perlu diperhatikan:
video.example.com {
root * /var/www/videos
# Jangan gunakan encode untuk video!
# Kompresi mengganggu Range Requests karena offset byte berubah
# encode gzip ← JANGAN untuk video
header {
# Accept-Ranges header menginformasikan client bahwa server support range
Accept-Ranges "bytes"
# Cache untuk video (bisa di-cache cukup lama)
Cache-Control "public, max-age=86400"
}
file_server
}
ETag dan Conditional Requests #
Caddy secara otomatis menghasilkan ETag untuk setiap file yang disajikan. ETag adalah identifier unik berdasarkan content file — digunakan untuk conditional requests:
Request pertama:
GET /style.css
Response:
200 OK
ETag: "abc123def456"
Content-Length: 45678
(full content)
Request berikutnya (browser sudah punya cache):
GET /style.css
If-None-Match: "abc123def456"
Response jika file tidak berubah:
304 Not Modified
(tidak ada body — hemat bandwidth)
Response jika file berubah:
200 OK
ETag: "xyz789uvw012"
(full content baru)
example.com {
root * /var/www/html
# ETag aktif otomatis — tidak perlu konfigurasi
# Tapi bisa dikontrol via header directive
# Nonaktifkan ETag jika diperlukan
# header -ETag
file_server
}
Perbandingan dengan Nginx untuk Static Files #
Caddy file_server vs Nginx try_files:
Nginx:
location / {
root /var/www/html;
index index.html;
try_files $uri $uri/ /index.html;
gzip on;
gzip_types text/css application/javascript;
add_header X-Frame-Options SAMEORIGIN;
add_header Cache-Control "no-cache";
}
# + konfigurasi SSL terpisah
# + cronjob certbot terpisah
Caddy:
example.com {
root * /var/www/html
try_files {path} /index.html
encode gzip zstd
header X-Frame-Options SAMEORIGIN
header Cache-Control "no-cache"
file_server
}
# SSL otomatis sudah termasuk
Caddy: lebih sedikit baris, HTTPS otomatis, tidak perlu config SSL terpisah
Troubleshooting file_server #
403 Forbidden #
# Paling umum: permission issue
ls -la /var/www/html
# Caddy perlu read+execute permission pada direktori
# dan read permission pada file
sudo chown -R caddy:caddy /var/www/html
# atau
sudo chmod -R o+rX /var/www/html # r untuk file, X untuk direktori saja
404 Not Found Padahal File Ada #
# Cek apakah path request cocok dengan lokasi file di filesystem
# Request ke /about → mencari /var/www/html/about atau /var/www/html/about.html
# Test langsung
caddy adapt --config /etc/caddy/Caddyfile | jq .apps.http.servers
# Tambahkan logging sementara
# log { output stderr level DEBUG }
# Verifikasi root yang aktif
curl -v https://example.com/about 2>&1 | grep -i "location\|x-caddy\|< HTTP"
File Lama Masih Muncul (Cache) #
# Browser mungkin menggunakan cached version
# Force refresh dengan Ctrl+Shift+R (hard reload)
# Atau cek ETag/Last-Modified di server
curl -I https://example.com/style.css | grep -i "etag\|last-modified\|cache"
# Hapus cache Caddy (tidak ada cache server-side di file_server standar)
# Caddy file_server tidak melakukan caching server-side
# Masalah biasanya di browser atau CDN di depan Caddy
Ringkasan #
file_serversecara otomatis menangani MIME types, ETag, Range Requests, dan canonical URI redirects tanpa konfigurasi tambahan.- Gunakan
try_filesuntuk “clean URLs” (tanpa ekstensi.html) atau SPA fallback keindex.html.precompressed gzip brdifile_servermemungkinkan Caddy menyajikan file pre-compressed langsung dari disk — lebih efisien dari on-the-fly compression.- Jangan gunakan
encodeuntuk video atau file biner besar yang perlu Range Requests — kompresi merusak byte offsets.- Direktori
rootyang berbeda untuk path yang berbeda (root /images/* /storage/images) memungkinkan aset dari berbagai lokasi storage disajikan melalui satu virtual host.- File berhidden (
.env,.git) sebaiknya di-hide— file sensitif tidak boleh bisa diakses dari luar.