簡介

Caddy

Caddy 是一個年輕的 web server,以 Go 語言撰寫,特色:

  • 支援自動從 Let’s Encrypt 取得與更新 TLS 1.3 加密憑證。
  • 以 HTTPS 作為預設的通訊協定。
  • 支援 HTTP/3。
  • 支援 IPv6。
  • 不依賴其它套件或 runtime,可獨立執行,因此很適合放在容器內跑。
  • 以 production 為目標,可替代爺爺級的 Apache 或 Nginx。
  • 提供組態設定 API。
  • 支援 virtual host。
  • 支援 reverse proxy。
  • 支援 rewrite。
  • 支援壓縮。

當然其它特色還很多,但其中比較值得一提的就是上面這幾點。

安裝

前面提到 Caddy 本身不用依賴其它套件,所以官方發布的就只是個單一的執行檔,撇開之後由我們自己產生的設定檔不算的話,這就是 Caddy 的全部了!

依照 Caddy 網站的路徑下載 caddy,這裡我們直接抓 Caddy 2 的版本(以下的範例也都是以 Caddy 2 為基準),抓下來的檔名因版次或平台不同會有差異,先改名成簡潔的 caddy,再幫它加一下可執行的屬性:

> chmod a+x ./caddy

除了自行下載外,一些 Linux 也有納入 Caddy 包,以 Ubuntu 系為例:

> sudo apt install caddy

用套件包安裝還會自動配置好系統服務,但是以開發環境來說這反而有點礙手了,往往我會把服務關掉:

> sudo systemctl disable caddy.service

免得它跟我搶 port。

靜態網頁伺服器

在 Caddy 所在位置新增一個 index.html,內容如下:

<h1>Hello Caddy!</h1>

最單純的用法,這個簡易的指令體現了 Caddy 的簡潔:

> ./caddy file-server --listen :2015

2019/11/30 00:36:24.301 WARN admin admin endpoint disabled
2019/11/30 00:59:38.744 INFO tls cleaned up storage units
2019/11/30 08:36:24 [INFO][cache:0xc0000d0140 Started certificate maintenance routine
2019/11/30 08:36:24 Caddy 2 serving static files on :2015

在此我們指定埠號為 2015,Caddy 會去找當前位置下的 index.html 來顯示,因此用瀏覽器開 127.0.0.1:2015 應該會看到如下圖畫面:

Hello Caddy!

檔案伺服器

先把剛剛的 index.html 改名或刪除,再加上 --browse 來跑,--browse 表示如果找不到 index.html,就列出當前位置下的檔案:

> ./caddy file-server --browse --listen :2015

2019/11/30 00:36:24.301 WARN admin admin endpoint disabled
2019/11/30 08:36:24 [INFO][cache:0xc0000d0140 Started certificate maintenance routine
2019/11/30 08:36:24 Caddy 2 serving static files on :2015

這次打開 127.0.0.1:2015 應該可以看到類似下面的畫面:

Caddy

前面指令內的 --browse 表示開啟瀏覽目前 Caddy 所在位置的目錄檔案清單 ,也就是上面看到的抓圖內容。

Caddyfile

前面都是簡單的範例,如果要做比較正規的應用,則需要一些組態設定,在 Caddy 的世界,除了用 API 之外,還支援 Caddyfile 的設定檔,API 與 Caddyfile 不同之處在於,API 必須使用 JSON 格式傳遞溝通,而 Caddyfile 的語法更像是傳統的定檔的格式,對人類來說更容易讀寫。

這裡必須再提一次這篇文章是講 Caddy 2,因為 Caddy 1 與 Caddy 2 的 Caddyfile 是不同的,雖然不同,但只是設定檔的關鍵字與格式不同,整體的 Caddyfile 語法結構還是相同的,所以還是得參考 Caddy 1 的 The Caddyfile Syntax 來了解 Caddyfile 的結構。

沿用剛才的例子,做一個簡單的 index.html,以及一個 Caddyfile 內容如下:

localhost:8080 {
  file_server
}

應該很容易理解,第一行表示監聽 localhost:8080,並且後方以大括號包住的區塊都是這組 localhost:8080 的設定。(在 Linux 針對埠號 1024 以下的常用埠號有權限限制,如果沒有權限的用戶要監聽埠號 80 的話會被系統阻擋,故要監聽 1024 以下的埠號須以 root 權限跑 Caddy。)

第二行表示啟動靜態檔案伺服,有 index.html 就會回應出去,並且未開啟目錄瀏覽。

跑起來看看:

> ./caddy run

caddy run 會自己去找同樣位置下的 Caddyfile,所以應該會看到同樣的 Hello Caddy! 網頁:

Caddy

Virtual Host

如果想要設定兩組網址,以 www1.example.com 與 www2.example.com 為例,可能的 Caddyfile 會長這樣:

www1.example.com:80 {
  root /www/www1.example.com
  file_server
}

www2.example.com:80 {
  root /www/www2.example.com
  file_server
}

在上面的例子中,我們用 root 來指定該組網址對應到的真實路徑。

反向代理

Caddy 可以當坦承接流量,並把請求轉送到後端的服務,而且配置超簡單,非常適合跑當代百花齊放的框架、語言。

一般服務在正式環境都會配有所謂的 app server,它負責調配服務的資源,例如 Python 系的 Gunicorn、Ruby 的 Passenger、PHP-FPM 等等,不論是哪種,Caddy 都可以扮演他們的反向代理,典型的 Caddyfile 配置會像這樣:

roysucks.com {
	encode zstd gzip
	reverse_proxy /* 127.0.0.1:8000
}

沒了,真的只有這樣,並且該轉送的 HTTP 標頭一個也不少。

如果是 PHP,那 Caddy 更有專門為它服務的 php_fastcgi 可用,Caddyfile 大概會像這樣:

php_fastcgi /* 127.0.0.1:9000

是不是超級簡單,香~,當然這些都只是最簡單的用法,更進階的配置請去看 Caddy 文件囉!

實際範例

Caddyfile 可用的指令頗多,難以一一解釋,下面是本人比較常用的範例:

前後端都走反向代理

{
	debug
	auto_https disable_redirects
	email [email protected]
}

http:// {
	# Enable Zstandard and Gzip compression
	encode zstd gzip

	log {
		# output file /var/log/access.log
	}

	handle_path /api/* {
		reverse_proxy :8000
	}

	handle {
		reverse_proxy :3000
	}
}

這是一個典型前後端分離、開發環境的配置,分為兩個區塊,最上面那塊沒有標注位址的是 Caddy 的全域設定,意義如下:

  • debug:在開發期間讓 log level 設為 debug,這要搭配下面的 log 共同使用。
  • auto_https disable_redirects:關閉自動把用戶從 HTTP 導向 HTTPS 的機制,個人不太喜歡這種隱式的重導,一般都會關掉。
  • email:申裝 TLS 憑證用的信箱,我們只需要提供信箱,其他 Caddy 會辦到好。

第二個區塊就是具體的位址的設定,在這裡位址設定成 http://,這表示不需要申裝 TLS,因為想要讓開發環境單純一點。

區塊內的部份:

  • encode zstd gzip:讓網頁壓縮傳輸,在開發環境沒什麼意義,可開可不開。
  • log:開啟 log 輸出,輸出目的是 stdout,如果把註解拿掉就會寫入檔案,log level 則會是前面全域設的 debug。
  • handle_path /api/*:把所有送往 /api/* 的請求轉送到位於 8000 埠的後端服務。
  • handle:把所有請求(除了上面的 /api/*)轉送到 3000 埠的前端服務。

handle_pathhandle 的區別為,handle_path 會去除掉 /api/ 再轉送給後端,因為我的後端並不認得那 /api/ 路徑,而 handle 則會原樣轉送,該用哪個就取決於每個環境的狀況囉!

後端走反向代理 前端為靜態 SPA

全域設定、log 設定參照前例,在此省略。

http:// {
	# Set this path to your site's directory.
	root * /usr/share/caddy/

	# Enable the static file server.
	file_server

	# Enable Zstandard and Gzip compression
	encode zstd gzip

	handle_path /api/* {
		# Proxies requests to one or more backends
		reverse_proxy unix//run/gunicorn.sock
	}

	handle {
		try_files {path} / # For built static site
	}
}

跟前例大同小異,只差幾個指令:

  • 多設定了 root 指定靜態網頁的檔案路徑、file_server 啟動靜態資源伺服器。
  • 最後的 try_files 把所有的網頁(除了那 /api/*)導向 SPA 的首頁,因為 SPA 通常都有自己的路由邏輯。

這裡的 SPA 是 Vue 的例子,把所有對 SPA 的請求都丟給 Vue Router 處理。

最後提醒一個重點中的重點:

檔案路徑要設對,不要像我設錯查半天。

結語

這篇文章介紹了 Caddy 2 的特性與 Caddyfile 的基本設定,Caddy 原生支援 HTTPS、HTTP/3、virtual host、反向代理,並且配置超級簡單,效能又好,實在沒有再回去用陳年的 Apache 的理由。

Caddy 2 的文件還沒有很完整,想要用的人還是必須多多參考 Caddy2 網站或 Caddy wiki 的文件。