Virtual Host

Virtual Host #

Virtual hosting memungkinkan satu server fisik melayani banyak website atau aplikasi yang berbeda — masing-masing dengan domain, konfigurasi, dan konten tersendiri. Ini adalah kebutuhan yang sangat umum: dari developer yang menjalankan beberapa project di satu VPS, hingga perusahaan yang mengelola puluhan subdomain dari satu server.

Caddy mendukung virtual hosting secara native dengan sintaks yang bersih. Setiap blok site di Caddyfile pada dasarnya adalah satu virtual host.

Konsep Dasar: Name-Based Virtual Hosting #

Name-based virtual hosting adalah bentuk yang paling umum — server membedakan site berdasarkan HTTP Host header yang dikirim browser:

Browser mengirim request ke 93.184.216.34:443
  Request 1: Host: example.com  → Blok site 'example.com'
  Request 2: Host: api.example.com → Blok site 'api.example.com'
  Request 3: Host: other-site.com → Blok site 'other-site.com'

Satu IP, banyak site — dibedakan oleh Host header
(Ini dimungkinkan oleh TLS SNI untuk HTTPS)

Konfigurasi Dasar Multi-Domain #

{
    email [email protected]
}

# Site 1: Website utama
example.com {
    root * /var/www/example
    encode gzip zstd
    file_server
}

# Site 2: Subdomain untuk API
api.example.com {
    reverse_proxy localhost:8080
    
    header {
        Access-Control-Allow-Origin "https://example.com"
    }
}

# Site 3: Blog di subdomain
blog.example.com {
    root * /var/www/blog
    encode gzip
    file_server
}

# Site 4: Domain lain yang di-host di server yang sama
another-site.com {
    root * /var/www/another-site
    file_server
}

# Site 5: Redirect domain lama ke domain baru
old-name.com {
    redir https://example.com{uri} permanent
}

Setiap blok site mendapatkan sertifikat TLS sendiri secara otomatis. Caddy mengelola semua sertifikat ini secara bersamaan.


Redirect www ke non-www (dan Sebaliknya) #

# Pola 1: Redirect www ke non-www (canonical = non-www)
www.example.com {
    redir https://example.com{uri} permanent
}

example.com {
    root * /var/www/html
    file_server
}

# Pola 2: Redirect non-www ke www (canonical = www)
example.com {
    redir https://www.example.com{uri} permanent
}

www.example.com {
    root * /var/www/html
    file_server
}

# Pola 3: Handle keduanya dalam satu blok
# (Caddy menerima keduanya tapi tidak ada canonical redirect)
example.com, www.example.com {
    root * /var/www/html
    file_server
}

# Pola 4: Handle dalam satu blok dengan redirect internal
example.com, www.example.com {
    @www host www.example.com
    redir @www https://example.com{uri} permanent
    
    root * /var/www/html
    file_server
}

Routing Berdasarkan Subdomain dalam Satu Blok #

Untuk arsitektur di mana banyak subdomain dilayani oleh satu pool server, kamu bisa menempatkan semua routing dalam satu blok site:

{
    email [email protected]
}

# Tangkap semua subdomain dalam satu blok
*.example.com, example.com {
    tls {
        dns cloudflare {env.CF_TOKEN}
    }
    
    # Routing per subdomain
    @apex     host example.com
    @app      host app.example.com
    @api      host api.example.com
    @admin    host admin.example.com
    @docs     host docs.example.com
    @staging  host staging.example.com
    
    # Handler per subdomain
    handle @apex {
        root * /var/www/landing
        file_server
    }
    
    handle @app {
        reverse_proxy app-service:3000
    }
    
    handle @api {
        reverse_proxy api-service:8080
        header Access-Control-Allow-Origin "https://app.example.com"
    }
    
    handle @admin {
        # Akses terbatas untuk admin
        @notInternal {
            not remote_ip 10.0.0.0/8 192.168.0.0/16
        }
        respond @notInternal 403
        
        reverse_proxy admin-service:9000
    }
    
    handle @docs {
        root * /var/www
        file_server
    }
    
    handle @staging {
        reverse_proxy staging-service:3001
    }
    
    # Default: 404 untuk subdomain yang tidak dikenal
    respond "Not Found" 404
}

Virtual Host dengan Konfigurasi Berbeda per Environment #

# Production
{
    email [email protected]
}

app.example.com {
    encode gzip zstd
    
    header {
        Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
        X-Frame-Options "SAMEORIGIN"
        -Server
    }
    
    reverse_proxy {
        to app-prod-1:3000 app-prod-2:3000 app-prod-3:3000
        lb_policy round_robin
        health_uri /health
        health_interval 10s
    }
    
    log {
        output file /var/log/caddy/app.log
        format json
    }
}
# Development (Caddyfile.dev)
{
    local_certs
    http_port 8080
    https_port 8443
}

app.localhost:8443 {
    tls internal
    
    # No cache headers untuk development
    header Cache-Control "no-store"
    
    reverse_proxy localhost:3000
    
    log {
        output stderr
        format console
        level DEBUG
    }
}

Path-Based Virtual Hosting #

Selain name-based, kamu juga bisa melakukan routing berdasarkan path — satu domain dengan beberapa “application” di path yang berbeda:

example.com {
    # /app/* → Frontend React
    handle /app/* {
        uri strip_prefix /app
        root * /var/www/app/dist
        
        @notFile not file
        rewrite @notFile /index.html
        
        file_server
    }
    
    # /api/* → Backend Node.js
    handle /api/* {
        reverse_proxy localhost:8080
    }
    
    # /* → Documentation site
    handle /* {
        uri strip_prefix 
        root * /var/www
        file_server
    }
    
    # / → Marketing landing page
    handle {
        root * /var/www/landing
        file_server
    }
}

Virtual Host untuk Development Lokal #

{
    local_certs
}

# Berbagai project di mesin development
# Semua menggunakan internal CA — di-trust setelah 'caddy trust'

myapp.localhost {
    reverse_proxy localhost:3000
}

myapi.localhost {
    reverse_proxy localhost:8080
}

myadmin.localhost {
    reverse_proxy localhost:9000
}

# Static site project
portfolio.localhost {
    root * ~/projects/portfolio/dist
    file_server
}

# PHP project
wordpress.localhost {
    root * ~/projects/wordpress
    php_fastcgi unix//run/php/php8.3-fpm.sock
    file_server
}

Snippet untuk Konfigurasi yang DRY #

Saat mengelola banyak virtual host, snippet membantu menghindari duplikasi:

# Snippet bersama
(common_headers) {
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains"
        X-Content-Type-Options "nosniff"
        X-Frame-Options "SAMEORIGIN"
        Referrer-Policy "strict-origin-when-cross-origin"
        -Server
    }
}

(access_log) {
    log {
        output file /var/log/caddy/{args[0]}.log {
            roll_size 50mb
            roll_keep 5
        }
        format json
    }
}

(static_site) {
    encode gzip zstd
    import common_headers
    file_server
}

# Gunakan snippet di setiap virtual host
example.com {
    root * /var/www/example
    import static_site
    import access_log "example-com"
}

blog.example.com {
    root * /var/www/blog
    import static_site
    import access_log "blog"
}

api.example.com {
    import common_headers
    import access_log "api"
    reverse_proxy localhost:8080
}

Virtual Host dengan TLS Berbeda Per Domain #

{
    email [email protected]
}

# Domain 1: Let's Encrypt (default)
example.com {
    file_server {
        root /var/www/example
    }
}

# Domain 2: ZeroSSL
critical-service.com {
    tls {
        issuer acme {
            ca https://acme.zerossl.com/v2/DV90
            eab {
                key_id  {env.ZEROSSL_KEY_ID}
                mac_key {env.ZEROSSL_MAC_KEY}
            }
        }
    }
    reverse_proxy localhost:9000
}

# Domain 3: Sertifikat manual dari CA komersial
enterprise.com {
    tls /etc/ssl/enterprise.crt /etc/ssl/enterprise.key
    reverse_proxy localhost:7000
}

# Domain 4: Wildcard dengan DNS challenge
*.app.example.com {
    tls {
        dns cloudflare {env.CF_TOKEN}
    }
    reverse_proxy localhost:3000
}

Monitoring dan Troubleshooting Virtual Host #

# Lihat semua site yang dikonfigurasi via Admin API
curl -s http://localhost:2019/config/apps/http/servers/ | jq 'keys'

# Lihat routing detail
curl -s http://localhost:2019/config/apps/http/servers/srv0/routes | jq .

# Cek sertifikat per domain
for domain in example.com api.example.com blog.example.com; do
    echo -n "$domain: "
    echo | openssl s_client -connect "$domain:443" -servername "$domain" 2>/dev/null \
        | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2
done

# Lihat log per domain
sudo journalctl -u caddy | grep "example.com"

# Test koneksi ke setiap virtual host
for domain in example.com api.example.com blog.example.com; do
    echo "$domain: $(curl -sI https://$domain | head -1)"
done

Ringkasan #

  • Setiap blok site di Caddyfile adalah virtual host — Caddy secara otomatis mengelola sertifikat TLS terpisah untuk setiap domain.
  • Gunakan @matcher host subdomain.example.com di dalam blok wildcard untuk routing subdomain yang berbeda ke backend yang berbeda.
  • handle blocks memungkinkan path-based routing yang bersih dalam satu virtual host.
  • Gunakan snippet untuk menghindari duplikasi konfigurasi yang sama di banyak virtual host.
  • Redirect www ke non-www (atau sebaliknya) mudah dilakukan dengan blok terpisah atau matcher host di dalam blok gabungan.
  • Untuk development, gunakan local_certs dan domain .localhost agar semua virtual host development mendapat HTTPS yang valid.

← Sebelumnya: Static Files   Berikutnya: File Server →

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