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.comdi dalam blok wildcard untuk routing subdomain yang berbeda ke backend yang berbeda.handleblocks 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
hostdi dalam blok gabungan.- Untuk development, gunakan
local_certsdan domain.localhostagar semua virtual host development mendapat HTTPS yang valid.