File Server

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_server secara otomatis menangani MIME types, ETag, Range Requests, dan canonical URI redirects tanpa konfigurasi tambahan.
  • Gunakan try_files untuk “clean URLs” (tanpa ekstensi .html) atau SPA fallback ke index.html.
  • precompressed gzip br di file_server memungkinkan Caddy menyajikan file pre-compressed langsung dari disk — lebih efisien dari on-the-fly compression.
  • Jangan gunakan encode untuk video atau file biner besar yang perlu Range Requests — kompresi merusak byte offsets.
  • Direktori root yang 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.

← Sebelumnya: Virtual Host   Berikutnya: Directory Browse →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact