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 version
Code 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/Caddyfile
Code language: Shell Session (shell)

The Caddyfile contents that worked for me:

{ order cgi before respond } localhost,, router, router.home { tls internal root * /www file_server 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 PROG=/usr/bin/caddy USE_PROCD=1 # starts after network starts START=21 # stops before networking stops STOP=89 start_service() { procd_open_instance 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 procd_close_instance }
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 caddyfile
Code 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:

[/home/] [//]
Code 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)