Static Files

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/html sesuai 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_server adalah konfigurasi minimal yang sudah berfungsi untuk static site lengkap dengan HTTPS otomatis.
  • Untuk SPA, gunakan matcher not file + rewrite @notFound /index.html agar client-side routing bekerja.
  • Aktifkan encode gzip zstd untuk kompresi otomatis — dampak bandwidth sangat signifikan terutama untuk JS/CSS besar.
  • Gunakan precompressed gzip br di file_server jika kamu pre-compress aset saat build — lebih efisien dari kompresi on-the-fly.
  • Set caching headers yang tepat: immutable untuk aset dengan hash, no-cache untuk HTML agar versi terbaru selalu dimuat.
  • Selalu lindungi file sensitif (.env, .git, config.php) dengan matcher + respond 404.
  • User caddy harus memiliki permission membaca file di webroot — gunakan chown -R caddy:caddy atau chmod o+r.

← Sebelumnya: DNS Challenge   Berikutnya: Virtual Host →

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