July 14, 2024: Edited this post to reflect working instructions for current version of caddy (2.8.4).
Hi there! This is my first post, and SNB has been massively helpful in my decision to get an ASUS router (AX86U) as well as integrating it into my home network.
I know that nginx is available as a Merlin add-on, but I've become quite smitten with caddy as it automatically generates certs and I generally find the config syntax a lot easier.
I wanted to see if I could install caddy on my router, and surprisingly, it worked! Here are the steps I used for anyone else interested. (If you see anything incorrect or inadvisable, please do let me know, though I only ask you do so constructively!)
Assumptions/Requirements
All steps below assume you're logged into the router over SSH.
Install Go
Folders and variables: Go
By default, Go sets itself up in $HOME, which by default is /root on tmpfs. We need to relocate it to the USB drive, and set environment variables so the Go build system knows where to find itself and its build system.
Folders and variables: Caddy
Caddy uses $XDG_DATA_HOME and $XDG_CONFIG_HOME to set its own environment. We want to keep it on /opt, and make sure it knows that.
Installing XCaddy and building Caddy
Replace with your own desired plugins in the demonstrated format, or none. Only --output is required.
If you want Caddy to generate certs automatically, you DO need a plugin for a supported DNS handler (like cloudflare).
transform-encoder is required to make Caddy output logs in common_log format needed by Fail2Ban etc.
NOTE: On my router, I got a fatal error on the very last step, claiming /opt/bin: permission denied. Nonetheless, the Caddy binary did compile to the right spot, and everything continued to work fine. If you know how to subvert this error, please let me know.
Installing as a service
We'll use the EntWare init script format to let us easily run Caddy in the background, restart, and check on it.
Persisting variables
NOTE: Caddy (and I assume any other Go app) will try to retrieve the number of CPU cores by querying /proc/self/cgroup—which doesn't exist on my AX86U—and will throw a (non-fatal) warning when it can't. You can get around this by exporting GOMAXPROCS yourself. I don't fully know what Go or Caddy does with this information, or what happens if you get it wrong. I left it commented so you can decide to set it yourself. (At any rate, the warning isn't visible when you run Caddy in the background as we're doing.)
Starting Caddy
Before you start it, you'll want to make sure you build your Caddyfile and check it with caddy fmt (while in the same folder as the Caddyfile). If it's not formatted right, you can fix it in-place by running caddy fmt --overwrite.
Troubleshooting
caddy start runs Caddy in the background, which keeps us from seeing its runtime logs (which only seem to show up on stdout).
If Caddy is dying unexpectedly, or something otherwise seems off, run /opt/etc/init.d/S98caddy stop (or killall caddy) to quit the service, then manually run caddy run --config /opt/etc/caddy/Caddyfile to see what it's doing. When you're satisfied, you can ^C and go back to running it through the init script.
If Caddy isn't working and caddy fmt says your config file is off, but it looks okay, try checking for random spaces around the {} brackets and deleting them. (Or use caddy fmt --overwrite)
Example Caddyfile
Just for fun, here's an example Caddyfile with DNS settings, logging, and a couple reverse proxy entries, one of which has CORS set and the other does not.
It's recommended to use wildcard domains for reverse proxying, so you don't leak your servers to SSL registrars, although sometimes that also means you have to set up CORS for things like server dashboards that use IFrames.
Also note that whatever you're using for your DNS, you have to set up a CNAME entry that points to your DDNS domain for every subdomain you wish to reverse proxy.
Editing Caddyfile remotely via SFTP
I don't want to SSH into my router and use vi every time I need to update my Caddyfile. If you don't have a preferred way to edit router files remotely yourself, I can at least recommend the SFTP sync extension for VS Code, the configuration of which is outside the scope of this post. However, it does require an SFTP server be running, so if you want to go this route make sure you also:
(Open to feedback if there are better ways to do this.)
Addendum for older models
See what hexcallm had to do additionally for their AC88U:
Hi there! This is my first post, and SNB has been massively helpful in my decision to get an ASUS router (AX86U) as well as integrating it into my home network.
I know that nginx is available as a Merlin add-on, but I've become quite smitten with caddy as it automatically generates certs and I generally find the config syntax a lot easier.
I wanted to see if I could install caddy on my router, and surprisingly, it worked! Here are the steps I used for anyone else interested. (If you see anything incorrect or inadvisable, please do let me know, though I only ask you do so constructively!)
Assumptions/Requirements
Requirement | Rationale |
---|---|
JFFS is on and persistent | If you want to keep Go on your system, you'll need to export variables in /jffs/scripts/init-start. |
USB drive is mounted | tmpfs will run out of memory if we try to build XCaddy inside it. |
EntWare is installed on USB at /opt | We'll be using the EntWare folder to store Caddy, and the instructions assume the default install path of /opt. |
Web Access from WAN is disabled | Caddy needs to be the only thing listening for web connections over WAN. |
Local Access Config has ports changed away from 80/443 | Not necessary if you want Caddy to use different ports. |
Dynamic DNS enabled | Any reverse proxy requires a way for WAN traffic to be forwarded to it. |
Port Forwarding set to forward 80/443 to router | (Or whatever ports you want Caddy to accept connections on.) |
All steps below assume you're logged into the router over SSH.
Install Go
Bash:
opkg update
opkg install go
Folders and variables: Go
By default, Go sets itself up in $HOME, which by default is /root on tmpfs. We need to relocate it to the USB drive, and set environment variables so the Go build system knows where to find itself and its build system.
Bash:
# Below line does not seem relevant anymore, but keeping it in case it is on your router.
mv $HOME/go /opt/home # So we don't run out of space
export GOROOT=/opt/bin/go # Go is here
export GOPATH=/opt/home/go # XCaddy will go here
export GOPROXY="https://proxy.golang.org" # Needed for install
export GOSUMDB="sum.golang.org" # Needed for install
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin # So we can access both
export GOTMPDIR=/opt/tmp # Temporary build folder for Go (change export to TMPDIR if you have issues)
Folders and variables: Caddy
Caddy uses $XDG_DATA_HOME and $XDG_CONFIG_HOME to set its own environment. We want to keep it on /opt, and make sure it knows that.
Bash:
mkdir /opt/var/log/caddy # Caddy logs will go here (need to be referenced in Caddyfile)
mkdir /opt/share/caddy # Caddy certs will go here
mkdir /opt/etc/caddy # Caddyfile will go here
# This sets Caddy's root
cat > /opt/etc/caddy/Caddyfile << EOF
{
storage file_system {
root /opt/share/caddy
}
}
EOF
export XDG_DATA_HOME=/opt/share
export XDG_CONFIG_HOME=/opt/etc
Installing XCaddy and building Caddy
Replace with your own desired plugins in the demonstrated format, or none. Only --output is required.
If you want Caddy to generate certs automatically, you DO need a plugin for a supported DNS handler (like cloudflare).
transform-encoder is required to make Caddy output logs in common_log format needed by Fail2Ban etc.
Bash:
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
# Build Caddy
xcaddy build --output /opt/bin \
--with github.com/caddy-dns/cloudflare \
--with github.com/caddyserver/transform-encoder
# Clean Go build cache
go clean -cache
go clean -modcache
NOTE: On my router, I got a fatal error on the very last step, claiming /opt/bin: permission denied. Nonetheless, the Caddy binary did compile to the right spot, and everything continued to work fine. If you know how to subvert this error, please let me know.
Installing as a service
We'll use the EntWare init script format to let us easily run Caddy in the background, restart, and check on it.
Bash:
cat > /opt/etc/init.d/S98caddy << EOF
#!/bin/sh
ENABLED="yes"
PROCS="caddy"
ARGS="start --config $XDG_CONFIG_HOME/caddy/Caddyfile"
WORK_DIR="$XDG_DATA_HOME/caddy"
DESC=$PROCS
PREARGS=""
PRECMD=""
POSTCMD=""
. /opt/etc/init.d/rc.func
EOF
chmod +x /opt/etc/init.d/S98caddy # Make it executable
Persisting variables
NOTE: Caddy (and I assume any other Go app) will try to retrieve the number of CPU cores by querying /proc/self/cgroup—which doesn't exist on my AX86U—and will throw a (non-fatal) warning when it can't. You can get around this by exporting GOMAXPROCS yourself. I don't fully know what Go or Caddy does with this information, or what happens if you get it wrong. I left it commented so you can decide to set it yourself. (At any rate, the warning isn't visible when you run Caddy in the background as we're doing.)
Bash:
cat >> /jffs/scripts/init-start << EOF
# For Caddy
export XDG_DATA_HOME=/opt/share
export XDG_CONFIG_HOME=/opt/etc
# export GOMAXPROCS=4 # Number of cores your CPU has
# Only if you want to keep the Go build system around
export GOROOT=/opt/bin/go
export GOPATH=/opt/home/go
export GOPROXY="https://proxy.golang.org"
export GOSUMDB="sum.golang.org"
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin
export GOTMPDIR=/opt/tmp # Change GOTMPDIR to TMPDIR if you have issues
EOF
Starting Caddy
Before you start it, you'll want to make sure you build your Caddyfile and check it with caddy fmt (while in the same folder as the Caddyfile). If it's not formatted right, you can fix it in-place by running caddy fmt --overwrite.
Bash:
/opt/etc/init.d/S98caddy start # Start Caddy in the background
/opt/etc/init.d/S98caddy check # Verify Caddy is running (if it's not, you probably have a config issue)
Troubleshooting
caddy start runs Caddy in the background, which keeps us from seeing its runtime logs (which only seem to show up on stdout).
If Caddy is dying unexpectedly, or something otherwise seems off, run /opt/etc/init.d/S98caddy stop (or killall caddy) to quit the service, then manually run caddy run --config /opt/etc/caddy/Caddyfile to see what it's doing. When you're satisfied, you can ^C and go back to running it through the init script.
If Caddy isn't working and caddy fmt says your config file is off, but it looks okay, try checking for random spaces around the {} brackets and deleting them. (Or use caddy fmt --overwrite)
Example Caddyfile
Just for fun, here's an example Caddyfile with DNS settings, logging, and a couple reverse proxy entries, one of which has CORS set and the other does not.
It's recommended to use wildcard domains for reverse proxying, so you don't leak your servers to SSL registrars, although sometimes that also means you have to set up CORS for things like server dashboards that use IFrames.
Also note that whatever you're using for your DNS, you have to set up a CNAME entry that points to your DDNS domain for every subdomain you wish to reverse proxy.
JSON:
{
storage file_system {
root /opt/share/caddy
}
}
*.domain.com {
tls {
dns cloudflare <cloudflare DNS key>
}
log {
output file /opt/var/log/caddy/<domain>-access.log
format transform "{common_log}"
}
@app host app.domain.com
handle @app {
reverse_proxy <server IP>:<port> {
header_down Access-Control-Allow-Origin *
}
}
@another_app host anotherapp.domain.com
handle @another_app {
reverse_proxy <server IP>:<port>
}
}
Editing Caddyfile remotely via SFTP
I don't want to SSH into my router and use vi every time I need to update my Caddyfile. If you don't have a preferred way to edit router files remotely yourself, I can at least recommend the SFTP sync extension for VS Code, the configuration of which is outside the scope of this post. However, it does require an SFTP server be running, so if you want to go this route make sure you also:
Bash:
opkg update
opkg install openssh-sftp-server
(Open to feedback if there are better ways to do this.)
Addendum for older models
See what hexcallm had to do additionally for their AC88U:
Tutorial - Installing Caddy reverse proxy
July 14, 2024: Edited this post to reflect working instructions for current version of caddy (2.8.4). Hi there! This is my first post, and SNB has been massively helpful in my decision to get an ASUS router (AX86U) as well as integrating it into my home network. I know that nginx is available...
www.snbforums.com
Last edited: