Directory Browse

Directory Browse #

Directory browsing memungkinkan pengunjung melihat daftar isi direktori ketika mereka mengakses path yang tidak memiliki index file. Secara default, Caddy mengembalikan 404 untuk direktori tanpa index — ini adalah perilaku yang aman. Untuk mengaktifkan directory listing, kamu perlu secara eksplisit menambahkan opsi browse di file_server.

Fitur ini berguna untuk file sharing server internal, repository download, atau dokumentasi yang disusun dalam folder tanpa halaman index.

Mengaktifkan Directory Browse #

files.example.com {
    root * /var/www/files
    
    file_server {
        browse    # Aktifkan directory listing
    }
}

Dengan konfigurasi ini, mengakses https://files.example.com/ akan menampilkan daftar semua file dan subdirektori di /var/www/files/, lengkap dengan nama file, ukuran, dan tanggal modifikasi.


Tampilan Default Browse #

Caddy menyediakan template HTML bawaan yang cukup bersih dan fungsional:

Directory listing tampilan default:
  ┌─────────────────────────────────────────────────────┐
  │  /downloads/                                        │
  ├─────────────────────────────────────────────────────┤
  │  📁 documents/         -           2024-01-15 10:00 │
  │  📁 images/            -           2024-01-14 09:30 │
  │  📄 README.md          1.2 KB      2024-01-13 14:25 │
  │  📄 manual.pdf         4.5 MB      2024-01-12 16:40 │
  │  📄 software.zip      23.1 MB      2024-01-11 11:15 │
  └─────────────────────────────────────────────────────┘

Template default menyertakan:

  • Nama file/direktori (dengan link)
  • Ukuran file (untuk file, bukan direktori)
  • Waktu modifikasi terakhir
  • Link kembali ke parent directory
  • Sorting berdasarkan nama (default)

Template Kustom #

Caddy mendukung template HTML kustom untuk mengubah tampilan directory listing sesuai kebutuhanmu. Template menggunakan format Go templates (text/template):

files.example.com {
    root * /var/www/files
    
    file_server {
        browse /etc/caddy/browse-template.html
    }
}

Template Sederhana #

<!-- /etc/caddy/browse-template.html -->
<!DOCTYPE html>
<html lang="id">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{.Name}} — File Browser</title>
    <style>
        * { box-sizing: border-box; margin: 0; padding: 0; }
        body { font-family: system-ui, sans-serif; background: #f5f5f5; color: #333; }
        .container { max-width: 960px; margin: 2rem auto; padding: 0 1rem; }
        h1 { font-size: 1.4rem; margin-bottom: 1rem; color: #1a1a1a; }
        .breadcrumb { font-size: 0.9rem; margin-bottom: 1.5rem; }
        .breadcrumb a { color: #0066cc; text-decoration: none; }
        .breadcrumb a:hover { text-decoration: underline; }
        table { width: 100%; background: white; border-radius: 8px;
                box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-collapse: collapse; }
        thead th { padding: 0.75rem 1rem; text-align: left; font-weight: 600;
                   border-bottom: 2px solid #e0e0e0; background: #fafafa; }
        tbody td { padding: 0.6rem 1rem; border-bottom: 1px solid #f0f0f0; }
        tbody tr:last-child td { border-bottom: none; }
        tbody tr:hover { background: #f8f9ff; }
        a { color: #0066cc; text-decoration: none; }
        a:hover { text-decoration: underline; }
        .dir { color: #e67e00; }
        .size, .time { color: #666; font-size: 0.9rem; white-space: nowrap; }
        .icon { margin-right: 0.4rem; }
    </style>
</head>
<body>
<div class="container">
    <h1>📁 {{.Name}}</h1>
    
    <!-- Breadcrumb navigation -->
    <div class="breadcrumb">
        {{range .Breadcrumbs}}
            <a href="{{.Link}}">{{.Text}}</a> /
        {{end}}
    </div>
    
    <table>
        <thead>
            <tr>
                <th>Nama</th>
                <th>Ukuran</th>
                <th>Terakhir Diubah</th>
            </tr>
        </thead>
        <tbody>
            <!-- Parent directory link -->
            {{if ne .Path "/"}}
            <tr>
                <td><a href="../">⬆ Kembali ke folder sebelumnya</a></td>
                <td class="size">—</td>
                <td class="time">—</td>
            </tr>
            {{end}}
            
            <!-- Direktori terlebih dahulu -->
            {{range .Items}}
            {{if .IsDir}}
            <tr>
                <td>
                    <span class="icon">📁</span>
                    <a class="dir" href="{{.URL}}">{{.Name}}/</a>
                </td>
                <td class="size">—</td>
                <td class="time">{{.ModTime.Format "02 Jan 2006, 15:04"}}</td>
            </tr>
            {{end}}
            {{end}}
            
            <!-- File setelah direktori -->
            {{range .Items}}
            {{if not .IsDir}}
            <tr>
                <td>
                    <span class="icon">📄</span>
                    <a href="{{.URL}}">{{.Name}}</a>
                </td>
                <td class="size">{{.HumanSize}}</td>
                <td class="time">{{.ModTime.Format "02 Jan 2006, 15:04"}}</td>
            </tr>
            {{end}}
            {{end}}
        </tbody>
    </table>
</div>
</body>
</html>

Variabel yang Tersedia di Template #

// Variabel yang bisa digunakan dalam template browse
.Name        string      // Nama direktori saat ini
.Path        string      // Path lengkap
.URL         string      // URL direktori saat ini
.Items       []FileInfo  // Daftar file dan subdirektori

// FileInfo fields:
.Name        string      // Nama file/direktori
.Size        int64       // Ukuran dalam bytes
.HumanSize   string      // Ukuran readable (misalnya "4.5 MB")
.URL         string      // URL untuk mengakses
.ModTime     time.Time   // Waktu modifikasi
.IsDir       bool        // True jika direktori

.Breadcrumbs []Crumb     // Untuk navigasi breadcrumb
// Crumb fields:
.Text        string      // Label
.Link        string      // URL

Mengamankan Directory Browse #

Directory listing yang tidak dilindungi bisa menjadi risiko keamanan — siapa saja bisa melihat struktur file kamu. Selalu tambahkan layer keamanan:

Basic Authentication #

files.example.com {
    root * /var/www/files
    
    # Lindungi seluruh site dengan password
    basicauth {
        # Generate hash: caddy hash-password --plaintext "password123"
        alice $2a$14$tR3.5pJb7B9vJMrg9BNi/u5mGUBjzFz8A.NlSFKxCN3h1R0LbYXRa
        bob   $2a$14$kQ7.8pLc9C0wMnsh0COj/v6nHVCkGGz9B.OmTGLyDO4i2S1McZYSb
    }
    
    file_server {
        browse
        hide .git .env
    }
}
# Buat bcrypt hash untuk password
caddy hash-password --plaintext "password-kamu"
# Output: $2a$14$...hash...

Batasi Akses ke IP Tertentu #

internal-files.company.com {
    root * /var/lib/shared-files
    
    # Hanya izinkan akses dari jaringan internal
    @external {
        not remote_ip 10.0.0.0/8 192.168.0.0/16 172.16.0.0/12
    }
    respond @external 403
    
    file_server {
        browse
    }
}

Gabungan IP Filter + Basic Auth #

secure-files.example.com {
    root * /var/www/secure
    
    # Layer 1: Hanya dari jaringan tertentu
    @outsider {
        not remote_ip 203.0.113.0/24 10.0.0.0/8
    }
    respond @outsider 403
    
    # Layer 2: Password protection
    basicauth {
        admin $2a$14$adminHashHere
    }
    
    file_server {
        browse
        hide .git .env *.key *.pem
    }
}

Menyembunyikan File dari Listing #

files.example.com {
    root * /var/www/files
    
    file_server {
        browse
        
        # File yang disembunyikan dari listing DAN tidak bisa diakses
        # Glob patterns didukung
        hide {
            .git
            .env
            .htaccess
            *.bak
            *.tmp
            *.log
            config.php
            secrets.json
            .DS_Store    # macOS metadata file
            Thumbs.db    # Windows thumbnail cache
        }
    }
}

Use Case: Internal File Sharing Server #

share.company.internal {
    tls internal    # Sertifikat dari internal CA Caddy
    
    root * /var/lib/company-files
    
    # Hanya bisa diakses dari jaringan internal
    @external not remote_ip 10.0.0.0/8 192.168.0.0/16
    respond @external 403
    
    # Basic auth sebagai layer tambahan
    basicauth * {
        alice   $2a$14$aliceHash
        bob     $2a$14$bobHash
        charlie $2a$14$charlieHash
    }
    
    # Log akses untuk audit
    log {
        output file /var/log/caddy/fileshare-access.log
        format json
    }
    
    file_server {
        browse /etc/caddy/browse-corporate.html
        
        hide .git .env *.key
    }
}

Menambahkan Search ke Directory Listing #

Template kustom bisa menyertakan fungsionalitas search client-side:

<!-- Tambahkan ke bagian <head> template -->
<script>
function filterFiles(query) {
    const rows = document.querySelectorAll('tbody tr.file-row');
    const lq = query.toLowerCase();
    rows.forEach(row => {
        const name = row.dataset.name || '';
        row.style.display = name.toLowerCase().includes(lq) ? '' : 'none';
    });
}
</script>

<!-- Input search di atas tabel -->
<input type="search"
       placeholder="Cari file..."
       oninput="filterFiles(this.value)"
       style="padding: 0.5rem; width: 100%; margin-bottom: 1rem;
              border: 1px solid #ddd; border-radius: 4px;">

<!-- Tambahkan data-name ke setiap baris file -->
{{range .Items}}
<tr class="file-row" data-name="{{.Name}}">
    ...
</tr>
{{end}}

Browse di Subdirektori Tertentu Saja #

example.com {
    root * /var/www/html
    
    # Aktifkan browse hanya untuk /public-files/*
    handle /public-files/* {
        root * /var/www/public-files
        
        file_server {
            browse
        }
    }
    
    # Semua path lain: static site biasa tanpa browse
    handle {
        file_server
    }
}

Disable Browse untuk Keamanan #

Jika kamu pernah mengaktifkan browse dan ingin menonaktifkannya, cukup hapus opsi tersebut:

# ANTI-PATTERN: Browse aktif di production tanpa proteksi
example.com {
    root * /var/www/production
    file_server {
        browse    # ← BERBAHAYA jika tidak dilindungi!
    }
}

# BENAR: Browse dinonaktifkan (default) atau dilindungi
example.com {
    root * /var/www/production
    file_server        # Tanpa browse — direktori tanpa index → 404
}

Ringkasan #

  • Directory browse dinonaktifkan secara default — aktifkan secara eksplisit dengan opsi browse di file_server hanya jika diperlukan.
  • Selalu lindungi directory listing dengan basicauth atau IP restriction sebelum deploy ke production — jangan biarkan listing publik tanpa proteksi.
  • Template kustom (browse /path/template.html) menggunakan Go template syntax dengan variabel .Items, .Name, .Breadcrumbs, dan lainnya untuk layout yang lebih baik.
  • Gunakan hide untuk menyembunyikan file sensitif dari listing — file yang di-hide juga tidak bisa diakses via URL langsung.
  • Untuk file sharing internal, kombinasikan tls internal + IP restriction + basicauth untuk keamanan berlapis.
  • Opsi browse di dalam blok handle memungkinkan kamu mengaktifkan listing hanya untuk subdirektori tertentu tanpa mempengaruhi path lain.

← Sebelumnya: File Server   Berikutnya: Error Pages →

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