Caddy on OpenWrt with access to LuCI

After getting OpenWrt working on my EdgeRouter 4, since I’ve grown accustomed to the flexibility & ease of Caddy in other situations, I wondered if I could use it in this case. Turns out, yes! Especially thanks to Siger Yang’s notes and this cgi-ubus script. I had to make some adjustments on top of what Yang describes, so here’s what worked for me:

Note: I have extra storage mounted at /mnt/sda1; adapt as needed.

First, on the ER-4, none of the MIPS downloads worked for me, so I had to build it myself:

opkg install git-http golang
mkdir /mnt/sda1/caddy-build
cd /mnt/sda1/caddy-build
git clone ""
echo 'export GOPATH=/mnt/sda1/go' | tee -a /root/.shinit
echo 'export PATH=$PATH:$GOROOT/bin:$GOPATH/bin' | tee -a /root/.shinit
. /root/.shinit
go install
cd /mnt/sda1/caddy-build/caddy
xcaddy build v2.6.2 --with
cp ./caddy /usr/bin/caddy
caddy versionCode language: Shell Session (shell)

Having built it, and confirmed it runs with that last caddy version command, I did the following to stop & disable uhttpd, and set up caddy:

service uhttpd stop
service uhttpd disable
mkdir -p /etc/caddy/conf; mkdir /etc/caddy/data
echo 'XDG_CONFIG_HOME=/etc/caddy/conf' | tee -a /etc/caddy/.env
echo 'XDG_DATA_HOME=/etc/caddy/data' | tee -a /etc/caddy/.env
wget -O /etc/caddy/
chmod +x /etc/caddy/
nano /etc/caddy/CaddyfileCode language: Shell Session (shell)

The Caddyfile contents that worked for me:

	order cgi before respond

localhost,, router, router.home {
	tls internal
	root * /www

	cgi /cgi-bin/cgi-backup* /www/cgi-bin/cgi-backup {
		script_name /cgi-bin/cgi-backup
	cgi /cgi-bin/cgi-download* /www/cgi-bin/cgi-download {
		script_name /cgi-bin/cgi-download
	cgi /cgi-bin/cgi-exec* /www/cgi-bin/cgi-exec {
		script_name /cgi-bin/cgi-exec
	cgi /cgi-bin/cgi-upload* /www/cgi-bin/cgi-upload {
		script_name /cgi-bin/cgi-upload
	cgi /cgi-bin/luci* /www/cgi-bin/luci {
		script_name /cgi-bin/luci
	cgi /ubus* /etc/caddy/ {
		script_name /ubus

I still wanted to serve a custom index.html from /www so I just made that the root, instead of the file_server /luci-static* { root /www } and redir / /cgi-bin/luci that Yang did. I also found that I needed the extra cgi script handling for images, downloads, etc. to work in LuCI. (There might be a more concise way to configure this but I think spelling it out for now makes it clearer.)

Finally, an OpenWrt init script at /etc/init.d/caddy:

#!/bin/sh /etc/rc.common



# starts after network starts
# stops before networking stops

start_service() {
  procd_set_param command "$PROG" run --envfile /etc/caddy/.env --config /etc/caddy/Caddyfile --adapter caddyfile
  procd_set_param stdout 1
  procd_set_param stderr 1
}Code language: Shell Session (shell)

And then to enable the service, test that it works, and examples of updating & reloading the Caddyfile:

service caddy enable
service caddy start
netstat -lnpt | grep -e 80 -e 443
/usr/bin/caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile
/usr/bin/caddy fmt --overwrite /etc/caddy/Caddyfile
/usr/bin/caddy reload --config /etc/caddy/Caddyfile --adapter caddyfileCode language: Shell Session (shell)

One of the neat things about this is easy SSL and reverse proxying. Since I also use the OpenWrt AdGuard Home service, I’m able to include this in the Caddyfile for easy access:

adguard.home {
	tls internal
	reverse_proxy localhost:8081

And the DNS is working because the AdGuardHome is configured for upstream DNS:

[//] language: CSS (css)

…and the OpenWrt dnsmasq /etc/config/dhcp contains:

config dnsmasq
	option domainneeded '1'
	option local '/home/'
	option domain 'home'
	option expandhosts '1'
	# ...

config domain
	option name 'router.home'
	option ip ''

config domain
	option name 'adguard.home'
	option ip ''Code language: PHP (php)