I have wanted to investigate the feasibility of moving to a more modern webserver setup on my home server, specifically Caddy or Nginx as they both sounded like they are much easier to set up and more streamlined than apache; but never seemed to get around to it. Well, finally I manged to over this past weekend.
The main case, serving out static content from one or more domains with HTTPS cert setup, was surprisingly easy and I was impressed by its ease of use. Caddy has a nice config syntax which is easy to read, reasonably intuitive and supports single- or multi-file configurations, with a fragment syntax and include mechanism to keep large, complex configs more manageable.
In order to fully replace Apache on my home server, I needed to ensure any replacement would support a few things over and above serving just static content from a single domain:
- Multiple domain hosting (virtual hosts)
- UNIX ~username webspaces
- Use traditional syslog format instead of JSON logs
- PHP (for Nextcloud)
- CGI (Common Gateway Interface)
- Golang vanity URLs to serve out go modules
Hosting Multiple Domains (virtual hosts) with Caddy
- Setting up multiple domain serving was very straightforward -- basic Caddy syntax and documentation spells it out clearly, refer to the Common Patterns section of the Caddy docs.
UNIX ~username webspaces with Caddy
- Hosting UNIX ~username style webspaces was pretty simple, just a basic use of the handle_path and root directives:
root * /home/joebloggs/public_html
}
Use Traditional syslog Format with Caddy instead of JSON Logs
- This one was simple, just add the following entry in the log {..} section of Caddyfile config:
format transform "{common_log}"
Hosting PHP and Nextcloud from Caddy
- Hosting a PHP app behind Caddy was trickier: I couldn't find examples that spelled out precisely the proper setup. I had to first discover what the fpm-php helper util/pkg was, and how to set it up, and that it has to run with the same user:group as Caddy, and the PHP files and data that are being served (by default fpm-php runs as nobody:nogroup); simply /etc/php/fpm-php[x.y]/fpm.d/www.conf to set user=caddy and group=caddy.
- Then, finally, the Caddyfile entry for one's domain must include the proper root and php_fastcgi proxy config:
blitter.com {# dir as root of the websiteroot * /var/www/www.blitter.com/htdocs# enable serving static files from root direncodefile_serverphp_fastcgi localhost:9000}
Hosting CGI from ~user web spaces with Caddy
- CGI was not too hard though it isn't spelled out in the Caddy Common Patterns documentation:
handle_path /~russtopia* {root * /home/russtopia/public_html
handle_path /cgi-bin/* {reverse_proxy localhost:10000 {transport fastcgi {env SCRIPT_FILENAME /home/russtopia/public_html/cgi-bin/{path}}}}
Serving Golang Vanity URLs with Caddy
- Golang 'vanity URLs' are a way to self-host one's own Go source package modules, or redirect domain assignments such that they are referred to just as modules on the big centralized source hosts like github.com, gitlab.io etc. For example, I have authored some Go programs and modules which I make available via the root name blitter.com/go/<module>. I want the Go tooling to be able to use go get, go install, and so on without issue.
I also have a rather unique git setup: I run git-daemon with packages visible from the server's /var/git/ directory, of which many are symlinked to the repo's actual location within a Gogs installation which lives and runs under the git:git user/group. In this way I can do raw git checkouts via the git:// or ssh:// schemes, as well as via https:// to my Gogs instance.
This setup in Caddy was not fully covered by any other online tutorial, at least not in my server's particular setup -- I use a /go/ URL endpoint within the overall blitter.com domain to host modules, rather than dedicating a go.blitter.com domain just for Go modules, so other tutorials didn't quite fit. So here's the entire setup that integrates the git-backend as well as golang vanity URL setup:
# Caddy tutorial on serving vanity go module URLs:# https://abhijithota.com/posts/golang-vanity-urls-using-caddy/####(gomodhandler) {handle /go/{args[0]} {@from_go query go-get=1handle @from_go {header Content-Type text/htmlrespond <<HTML<!DOCTYPE html><html><head><meta name="go-import" content="blitter.com/go/{args[0]} git https://blitter.com/git/{args[0]}"><!-- <meta http-equiv="refresh" content="0; url=https://blitter.com/git/{args[0]}" /> --></head></html>HTML 200}}}####www.blitter.com {redir https://blitter.com{uri}}blitter.com {
####> git-daemon ##### Caddy tutorial on git over HTTP(s) proxying git-daemon:# https://www.jamesatkins.com/posts/git-over-http-with-caddy/handle_path /git* {root * /var/git}handle_path /var/git* {root * /git}@git_cgi path_regexp "^.*/(HEAD|info/refs|objects/info/[^/]+|git-upload-pack)$"@git_static path_regexp "^.*/objects/([0-9a-f]{2}/[0-9a-f]{38}|pack/pack-[0-9a-f]{40}\.(pack|idx))$"handle @git_cgi {reverse_proxy unix//run/git-cgi.socket {transport fastcgi {#env SCRIPT_FILENAME ${pkgs.git}/libexec/git-core/git-http-backendenv SCRIPT_FILENAME /usr/libexec/git-core/git-http-backendenv GIT_HTTP_EXPORT_ALL 1env GIT_PROJECT_ROOT /var/git}}}handle @git_static {file_server {root /var/git}}####< git-daemon ####import gomodhandler bacillusimport gomodhandler brevityimport gomodhandler chacha20import gomodhandler cryptmtimport gomodhandler go-frodokemimport gomodhandler goutmpimport gomodhandler groestlimport gomodhandler herradurakeximport gomodhandler hkexshimport gomodhandler hopscotchimport gomodhandler kyberimport gomodhandler lpasswdimport gomodhandler moonphaseimport gomodhandler mtwistimport gomodhandler newhopeimport gomodhandler xsimport gomodhandler xsd}
Footnote 2: Sometime recently (as of go v1.2x) the go get system seemed to start requiring any go module have at least a latest tag applied otherwise the module will not be found. I had a few modules that lacked tags, so that was a real head-scratcher. Also do a go clean -modcache from time to time to ensure go is really fetching things as opposed to using cached copies, so if dependencies have broken it will be revealed when attempting go mod init && go mod tidy.


