Static Files #
Menyajikan file statis adalah salah satu use case paling fundamental dari sebuah web server. Caddy melakukannya dengan sangat efisien dan dengan konfigurasi yang minimal — sebuah kombinasi yang membuat Caddy menjadi pilihan populer untuk hosting static website, SPA (Single Page Application), dan aset statis.
Artikel ini membahas semua yang perlu kamu ketahui untuk menyajikan file statis secara optimal dengan Caddy, dari konfigurasi paling dasar hingga optimasi production yang lengkap.
Konfigurasi Paling Sederhana #
# Tiga baris ini sudah cukup untuk static website lengkap dengan HTTPS
example.com {
root * /var/www/html
file_server
}
Di balik tiga baris ini, Caddy secara otomatis:
- Mendapatkan dan memperbarui sertifikat TLS dari Let’s Encrypt
- Redirect semua HTTP request ke HTTPS
- Menyajikan file dari
/var/www/htmlsesuai path request - Mengirim MIME type yang tepat untuk setiap jenis file
- Mengompresi response jika client mendukung (dengan
encode)
Direktif root — Memahami Webroot
#
Direktif root mendefinisikan direktori base dari mana file akan disajikan. Argumen pertamanya adalah matcher yang menentukan untuk request mana root ini berlaku:
example.com {
# '*' = berlaku untuk semua request
root * /var/www/html
# Bisa juga set root berbeda untuk path tertentu
root /images/* /storage/images
root /* /var/www
root * /var/www/html # default untuk semua lainnya
file_server
}
Implikasi Penting #
Request ke: /images/logo.png
Jika root * /var/www/html
→ Caddy baca: /var/www/html/images/logo.png
Jika root /images/* /storage/images
→ Caddy baca: /storage/images/logo.png
(path setelah prefix /images/ ditambahkan ke root)
Permission yang Diperlukan #
User caddy (atau user yang menjalankan Caddy) harus bisa membaca file di direktori root:
# Cek user yang menjalankan Caddy
ps aux | grep caddy
# Set ownership yang benar
sudo chown -R caddy:caddy /var/www/html
# Atau berikan read permission ke semua
sudo chmod -R o+r /var/www/html
sudo chmod o+x /var/www/html # Execute bit diperlukan untuk direktori
# Verifikasi
ls -la /var/www/html
sudo -u caddy cat /var/www/html/index.html # Test akses sebagai user caddy
Direktif file_server — Opsi Lengkap
#
example.com {
root * /var/www/html
file_server {
# File index yang dicari (urutan prioritas)
# Default: index.html
index index.html index.htm default.html
# Sembunyikan file/direktori tertentu
# Pattern menggunakan glob, cocokkan dari root
hide .git .env .htaccess *.secret *.key wp-config.php
# Nonaktifkan canonical redirect (trailing slash)
# Default: Caddy redirect /about ke /about/
# disable_canonical_uris
# Aktifkan directory listing (lihat artikel 'browse' untuk detail)
# browse
# Precompressed files — sajikan .gz atau .br jika ada
precompressed gzip br
}
}
precompressed — Sajikan File Pre-compressed
#
example.com {
root * /var/www/html
file_server {
# Jika client mendukung gzip dan /style.css.gz ada di disk,
# Caddy akan sajikan file .gz langsung
precompressed gzip br
}
}
Workflow untuk precompressed files:
# Kompres semua file JS dan CSS saat build
cd /var/www/html
# Buat versi .gz
find . -name "*.js" -o -name "*.css" -o -name "*.html" | while read f; do
gzip -k -9 "$f"
done
# Buat versi .br (Brotli) — butuh brotli tool
find . -name "*.js" -o -name "*.css" -o -name "*.html" | while read f; do
brotli -k -q 11 "$f"
done
# Hasil: setiap file.js juga punya file.js.gz dan file.js.br
# Caddy memilih versi yang tepat berdasarkan Accept-Encoding header
MIME Types #
Caddy secara otomatis mendeteksi MIME type berdasarkan ekstensi file. Tapi ada kasus di mana kamu perlu override atau menambahkan MIME type kustom:
example.com {
root * /var/www/html
# Override MIME type untuk ekstensi tertentu
@wasm {
path *.wasm
}
header @wasm Content-Type "application/wasm"
# MIME type untuk format modern
@avif path *.avif
header @avif Content-Type "image/avif"
@webp path *.webp
header @webp Content-Type "image/webp"
file_server
}
Caching Headers untuk Performa #
Konfigurasi caching yang tepat bisa dramatically mengurangi beban server dan mempercepat loading untuk returning visitors:
example.com {
root * /var/www/html
# Strategy: cache-busting via hash di nama file
# File seperti app.a1b2c3d4.js di-cache selamanya
# HTML tidak di-cache (agar versi terbaru selalu dimuat)
# Aset dengan hash di nama file (cache agresif — 1 tahun)
@hashedAssets {
path_regexp \.[a-f0-9]{8,}\.(js|css|woff2?|ttf|eot)$
}
header @hashedAssets Cache-Control "public, max-age=31536000, immutable"
# Gambar (cache moderate — 30 hari)
@images path *.jpg *.jpeg *.png *.gif *.webp *.avif *.svg *.ico
header @images Cache-Control "public, max-age=2592000"
# HTML — selalu cek versi terbaru
@html path *.html
header @html Cache-Control "no-cache"
# Root path (/)
@root path /
header @root Cache-Control "no-cache"
# JSON, XML, teks — jangan cache
@dynamic path *.json *.xml
header @dynamic Cache-Control "no-store"
encode gzip zstd
file_server
}
Security Headers untuk Static Site #
example.com {
root * /var/www/html
header {
# Security headers
Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
Referrer-Policy "strict-origin-when-cross-origin"
# Content Security Policy — sesuaikan dengan kebutuhan
Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com;"
# Hapus header yang tidak perlu
-Server
-X-Powered-By
}
encode gzip zstd
file_server
}
Deployment SPA (React, Vue, Angular) #
SPA menggunakan client-side routing — URL seperti /about dan /products/123 tidak memiliki file yang sesuai di filesystem. Caddy perlu dikonfigurasi untuk mengembalikan index.html untuk semua path yang tidak ditemukan, agar React Router / Vue Router bisa menanganinya:
app.example.com {
root * /var/www/app/dist
encode gzip zstd
# Caching untuk aset statis (hashed filenames dari build tool)
@hashedAssets {
path_regexp assets/.*\.[a-f0-9]{8}\.(js|css)$
}
header @hashedAssets Cache-Control "public, max-age=31536000, immutable"
# Header untuk HTML
@html path *.html /
header @html Cache-Control "no-cache"
# KUNCI UNTUK SPA: jika file tidak ada di filesystem,
# kembalikan index.html agar routing client-side bekerja
@notFound {
not file
not path /api/* # Jangan redirect API calls
}
rewrite @notFound /index.html
file_server
}
Vite Build Output #
# Contoh struktur build output Vite/React
/var/www/app/dist/
├── index.html
├── assets/
│ ├── index-a1b2c3d4.js ← Hash di nama file
│ ├── index-e5f6g7h8.css ← Hash di nama file
│ └── vendor-i9j0k1l2.js ← Hash di nama file
└── images/
└── logo.svg
Menyajikan Multiple Static Sites #
{
email [email protected]
}
# Site 1: Blog
blog.example.com {
root * /var/www/blog
encode gzip zstd
file_server
}
# Site 2: Documentation
docs.example.com {
root * /var/www
encode gzip zstd
# Redirect root ke /introduction
@root path /
redir @root /introduction permanent
file_server
}
# Site 3: Marketing landing page
example.com, www.example.com {
@www host www.example.com
redir @www https://example.com{uri} permanent
root * /var/www/landing
encode gzip zstd
header {
Cache-Control "no-cache" # Landing page selalu fresh
-Server
}
file_server
}
Melindungi File Sensitif #
example.com {
root * /var/www/html
# Blokir akses ke file/direktori sensitif
@sensitive {
path /.env
path /.git/*
path /wp-config.php
path /config.php
path /database.yml
path /secrets.json
path /*.key
path /*.pem
path /*.sql
path /*.bak
path /node_modules/*
path /vendor/*
}
respond @sensitive 404
# Blokir file yang diawali titik (dotfiles)
@dotfiles {
path_regexp ^/\.
}
respond @dotfiles 404
file_server
}
Optimasi Bandwidth: Encode + Etag #
example.com {
root * /var/www/html
# Kompresi response
encode {
gzip 6 # Level kompresi gzip (1-9)
zstd # Lebih efisien dari gzip
minimum_length 1024 # Hanya compress >= 1KB
}
# ETag otomatis dihasilkan Caddy berdasarkan:
# - Modification time file
# - Size file
# Browser menggunakan ETag untuk conditional GET (If-None-Match)
# → Server return 304 Not Modified tanpa body jika file tidak berubah
# → Hemat bandwidth signifikan untuk returning visitors
file_server
}
Deployment Workflow #
# Workflow deploy static site ke Caddy
# 1. Build site
npm run build # atau hugo, jekyll, gatsby, dll.
# 2. Sync ke server
rsync -avz --delete ./dist/ user@server:/var/www/html/
# 3. Fix permission (jika perlu)
ssh user@server "sudo chown -R caddy:caddy /var/www/html"
# 4. Reload Caddy (tidak perlu restart untuk static files saja)
ssh user@server "sudo systemctl reload caddy"
# Verifikasi deployment
curl -I https://example.com
# Cek header Cache-Control, Content-Encoding, dll.
Ringkasan #
- Kombinasi
root * /path+file_serveradalah konfigurasi minimal yang sudah berfungsi untuk static site lengkap dengan HTTPS otomatis.- Untuk SPA, gunakan matcher
not file+rewrite @notFound /index.htmlagar client-side routing bekerja.- Aktifkan
encode gzip zstduntuk kompresi otomatis — dampak bandwidth sangat signifikan terutama untuk JS/CSS besar.- Gunakan
precompressed gzip brdifile_serverjika kamu pre-compress aset saat build — lebih efisien dari kompresi on-the-fly.- Set caching headers yang tepat:
immutableuntuk aset dengan hash,no-cacheuntuk HTML agar versi terbaru selalu dimuat.- Selalu lindungi file sensitif (
.env,.git,config.php) dengan matcher +respond 404.- User
caddyharus memiliki permission membaca file di webroot — gunakanchown -R caddy:caddyatauchmod o+r.