How to Compile and Install Caddy 2.0 on FreeBSD 12

Jared Wolff · 2020.3.6· 6 Minute Read · freebsd · caddy · web

If you’re just as excited about Caddy 2.0 as I am, you probably can’t wait to install it. Unfortunately, there are some extra hoops you need to go through to install it on a FreeBSD 12.1 (or similar) server. In this tutorial, i’ll break down the steps to get you there.

Install go

Currently pkg has version 1.13.5. But, we need 1.14 according to Caddy’s Github.


Luckily, the port for go has been updated. So let’s download it and get it compiling!

First you’ll want to use portsnap fetch to get the latest snapshot

portsnap fetch
Looking up mirrors... 6 mirrors found.
Fetching public key from done.
Fetching snapshot tag from done.
Fetching snapshot metadata... done.
Fetching snapshot generated at Fri Mar  6 00:07:46 UTC 2020:
82ae2cbdbee69b4371b8b15b03b9f1ed1bc5da3955f46e          83 MB   22 MBps    04s
Extracting snapshot... done.
Verifying snapshot integrity...

Then extract go and Mk

portsnap extract lang/go
portsnap extract Mk
portsnap extract Templates

Then start the compilation process!

cd /usr/ports/lang/go/ && make install clean
===>  License BSD3CLAUSE accepted by the user
===>   go-1.14_1,1 depends on file: /usr/local/sbin/pkg - found
=> go1.14.src.tar.gz doesn't seem to exist in /usr/ports/distfiles/.
=> Attempting to fetch

This will take a few minutes depending the speed of your processor. You can make sure it’s installed by running go version after the compilation has completed:

go version
go version go1.14 freebsd/amd64

Install caddy

Once installed, you’ll need to clone the Caddy repository. Place it in your GOPATH/src/ directory.

git clone ""
git checkout v2.0.0-beta.15

Then change directories, make sure that GO111MODULE is set and start the build!

cd caddy/cmd/caddy/
setenv GO111MODULE auto
go build

Once complete you should have a nice caddy binary file in the caddy/cmd/caddy directory:

Caddy is an extensible server platform.
  caddy <command> [<args...>]
  adapt           Adapts a configuration to Caddy's native JSON
  build-info      Prints information about this build
  environ         Prints the environment
  file-server     Spins up a production-ready file server
  hash-password   Hashes a password and writes base64
  help            Shows help for a Caddy subcommand
  list-modules    Lists the installed Caddy modules
  reload          Changes the config of the running Caddy instance
  reverse-proxy   A quick and production-ready reverse proxy
  run             Starts the Caddy process and blocks indefinitely
  start           Starts the Caddy process in the background and then returns
  stop            Gracefully stops a started Caddy process
  validate        Tests whether a configuration file is valid
  version         Prints the version
Use 'caddy help <command>' for more information about a command.


Finally you should copy it over to /usr/local/bin/ or similar depending on how your system is set up. If you’re using a jail management software like Bastille, you can use the cp command:

bastille cp caddyjail ./caddy /usr/local/bin/

This will copy over your binary to the jail of your choice. In this case i’m transferring to a jail called caddyjail.

Starting caddy on boot

If you want to start caddy on startup (or as a service) you’ll need to install an rc.d script. Here’s an example of one i’ve borrowed and modified (credit to riggs for the original!)

# $FreeBSD: head/net/caddy/files/ 452063 2017-10-14 12:58:24Z riggs $
# PROVIDE: caddy
# KEYWORD: shutdown
# Add the following lines to /etc/rc.conf.local or /etc/rc.conf
# to enable this service:
# caddy_enable (bool):	Set to NO by default.
#				Set it to YES to enable caddy.
# caddy_user (user):		Set user to run caddy.
#				Default is "caddy".
# caddy_group (group):	Set group to run caddy.
#				Default is "caddy".
# caddy_conf (path):		Path to caddy configuration file.
#				Default is /usr/local/etc/caddyfile.conf

. /etc/rc.subr


load_rc_config $name

: ${caddy_enable:="NO"}
: ${caddy_user:="caddy"}
: ${caddy_group:="caddy"}
: ${caddy_conf:="/usr/local/etc/caddyfile.conf"}
: ${caddy_log:="/home/caddy/caddy.log"}
: ${caddy_env:="CADDYPATH=/home/caddy/"}
: ${caddy_https_port:="4443"}
: ${caddy_http_port:="8880"}

command_args="-f -p ${pidfile} /usr/bin/env ${caddy_env} ${procname} -agree -http-port ${caddy_http_port}  -https-port ${caddy_https_port} -conf=${caddy_conf} -log=${caddy_log} ${caddy_args}"


        if [ ! -e ${pidfile} ]; then
                install -o ${caddy_user} -g ${caddy_group} /dev/null ${pidfile};

run_rc_command "$1"

You can place it in /usr/local/etc/rc.d/caddy. Then add it to your boot up using sysrc:

sysrc caddy_enable="YES"

Or you can edit /etc/rc.conf directly. The choice is yours.

If you’re using Bastille, you can also run bastille sysrc caddyjail caddy_enable="YES" to accomplish the same for the jail.

Note: because I’m running caddy in a jail, i’ve changed the https port and http port to 4443 and 8880 respectively. That way you can forward connections on 443 and 80 using your pf firewall. Example below:

# ! IMPORTANT: this needs to be set before it's copied.
ext_addr=<your server ip>

# Caddy related
caddy_addr=<address of your caddy jail>

set block-policy return
scrub in on $ext_if all fragment reassemble
set skip on lo

table <jails> persist
nat on $ext_if from <jails> to any -> $ext_addr

# container routes
rdr pass inet proto tcp from any to port 80 -> $caddy_addr port 8880
rdr pass inet proto tcp from any to port 443 -> $caddy_addr port 4443

# Enable dynamic rdr (see below)
rdr-anchor "rdr/*"

block in all
pass out quick modulate state
antispoof for $ext_if inet
pass in inet proto tcp from any to any port ssh flags S/SA keep state

# make sure you also open up ports that you are going to use for dynamic rdr
pass in inet proto tcp from any to any port 80 flags S/SA keep state
pass in inet proto tcp from any to any port 443 flags S/SA keep state

Even if you’re not running it in a jail, you’ll likely want to forward privileged ports to non-priviledged ones.

Final Configuration

Additionally, you may need ca_root_nss installed. Simply run pkg install ca_root_nss on your target system/jail. You can also use bastille pkg caddyjail install ca_root_nss

You also may have noticed in the rc.d script, we’re using a caddy user to run caddy. You can set that up by using the pw command:

pw useradd caddy -m -s /usr/sbin/nologin

Or with Bastille like so:

bastille cmd caddy pw useradd caddy -m -s /usr/sbin/nologin

This will create a user that can’t log in but can be used to help “sandbox” the caddy daemon even further.

Finally, the rc.d script is expecting a configuration file in /usr/local/etc/caddyfile.conf You should place your configuration file here.

Finally start your caddy service using service:

service caddy start

By default logs go to /home/caddy/caddy.log. You can run tail -f /home/caddy/caddy.log to watch the server boot up. If you are using TLS, Caddy will attempt to get your certificate. As long as port 80 & 443 are unblocked, you should be good to go!


In this very quick and dirty tutorial, you’ve learned how to compile and install Caddy on FreeBSD 12.1. At this point you should be ready to customize, add domains and more. So let the fun begin!

The Ultimate Guide to Particle Gen3 Cover

Want to learn more about what Particle has to offer?

If you're trying to learn the ins and outs of Partilce IoT you should check out The Ultimate Guide to Particle Gen 3. Click the button below for more information.

Let's go!

Subscribe here!

You may also like